# Scan Artifacts Specification This document describes the file layout and purpose of each artifact produced by a completed security scan. --- ## Directory Structure All scan artifacts live in a `.security/` directory at the repository root: ``` .security/ ├── scan.db # SQLite database (source of truth) ├── findings.json # Exported findings — simple format (generated by finalize.py) ├── report.md # Human-readable report (generated by finalize.py) ├── integrity.sha256 # SHA-256 of findings.json (tamper detection) ├── threat-model.md # Repository threat model (if generated) └── scans/ └── / ├── architecture.md # Phase 1 output (full-scan only) ├── findings.json # Structured format — validated against report-schema.json ├── security-report.html # Self-contained HTML report ├── report.json # Machine-readable summary └── manifest.json # File hashes + completion timestamp ``` --- ## Artifact Descriptions ### scan.db — Source of Truth A SQLite database containing the complete scan state. This is the authoritative data store that all other artifacts are derived from. **Tables:** - `scans` — Scan metadata (id, repo, branch, started_at, completed_at, config) - `findings` — All findings conforming to the schema in `finding-format.md` - `triage_log` — Status change history (who changed what, when, and why) **Rules:** - All mutations happen here first. Never edit `findings.json` or `report.md` directly. - The database is append-only during a scan. Findings are inserted, never deleted (status changes use the `status` field). - Triage actions (marking false-positive, accepted-risk, etc.) are recorded with a timestamp and reason in `triage_log`. **Typical operations:** ```sql -- Count open findings by severity SELECT severity, COUNT(*) FROM findings WHERE scan_id = ? AND status = 'open' GROUP BY severity ORDER BY CASE severity WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 WHEN 'low' THEN 4 WHEN 'info' THEN 5 END; ``` --- ### findings.json — Sealed Export A JSON array of all findings from the scan, exported from `scan.db` at finalization time. **Properties:** - Generated by `finalize.py` — never written by hand - Represents a point-in-time snapshot of findings at scan completion - Immutable after generation. If findings change (triage, fixes), re-run finalization to produce a new export - Each entry conforms exactly to the schema in `finding-format.md` **Structure:** ```json { "scan_id": "f0e1d2c3-b4a5-6789-0123-456789abcdef", "repository": "myorg/myapp", "branch": "main", "finalized_at": "2026-06-24T03:30:00Z", "findings": [ { /* finding object per finding-format.md */ } ] } ``` --- ### report.md — Human-Readable Report The markdown report formatted according to `report-format.md`. Intended for human review, pull request comments, or export to documentation systems. **Properties:** - Generated from the same data as `findings.json` at finalization time - Read-only artifact — regenerate rather than edit - Self-contained: readers should not need to consult `scan.db` or `findings.json` --- ### integrity.sha256 — Tamper Detection A SHA-256 hash of `findings.json`, computed at finalization time. **Format:** ``` findings.json ``` Example: ``` e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 findings.json ``` **Purpose:** - Allows downstream tools (CI gates, compliance checks, dashboards) to verify that `findings.json` has not been modified since finalization - If the hash does not match, the findings export must be considered untrusted and regenerated from `scan.db` **Verification:** ```bash cd .security/ sha256sum -c integrity.sha256 ``` Expected output on success: `findings.json: OK` --- ### threat-model.md — Repository Threat Model (Optional) A structured threat model for the repository, generated on first scan or when explicitly requested. Not regenerated on every scan. **Contains:** - Trust boundaries (what's inside vs. outside the security perimeter) - Data flows (what sensitive data moves where) - Entry points (APIs, file uploads, webhooks, CLI inputs) - Assets (databases, credentials, user data, secrets) - Threat actors (who might attack and what they'd target) **Rules:** - Only created when explicitly triggered or on first scan of a new repository - Updated manually or on request — not overwritten by routine scans - Informs severity decisions: a finding that crosses a trust boundary is more severe than one contained within a trusted zone --- ## Lifecycle 1. **Scan starts** → `scan.db` is created (or a new scan row is inserted into an existing database) 2. **Analysis runs** → Findings are inserted into `scan.db` as they are discovered 3. **Triage (optional)** → Agent or human reviews findings, updates statuses in `scan.db` 4. **Finalization** → `finalize.py` exports `findings.json`, generates `report.md`, computes `integrity.sha256` 5. **Post-seal** → Artifacts are committed, pushed, or attached to a PR. No further modifications without re-finalization. --- ## Gitignore Considerations The `.security/` directory should generally be committed so findings are tracked alongside code. However: - `scan.db` may be gitignored in repositories where only the sealed artifacts matter (reduces churn from SQLite binary diffs) - If `scan.db` is gitignored, `findings.json` + `integrity.sha256` become the durable record Recommended `.gitignore` entry when excluding the database: ```gitignore .security/scan.db ```