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>
This commit is contained in:
TheFlow 2026-02-12 07:47:53 +13:00
parent 0757dd3670
commit 40b9692dbc

View file

@ -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