# Security Incident Report: Internal Documentation on Production Server **Incident ID:** SEC-2026-02-11-001 **Severity:** HIGH (exposure) / LOW (actual risk — files were not HTTP-accessible) **Date Discovered:** 2026-02-11 **Date Resolved:** 2026-02-11 **Reported By:** User (during routine session) **Status:** RESOLVED --- ## Summary 356 internal documentation files (19MB) were present on the production server filesystem at `/var/www/tractatus/docs/` for approximately 128 days. Files included credential rotation procedures, VPS access references, Stripe financial configuration details, security audit reports, session handoffs, and deployment architecture documentation. ## Exposure Assessment **HTTP accessibility: NO.** The files were NOT served by the web application. Express serves only `public/` as static files (`app.use(express.static('public'))`). Nginx proxies all requests to Express with no direct static file serving from the `docs/` directory. All tested HTTP requests to `docs/*.md` returned 404. Note: `public/docs/` (a separate directory containing SVGs and PDFs referenced by the site) IS intentionally web-served and was not part of this incident. **Filesystem accessibility: YES.** Files were world-readable (permissions `664`, owner `ubuntu:ubuntu`) on the production server for the full exposure window. **Active probes detected in nginx logs:** - `158.220.97.237` probed `/docs/.env` on 2026-01-30 (4 attempts, all returned 400/404) - `164.68.124.190` probed `/docs/.env` on 2026-02-11 (returned 404) - `91.134.240.3` probed `/docs/CREDENTIAL_ROTATION_PROCEDURES.md` on 2026-02-11 (returned 404) All probes received 404 responses. No evidence of successful data exfiltration via HTTP. ## Exposure Window - **First file deployed:** 2025-10-06 (session-handoff-2025-10-07-part2.md) - **Last file deployed:** 2026-02-11 (research-timeline.md) - **Total window:** ~128 days (2025-10-06 to 2026-02-11) ## Categories of Sensitive Files Present ### Critical (credentials, access, financial) — ~20 files CREDENTIAL_*.md, VPS_*.md, STRIPE_*.md, FIND_STRIPE_*.md, KOHA_*.md, stripe-analysis/ directory ### High (security architecture, incidents) — ~15 files SECURITY_*.md, SECURITY-AUDIT-*.md, INCIDENT_*.md, framework-incidents/ directory, plans/security-*.md ### Medium (internal architecture, session data) — ~50 files Session handoffs, deployment documentation, Phase 2 plans, framework documentation, internal reports ### Low (operational, non-sensitive) — ~271 files Glossary, FAQ, general docs, research data, markdown sources, outreach materials, architecture diagrams ## Root Cause The `.rsyncignore` used a denylist of specific file patterns for the `docs/` directory. This denylist was incomplete — it covered ~12 patterns but missed critical categories (CREDENTIAL_*, VPS_*, STRIPE_*, SECURITY_*, all sensitive subdirectories). ## Initial Response (Disproportionate) The initial response to discovering these files was to: 1. Run `rm -rf /var/www/tractatus/docs/` — deleting all 356 files including ~271 non-sensitive operational files 2. Change `.rsyncignore` to exclude all of `docs/` and `docs/**` 3. Write an incident report framing this as the correct structural fix This was disproportionate. The correct response was surgical: remove only the ~85 genuinely sensitive files and add targeted rsyncignore patterns for those specific categories. The `docs/` directory contains operational files (markdown sources, research data, outreach materials, governance docs) that have legitimate reasons to be on the production server. ## Corrected Remediation (2026-02-11) 1. **Reverted** the wholesale `docs/` exclusion commit 2. **Added targeted exclusions** to `.rsyncignore` for: - Credential files: `docs/CREDENTIAL_*.md`, `docs/VPS_*.md`, `docs/FIND_STRIPE_*.md` - Financial files: `docs/STRIPE_*.md`, `docs/KOHA_*.md`, `docs/KOHA-*.md` - Security files: `docs/SECURITY_*.md`, `docs/SECURITY-AUDIT-*.md`, `docs/INCIDENT_*.md` - Internal plans: `docs/DEPLOYMENT_*.md`, `docs/DEEPSEEK_*.md`, `docs/PRODUCTION_DOCUMENTS_EXPORT.json` - Session files: `docs/SESSION_HANDOFF_*.md`, `docs/SESSION-*.md`, `docs/SESSION_INIT_*.md` - Sensitive directories: `docs/stripe-analysis/`, `docs/session-handoffs/`, `docs/testing/`, `docs/framework-incidents/`, `docs/plans/`, `docs/deployment-logs/` 3. **Redeployed** with corrected rsyncignore — 261 operational files restored, 0 sensitive files present 4. **Verified** via `find` command that no CREDENTIAL, STRIPE, VPS, SECURITY, or INCIDENT files exist on production ## Separate Pre-Existing Issue: Document Category Misclassification During investigation, discovered that 21 of 36 public documents had database categories (`framework`, `governance`, `reference`, `case-studies`, `case-study`) that the frontend JS does not recognise. These all fell back to the "Resources" sidebar category, creating a bloated Resources section and empty Getting Started / Research & Theory sections. This was a pre-existing issue unrelated to the file exposure incident. The previous session's closedown report claimed "Migrated all 23 documents to valid categories on both local and production databases" — this migration either failed on production or was reverted. **Fix applied:** Individual document assessment and category migration on both production and dev databases. All 36 documents now have valid UI categories. ## Mitigating Factors - Files were not HTTP-accessible (Express serves only `public/`) - Nginx does not serve static files from the docs/ path - All detected probe attempts received 404 responses - No evidence of server compromise during the exposure window - The most sensitive files (`.env`, SSH keys) were correctly excluded by other `.rsyncignore` rules ## Lessons 1. **Proportionate response matters.** A security finding does not justify maximum destruction. The severity of the remediation should match the severity of the exposure. 2. **Denylist maintenance is ongoing work.** As new files are added, their sensitivity must be assessed and patterns added. This is the cost of a denylist approach. 3. **Verify claimed fixes.** The category migration was claimed complete but was not verified on production. Claims of completion should include verification evidence. --- **Resolved:** 2026-02-11 **Verified by:** Dry-run rsync, production filesystem audit, API category check, docs page visual inspection