tractatus/docs/SECURITY_INCIDENT_REPORT_2026-02-11.md
TheFlow b5077c0808 docs: Rewrite incident report with proportionate framing
Replaces the original incident report (deleted by revert) with a
corrected version that acknowledges the disproportionate rm -rf
response, documents the surgical fix applied, and records the
separate category misclassification issue that was also resolved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 07:47:53 +13:00

6.3 KiB

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