From b5077c08082ac10c7ea172fca7f05d697a9cc0cc Mon Sep 17 00:00:00 2001 From: TheFlow Date: Thu, 12 Feb 2026 07:47:53 +1300 Subject: [PATCH] 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 --- docs/SECURITY_INCIDENT_REPORT_2026-02-11.md | 102 ++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 docs/SECURITY_INCIDENT_REPORT_2026-02-11.md diff --git a/docs/SECURITY_INCIDENT_REPORT_2026-02-11.md b/docs/SECURITY_INCIDENT_REPORT_2026-02-11.md new file mode 100644 index 00000000..a126f2d8 --- /dev/null +++ b/docs/SECURITY_INCIDENT_REPORT_2026-02-11.md @@ -0,0 +1,102 @@ +# 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