|
| 1 | +<!-- SPDX-License-Identifier: MPL-2.0 --> |
| 2 | +# PROOF-PROGRAMME.md — panic-attack from first principles |
| 3 | + |
| 4 | +> Strategic plan for moving panic-attack from "two completed sibling Idris2 proofs (PA1 + PA2)" to **end-to-end formal soundness** of detection, inference, and persistence — without changing perf or functionality. |
| 5 | +
|
| 6 | +## Status as of 2026-06-02 |
| 7 | + |
| 8 | +### Completed proofs |
| 9 | + |
| 10 | +| ID | File | Mechanises | |
| 11 | +|---|---|---| |
| 12 | +| **PA1** | `src/abi/PatternCompleteness.idr` | Every `Lang` constructor has at least one analyzer; every `WPCategory` has at least one detector; cross-language checks apply uniformly. Top theorem: `completeScanForAll`. | |
| 13 | +| **PA2** | `src/abi/ClassificationSoundness.idr` | `Severity` is totally ordered (`LTE`); `maxSeverity` is commutative + idempotent; numeric ABI encoding preserves the ordering. | |
| 14 | + |
| 15 | +### Open obligations (from `PROOF-NEEDS.md` line 19–23) |
| 16 | + |
| 17 | +| Component | Property | |
| 18 | +|---|---| |
| 19 | +| Bridge reachability soundness | No reachable dep wrongly classified as phantom. | |
| 20 | +| Attestation chain unforgeability | Intent/evidence/seal triple cryptographically binds; tampering detectable. | |
| 21 | +| Kanren taint analysis | Taint propagation tracks **every** tainted dataflow (no missed sinks). | |
| 22 | + |
| 23 | +## Three-layer landscape |
| 24 | + |
| 25 | +### Layer 1 — **Surface** (per-category detection soundness + completeness) |
| 26 | + |
| 27 | +For each `WeakPointCategory`, two properties: |
| 28 | +- **Soundness**: every detector emission corresponds to a real instance of the pattern. |
| 29 | +- **Completeness**: every real instance of the pattern is detected (modulo declared scope — e.g. test files, see [[v2.5.5 test_context]]). |
| 30 | + |
| 31 | +PA1 proves *dispatch* completeness (every category has a detector). PA2 proves *severity ordering* on what's emitted. Layer-1 work extends this **per category** to the pattern-recognition function itself. |
| 32 | + |
| 33 | +Layer 1.0 — **Common machinery** (one-time, foundational): |
| 34 | +- Formalise `Pattern` as a regex/AST predicate over normalised source. |
| 35 | +- Prove the comment / string-literal stripping function (`strip_inline_cfg_test_modules` + global comment-strip) is **idempotent** and **preserves non-token positions**. This justifies the analyzer's stripping pass as a sound preprocessing step. |
| 36 | +- Recommended prover: **Idris2** (sibling to existing PA1/PA2). Difficulty: medium. Value: every category proof becomes a simple application of Layer 1.0 + the category's regex. |
| 37 | + |
| 38 | +Layer 1.1–1.25 — **Per category** (25 obligations, parallelisable): |
| 39 | + |
| 40 | +For PA001..PA025 each, state and prove: |
| 41 | +``` |
| 42 | +soundness : (file : String) -> (locs : List Loc) |
| 43 | + -> detect category file = Just locs |
| 44 | + -> AllOf (\loc => isInstanceOf category (locate file loc)) locs |
| 45 | +
|
| 46 | +completeness : (file : String) -> (loc : Loc) |
| 47 | + -> isInstanceOf category (locate file loc) |
| 48 | + -> stripFalsePositives category file loc = NotSuppressed |
| 49 | + -> Elem loc (Maybe.toList (detect category file)) |
| 50 | +``` |
| 51 | + |
| 52 | +Worked example — **PA004 (UnsafeCode)**: |
| 53 | +```idris |
| 54 | +-- src/abi/PA004_UnsafeCode.idr |
| 55 | +SoundnessPA004 : (file : String) -> (loc : Loc) |
| 56 | + -> contains (stripStrings file) "unsafe " loc |
| 57 | + -> isUnsafeBlock (locate file loc) |
| 58 | +``` |
| 59 | +This is *decidable* because `contains` is a constructive predicate. The matching Rust detector at `src/assail/analyzer.rs:1052` simply mirrors the same predicate. |
| 60 | + |
| 61 | +**Estimate**: 25 obligations × ~30 LoC each = ~750 LoC of Idris2 proof; ~2 weeks of focused work. |
| 62 | + |
| 63 | +**Cross-fit with `proven`**: Layer-1 categories that wrap simple validators (PA017 `PathTraversal`, PA022 `CryptoMisuse` on the hash-algorithm side) can borrow proofs from `proven`'s `SafePath` and `SafeCrypto` directly — see [proven swap-outs](#proven-cross-fit) below. |
| 64 | + |
| 65 | +### Layer 2 — **Engine** (inference: miniKanren + taint) |
| 66 | + |
| 67 | +#### 2.1 miniKanren unification soundness |
| 68 | + |
| 69 | +`src/kanren/core.rs` ships a v2.0.0 microKanren-style engine: `Term`, `Substitution`, `Goal`, `mplus`/`bind`. Three properties to mechanise: |
| 70 | + |
| 71 | +1. **Unification correctness** — `unify(u, v, σ)` returns `σ'` iff there's an mgu `θ` with `θ(u) = θ(v)` extending `σ`. |
| 72 | +2. **Substitution composition** — `walk*(t, σ)` is the canonical representative of `t`'s equivalence class in `σ`. |
| 73 | +3. **Search completeness** — `mplus` interleaving doesn't drop a satisfiable goal under bounded depth. |
| 74 | + |
| 75 | +Recommended prover: **Coq** (because the standard miniKanren correctness proofs by Bender/Hemann use Coq; reuse their reasoning machinery). Difficulty: hard but well-trodden. |
| 76 | + |
| 77 | +**Estimate**: ~1500 LoC Coq (Bender's thesis is ~900 LoC; we extend with our taint goals). 4 weeks. |
| 78 | + |
| 79 | +#### 2.2 Taint propagation completeness |
| 80 | + |
| 81 | +`src/kanren/taint.rs` defines taint flow rules (CommandInjection / UnsafeDeserialization / DynamicCodeExecution / UnsafeFFI / AtomExhaustion / PathTraversal). The property: |
| 82 | + |
| 83 | +> If a source-to-sink dataflow path exists in the program AST, the rule engine emits a `WeakPoint` whose `recommended_attack` reflects the sink category. |
| 84 | +
|
| 85 | +Two sub-obligations: |
| 86 | +- **Source coverage**: every taint source the rules recognise is enumerated in a `Datasource` ADT and reflected in the proof. |
| 87 | +- **Sink coverage**: same for sinks. |
| 88 | +- **Transitivity**: if `flows(a→b)` and `flows(b→c)`, then `flows(a→c)`. |
| 89 | + |
| 90 | +Mechanise as a **fixed-point lattice** in Coq. The kanren engine becomes the procedural computation of the lattice; the proof shows the procedural answer = the least fixed point. |
| 91 | + |
| 92 | +#### 2.3 Cross-language analyzer soundness |
| 93 | + |
| 94 | +`src/kanren/crosslang.rs` — when a finding crosses an FFI boundary (Rust ↔ Zig, Rust ↔ C, Python ↔ Rust), the cross-language rules emit a `UnsafeFFI` weak point. Soundness here = the FFI boundary in the AST is a real ABI boundary. |
| 95 | + |
| 96 | +This is **hard** because it requires modelling ABIs in the prover. Defer to Layer-2 follow-up; PA1's per-language detector dispatch already gives us a coarse safety net. |
| 97 | + |
| 98 | +### Layer 3 — **Persistence + Integrity** |
| 99 | + |
| 100 | +#### 3.1 Hexad↔Octad isomorphism |
| 101 | + |
| 102 | +`src/storage/mod.rs` defines `PanicAttackHexad` (6-tuple in panic-attack's own model) and pushes to verisimdb as an `Octad` (8-tuple). The hexad has fewer fields; the octad adds `attestation` + `provenance`. |
| 103 | + |
| 104 | +Property: the round-trip `panic-attack-hexad → verisimdb-octad → panic-attack-hexad` is the identity on the hexad fields. |
| 105 | + |
| 106 | +Recommended prover: **proptest** in Rust with `arbitrary` instances on both records. The structural-equivalence proof is trivial enough (both are records of `Option<String>` and `Vec<String>`) that property testing is sufficient evidence; formalising in Idris2 would be busywork. |
| 107 | + |
| 108 | +**Estimate**: 50 LoC proptest, 1 day. |
| 109 | + |
| 110 | +#### 3.2 Attestation chain unforgeability |
| 111 | + |
| 112 | +`src/attestation/{chain,envelope,evidence,intent,seal}.rs`. The triple is `Intent → Evidence → Seal`; the `Seal` includes an Ed25519 signature over `(Intent, Evidence)`. |
| 113 | + |
| 114 | +Properties: |
| 115 | +1. **Integrity**: tampering with `Intent` or `Evidence` invalidates `Seal`. |
| 116 | +2. **Authenticity**: only the holder of the signing key can produce a valid `Seal`. |
| 117 | +3. **Non-repudiation**: a valid `Seal` is publicly verifiable. |
| 118 | + |
| 119 | +These reduce to standard Ed25519 properties (EUF-CMA). Mechanise via: |
| 120 | +- A small Idris2 model of the chain with abstract `Sig` and `Hash` operations. |
| 121 | +- An assumption `ed25519_euf_cma : ∀ k m m'. Verify(k, m, Sign(k, m)) ∧ ¬Verify(k, m', Sign(k, m))`. |
| 122 | +- Derive (1)–(3) from the assumption + chain structure. |
| 123 | + |
| 124 | +The proof is **trivial given the cryptographic assumption** — the work is being honest about the assumption and matching it to the `ed25519-dalek` API our Rust code uses. |
| 125 | + |
| 126 | +Recommended prover: **Idris2** (matches PA1/PA2; tiny). Difficulty: easy. Value: high — attestation is load-bearing for the audit trail. |
| 127 | + |
| 128 | +**Estimate**: 200 LoC Idris2, 3 days. |
| 129 | + |
| 130 | +#### 3.3 Bridge reachability soundness |
| 131 | + |
| 132 | +`src/bridge/reachability.rs` walks `Cargo.lock` + crate graph to classify deps as `Mitigable` / `Unmitigable` / `Informational`. The "phantom" classification is the most consequential — a phantom dep that's actually reachable becomes a missed vuln. |
| 133 | + |
| 134 | +Property: `classify(dep) = Phantom` ⇒ `dep` does not appear on any reachable path from the root crate. |
| 135 | + |
| 136 | +This is a **graph reachability** proof. Formalise the lockfile as a labelled graph, the reachability predicate as transitive closure of dep edges, and `classify` as a sound under-approximation: if it returns Phantom, the corresponding reachability is False. |
| 137 | + |
| 138 | +Recommended prover: **Coq** (graph reasoning is well-trodden; Idris2 can do it but Coq's `Set` libraries are nicer for transitive closure). Difficulty: medium. Value: high — this is the PR #87 issue from earlier. |
| 139 | + |
| 140 | +**Estimate**: ~600 LoC Coq, 1.5 weeks. |
| 141 | + |
| 142 | +## Proven cross-fit |
| 143 | + |
| 144 | +From the `hyperpolymath/proven` survey (2026-06-02): two leaf-validator candidates qualify as semantic-equivalent + perf-neutral: |
| 145 | + |
| 146 | +| # | Swap | Where | Path | |
| 147 | +|---|---|---|---| |
| 148 | +| 1 | `SafePath::has_traversal` + `sanitize_filename` | `src/abduct/mod.rs:123,266`; `src/main.rs:2314,2377` (`fs::canonicalize(..).unwrap_or_else(\|_\| dir)` patterns) | **port-to-Rust** with proptest invariants against proven's Idris2 reference. NOT FFI (dylib build cost too high for `cargo install`). | |
| 149 | +| 2 | `SafeUrl::parse` | `src/storage/mod.rs:1071` `VERISIMDB_URL` (currently raw `String`, no scheme/host validation before HTTP POST) | **port-to-Rust** wrapping `url::Url` + proptest scheme-required invariant. | |
| 150 | + |
| 151 | +What `proven` does NOT cover (and Layer 1–3 must prove from first principles): |
| 152 | +- miniKanren engine (Layer 2.1) |
| 153 | +- 49-language analyzer dispatch (PA1 already covers this) |
| 154 | +- Hexad/Octad data model (Layer 3.1) |
| 155 | +- A2ML attestation chain (Layer 3.2) |
| 156 | +- Bridge reachability (Layer 3.3) |
| 157 | +- Sweep tracker / mass-panic temporal index (no equivalent) |
| 158 | +- Adjudicate / Axial / Ambush / Abduct merge logic (bespoke) |
| 159 | + |
| 160 | +Skip (semantic mismatch or already-total): |
| 161 | +- `SafeJson` — `serde_json::from_str` is already total + typed. |
| 162 | +- `SafeRegex` — `regex` crate is already RE2-lineage (linear-time, ReDoS-safe). |
| 163 | +- `SafeDateTime` — `chrono::Utc::now().to_rfc3339()` is total on emit. |
| 164 | +- `SafeCommand` — `Command::new(&str_args)` doesn't shell-interpolate. |
| 165 | +- `SafeEnv` — env keys in panic-attack are compile-time literals. |
| 166 | +- `SafeUUID` — semantic mismatch (we use deterministic timestamps for replayability). |
| 167 | + |
| 168 | +## Sequencing |
| 169 | + |
| 170 | +Recommended order (each row is parallel-iseable; rows are sequenced): |
| 171 | + |
| 172 | +| # | Phase | Output | Estimated effort | Risk | |
| 173 | +|---|---|---|---|---| |
| 174 | +| 1 | **Foundation**: Layer 1.0 (comment/string-strip idempotence + token preservation) | `src/abi/Stripping.idr` + 1 Idris2 module | 3–5 days | low | |
| 175 | +| 2 | **Quick wins**: Hexad↔Octad roundtrip proptest + Layer 1 PA001/PA004/PA017 | 200 LoC Rust proptest + 3 Idris2 modules | 1 week | low | |
| 176 | +| 3 | **Cross-fit**: SafePath + SafeUrl ports (with proptest against `proven`'s Idris2 reference) | 2 Rust modules + 2 proptest suites | 1 week | low | |
| 177 | +| 4 | **Persistence**: Attestation chain (Layer 3.2) | 1 Idris2 module | 1 week | low | |
| 178 | +| 5 | **Engine**: miniKanren correctness (Layer 2.1, ~1500 LoC Coq) | `formal/Kanren.v` | 4 weeks | medium (well-trodden, but big) | |
| 179 | +| 6 | **Surface**: remaining PA categories (Layer 1.1–1.25 minus quick-wins) | ~22 Idris2 modules | 4 weeks | medium | |
| 180 | +| 7 | **Reachability**: bridge soundness (Layer 3.3, Coq graph) | `formal/BridgeReachability.v` | 1.5 weeks | medium | |
| 181 | +| 8 | **Taint completeness**: kanren taint flow (Layer 2.2) | extends `formal/Kanren.v` | 3 weeks | high (fixpoint lattice + soundness witness) | |
| 182 | +| 9 | **FFI ABI** (Layer 2.3 deferred — needs ABI modelling) | TBD | TBD | high — defer until 1–8 land | |
| 183 | + |
| 184 | +**Total to row 8**: ~16 calendar weeks of focused proof work for "panic-attack is provably sound to the per-category and per-flow level". After row 8, only the cross-language FFI ABI soundness remains as an aspirational item. |
| 185 | + |
| 186 | +## What this is NOT |
| 187 | + |
| 188 | +- **Not** a sweep to add `Admitted` placeholders. Every new Idris2 module must `Qed`-close (or its Coq equivalent) before landing. If a proof is too hard, the obligation stays open and we record it in `PROOF-NEEDS.md` — that's the honest path. |
| 189 | +- **Not** a performance regression. proven swap-outs are explicitly evaluated for perf neutrality; the rest of the programme is pure docs/proof artefacts that don't touch the hot path. |
| 190 | +- **Not** a 25-PR storm. Group by phase; one PR per phase row above. |
| 191 | + |
| 192 | +## References |
| 193 | + |
| 194 | +- `PROOF-NEEDS.md` — current proof-debt ledger (this document is the long-term plan; that one tracks the immediate next step). |
| 195 | +- `src/abi/PatternCompleteness.idr` (PA1) |
| 196 | +- `src/abi/ClassificationSoundness.idr` (PA2) |
| 197 | +- `hyperpolymath/proven` — leaf validator library; cross-fit candidates listed above. |
| 198 | +- ROADMAP.adoc "Long-Term" section: "Formal verification of core analysis rules (via proven library)" — this document operationalises that bullet. |
0 commit comments