mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-19 21:19:28 +00:00
docs(pre-push-safety-net): record approved design and implementation plan
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
# Design: pre-push-safety-net
|
||||
|
||||
Status: approved for task generation on 2026-09-12 following the maintainer's "ok, next kiro phase".
|
||||
Requirements and tasks are approved; group 1 implementation was authorized by "approve." on
|
||||
2026-09-12. Runtime and latency claims remain unverified.
|
||||
|
||||
## Overview
|
||||
|
||||
The hook is a bounded transaction around the existing ranker and detector. It analyzes immutable
|
||||
pushed tips, supplies change context to the ranker, and admits only actionable security regressions
|
||||
to the terminal. The ranker remains the scope mechanism. Third-party vendor code stays outside
|
||||
targets and graphs. WordPress is one regression workload, not the architecture's organizing unit.
|
||||
|
||||
The design separates three decisions: the ranker selects questions, the arbiter establishes modeled
|
||||
claims, and hook policy decides whether a supported change-related claim merits an interruption.
|
||||
An entailed rung is necessary for initial admission but is not sufficient by itself: a wrong fact,
|
||||
an irrelevant old defect, or an unactionable witness can still produce noise.
|
||||
|
||||
### Goals and Non-Goals
|
||||
|
||||
- Deliver specific security findings with witness, change relationship, location, and repair direction.
|
||||
- Bound the entire push and retain exact coverage; reuse matching analysis work without stale verdicts.
|
||||
- Measure recall, noise, coverage and latency together across supported languages/frameworks.
|
||||
- Do not build a second ranker/scope heuristic, an incremental CPG engine, a daemon, an automatic fixer,
|
||||
or a mandatory model service. Do not scan intermediate pushed history in this feature.
|
||||
|
||||
## Boundary Commitments
|
||||
|
||||
### This Spec Owns
|
||||
|
||||
- Git push inputs, immutable snapshots, comparison identity, and change context passed to the ranker.
|
||||
- Local artifact reuse, the shared push deadline, delta attribution and hook alert admission.
|
||||
- Concise rendering, explicit finding/coverage/push dispositions, and integration instructions.
|
||||
|
||||
### Out of Boundary
|
||||
|
||||
- Ranking features, weights, tier definitions, and exploration: `flow-aware-ranking` owns these.
|
||||
- Family facts, sanitizers, graph construction semantics and generic driver correctness:
|
||||
`contributor-scan` owns these. Hook integration may extend contracts but may not fork the driver.
|
||||
- Broader telemetry mechanisms: `zero-with-a-reason`; durable dismissals and learning:
|
||||
`finding-feedback-loop`. Initial rollout does not require either to be complete.
|
||||
- Vendor source analysis, new C memory abstractions, dependency CVE inventories, whole-tree assurance.
|
||||
|
||||
### Allowed Dependencies
|
||||
|
||||
Use Git, Python's standard library, `preprocess`, `mapping`, `model/regions`, `model/layout`,
|
||||
`model/scan`, `model/evidence`, `cpg/backend`, existing arbiters, redaction and report records.
|
||||
No new mandatory package or service. Dependency direction is existing engine/types -> push adapters
|
||||
and policy -> push runner -> CLI. The engine never imports the push package; shared deadline and
|
||||
artifact contracts live at the engine boundary. Ranker internals never depend on Git hook objects.
|
||||
|
||||
### Revalidation Triggers
|
||||
|
||||
Changes to engine/frontend/overlay versions, facts/sanitizers, layout, scope rules, ranker mode,
|
||||
query depth, deduplication, finding identity, or alert admission invalidate relevant cached results
|
||||
and require the security/fixed/benign push suite. Ranker changes also require untouched evaluation.
|
||||
Driver contract changes revalidate ordinary `ousast scan`, coverage reports, and repository benchmarks.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Existing Architecture Analysis
|
||||
|
||||
`preprocess_repository` reads the live filesystem and labels it with HEAD; this is not a pushed
|
||||
snapshot. `scan_repository` builds a full-root CPG before selecting regions. `ScanBudget` limits
|
||||
regions/model calls, not total elapsed time. Backend graphs are temporary, with per-command timeouts
|
||||
and cleanup. `order_by_evidence` is implemented but disabled by the ordinary CLI caller.
|
||||
|
||||
Consequently the current ranker does not bound graph-build cost. A faster ordering alone cannot
|
||||
establish a fast pre-push hook, and passing fewer regions must not be described as incremental parsing.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Git ref updates] --> B[Immutable tips and comparison context]
|
||||
B --> C[First-party inputs with vendor exclusions]
|
||||
C --> D[Compatible graph or bounded graph build]
|
||||
B --> E[Changed and affected context]
|
||||
D --> F[Existing ranker selects scope]
|
||||
E --> F
|
||||
F --> G[Existing arbiters and targeted base comparison]
|
||||
G --> H[Change attribution and actionability admission]
|
||||
H --> I[Compact hook result and detailed artifact]
|
||||
D --> J[Coverage and deadline accounting]
|
||||
F --> J
|
||||
G --> J
|
||||
J --> I
|
||||
```
|
||||
|
||||
The deadline surrounds the entire flow. Cache lookup does not bypass identity checks; a miss does
|
||||
not trigger unbounded work. The default path is local and deterministic. An optional LLM may reorder
|
||||
within allowed ranker constraints or explain existing evidence; it cannot mint facts or admit alerts.
|
||||
|
||||
### Language and Framework Contract
|
||||
|
||||
Requirements 8.1, 8.2, 8.3, 8.4 make abstract analysis a transfer contract, not a claim of automatic
|
||||
language coverage. Frontends and versioned facts identify sources, sink argument roles, carried
|
||||
values, dispatch, guards, obligations and configuration properties. The common evidence vector,
|
||||
ranker scope decisions and arbiters consume these representations. Core ranking must not inspect
|
||||
repository names, PHP syntax, WordPress calls or Express API names to assign priority. Equivalent
|
||||
normalized evidence receives the same core tier and score regardless of origin; policy admission
|
||||
also requires the independently evaluated capability for that origin.
|
||||
|
||||
Existing seams are `mapping.py` (route/middleware mapping), `ruleset/semantic/*.toml` and
|
||||
`ruleset/obligations/*.toml` (reviewed language/framework facts), `model/specs.py` (abstract question
|
||||
construction), `model/evidence.py` (shared ranking), and `cpg/queries/` (graph queries). Reuse them;
|
||||
do not add a universal source IR or move framework checks into the hook. Frontend normalization
|
||||
belongs at the backend/query boundary and must document unsupported constructs. Existing
|
||||
language-specific integrations require audit; this design does not claim they already conform.
|
||||
|
||||
For Express, verify route registration and ordered middleware, request input, imported handlers,
|
||||
service calls, callbacks and async boundaries. A function named `authenticate` does not establish
|
||||
that its guard governs an operation. Require the same guard/obligation evidence as in other
|
||||
frameworks. `node_modules` and other declared third-party trees stay outside targets AND graphs;
|
||||
explicit framework/library summaries describe relevant behavior without analyzing vendor source.
|
||||
Do not exclude first-party JavaScript merely because it is browser code: record its partition and
|
||||
capability separately from the server. TypeScript support also needs frontend evidence, not an
|
||||
assumption based on normalizing its language label to JavaScript.
|
||||
|
||||
PMPro is the development workload; Node/Express checks transfer of the frozen core. Framework/fact
|
||||
adaptations are recorded separately from zero-change transfer, and any case used for repairs becomes
|
||||
regression data. C and C++ use the same scheduling/reporting contract but may need different abstract
|
||||
domains: taint transfer, guard dominance and numeric bounds are distinct capability claims. Keep
|
||||
libpng as an envelope/unsupported-bounds control until a suitable domain is implemented and measured.
|
||||
Concrete workload roles and required paired changes are in `evaluation.md`.
|
||||
|
||||
### Technology Stack
|
||||
|
||||
| Layer | Choice | Role |
|
||||
|---|---|---|
|
||||
| Push input | Installed Git, object-format-aware commands | Resolve refs, trees and immutable blobs |
|
||||
| Runtime | Existing Python >=3.11 CLI and standard library | Transaction, cache, deadline, policy |
|
||||
| Analysis | Existing Joern backend; evaluated baseline 4.0.625 | Graph and arbiters, version recorded |
|
||||
| Storage | Local content-addressed directories and JSON manifests | Atomic reusable artifacts, no database |
|
||||
| Presentation | Existing finding records plus compact hook renderer | Separate findings and coverage |
|
||||
|
||||
Align Docker, Compose and provisioning with the evaluated Joern version before any deployment
|
||||
claim. The old defaults are 4.0.623. Pin alignment is predecessor packaging work, not completed here.
|
||||
|
||||
## File Structure Plan
|
||||
|
||||
New files (proposed, not created by this documentation change):
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/openultrasast/push/snapshot.py` | Parse ref updates, resolve bases, materialize blobs, produce change context |
|
||||
| `src/openultrasast/push/cache.py` | Artifact keys, validity, atomic publication and eviction |
|
||||
| `src/openultrasast/push/policy.py` | Delta identity, actionability admission and push disposition |
|
||||
| `src/openultrasast/push/runner.py` | Shared deadline, ranker/engine orchestration, per-ref result aggregation |
|
||||
| `src/openultrasast/push/report.py` | Compact terminal result and reproducible push manifest |
|
||||
| `benchmarks/push/README.md` and `benchmarks/push/measure.py` | Labeled push recipes and joint quality/latency measurement |
|
||||
| `tests/test_push_snapshot.py`, `tests/test_push_cache.py`, `tests/test_push_policy.py`, `tests/test_push_runner.py` | Contract and integration checks below |
|
||||
|
||||
Modified files: `cli.py` and `config.py` add explicit push command/configuration;
|
||||
`model/regions.py` carries change context without Git-specific logic; `model/scan.py` consumes the
|
||||
ranker's selected questions and returns exact selected/deferred IDs, deadline and reusable-graph
|
||||
contracts; `cpg/backend.py` owns graph artifact validation and process cancellation. Existing
|
||||
`model/evidence.py` changes only through the ranking spec. Packaging pin fixes remain in existing
|
||||
Docker/Compose/ops paths. `README.md` documents opt-in integration without overwriting existing hooks.
|
||||
|
||||
Cross-language validation reuses `tests/test_model_evidence.py` for equivalent normalized evidence,
|
||||
`tests/test_model_scan.py` for shared scope/coverage behavior, and existing mapping/fact tests for
|
||||
frontend adaptation. Reviewed workload recipes use `benchmarks/repos/*.toml` and the proposed
|
||||
`benchmarks/push/` harness. Necessary mapping/fact/query fixes stay within the engine/ranking specs;
|
||||
they do not become JavaScript-specific branches in `push/`.
|
||||
|
||||
## Components and Interfaces
|
||||
|
||||
### Snapshot Adapter
|
||||
|
||||
`PushUpdate(local_ref: str, local_oid: str, remote_ref: str, remote_oid: str)` records every input
|
||||
line. `PushComparison(head_oid: str, base_oid: str | None, base_reason: str, refs: tuple[str, ...])`
|
||||
records immutable identity. Object IDs are validated through Git; no hard-coded SHA-1 length.
|
||||
|
||||
For existing refs, compare the supplied old and new tips, including force-pushes. For a new branch,
|
||||
use a configured comparison ref resolved to an OID and its unique merge base with the pushed tip;
|
||||
record that this is a chosen baseline, not the previous remote branch. No configured/unique/local
|
||||
base means comparison incomplete. Do not substitute HEAD or a remote-tracking ref silently. No
|
||||
automatic fetch inside the hook. Peel annotated tags to commits; other objects are unsupported.
|
||||
Deletion updates require no target scan. Deduplicate identical target graphs, but retain separate
|
||||
comparisons when the same target has different bases. Multiple refs share one deadline.
|
||||
|
||||
Materialize tracked blobs directly into isolated scratch storage; do not checkout over the user's
|
||||
tree or run filters/build scripts. Use NUL-delimited tree/diff records and argument arrays. Include
|
||||
deletions as impact seeds and retain rename attribution. Symlinks are not followed outside the
|
||||
snapshot; gitlinks, absent LFS content and unsupported objects produce explicit coverage limits.
|
||||
Target-tree content is the analysis population; intermediate history is out of scope.
|
||||
|
||||
### Ranker Scope Contract
|
||||
|
||||
The snapshot adapter provides `ChangeContext`: base/head identities, changed/deleted paths and spans,
|
||||
manifest/config changes, and known affected first-party relationships. It supplies evidence, not
|
||||
an alternative inclusion policy. The existing ranker produces `ScopeDecision`: selected question
|
||||
IDs, their priority/evidence, deferred question IDs with reasons, and unresolved context boundaries.
|
||||
Identifiers include unit/language/path/function/family to avoid collisions across partitions.
|
||||
|
||||
Third-party vendor exclusions apply before both preprocessing targets and graph construction.
|
||||
Tests remain available but unshipped, consistent with the approved layout decision. The ranker
|
||||
cannot reintroduce vendor sources. Known external summaries are versioned facts; without a relevant
|
||||
summary, a claim depending on excluded internals is unresolved. Do not assume a missing external
|
||||
call is safe. Entry/callee, field, hook and removed-guard context must be represented in the ranked
|
||||
question; an unchanged sink may be affected by changed reachability or sanitization.
|
||||
|
||||
Graph construction and question scope are distinct costs. Initial graph input is the first-party
|
||||
repository per supported frontend, or an explicitly declared analysis unit with recorded boundaries.
|
||||
Do not create an additional heuristic source-slicer. A mixed-language repository has explicit
|
||||
frontend partitions and coverage per partition; unsupported cross-language edges remain unresolved.
|
||||
Use old graph relationships only as prioritization hints after a code change, never as current proof.
|
||||
If a ranked question's necessary first-party context is absent, expand its declared unit within the
|
||||
remaining budget or report it unresolved. No claim of exact tier-0 pruning extends beyond the
|
||||
modeled graph/facts/scope; unanswered evidence is never tier 0.
|
||||
|
||||
### Cache and Engine Boundary
|
||||
|
||||
Graph key: digest of every included blob/path and applicable declarations, frontend/engine version,
|
||||
overlay semantics/options, language, and exclusions. Query key additionally includes graph key,
|
||||
question/context/depth, facts/sanitizers, query code and relevant analysis configuration. Result key
|
||||
also includes ranking/admission versions and any model/prompt identity. Comparison results include
|
||||
both base and head identities. Persist manifests with source byte counts and graph census.
|
||||
|
||||
Only validated, complete artifacts can satisfy complete-result lookups. Partial artifacts may be
|
||||
retained for diagnostics but never promoted by cache presence. Publish atomically from private
|
||||
temporary paths under a per-key lock; another push may wait only within its deadline. Apply a
|
||||
configured cache size limit and evict unleased entries. No live graph mutation or dependence on
|
||||
CPG node IDs remaining stable across builds. A changed input invalidates that graph; incremental
|
||||
Joern mutation is neither assumed nor promised. Unchanged declared units and identical snapshots
|
||||
can reuse graphs; changed units rebuild. Measure whether this is enough before claiming scale.
|
||||
|
||||
The maintainer confirmed interest in persistence followed by differential updates on 2026-09-12.
|
||||
Persistence is the first optimization; differential graph mutation remains a separate future
|
||||
experiment. Its acceptance gate must compare against a cold rebuild on identical inputs and
|
||||
semantics, including changed/deleted/renamed declarations, affected call edges and dataflow
|
||||
overlays, and invalidation of dependent query/results artifacts. Updating only changed file nodes
|
||||
is insufficient. No graph partition may claim complete cross-partition analysis without validated
|
||||
relationship handling. This sequencing does not expand the approved implementation into a new
|
||||
incremental engine.
|
||||
|
||||
The backend owns `GraphArtifact` validation/lifecycle. The generic driver accepts an optional
|
||||
validated graph lease and an absolute monotonic deadline, preserving its ordinary fresh-build path.
|
||||
Queries operate on an isolated lease if loading can modify a graph. Every subprocess timeout is
|
||||
bounded by remaining transaction time, including fallback frontend, overlay, shard and retry work.
|
||||
Cancel process groups, close leases, then persist coverage within the cancellation allowance.
|
||||
|
||||
### Delta and Actionability Policy
|
||||
|
||||
Admission requires all of: an enabled/evaluated capability, `model_entailed` or a future actually
|
||||
produced `execution_confirmed` result, a locatable witness, applicable family semantics, no unresolved
|
||||
dependency that undermines the claim, supported change attribution, and a concrete repair direction.
|
||||
No production caller currently reaches `execution_confirmed`; it is not a prerequisite or a promise.
|
||||
|
||||
Findings are keyed by family/mechanism and stable source/operation identity, with rename/span mapping;
|
||||
line numbers alone do not establish novelty. Re-query relevant base counterparts under the same
|
||||
semantics and sufficient comparable context. A missing old finding from a truncated/failed scan is
|
||||
unknown, not proof that the head introduced a bug. A removed guard or newly connected source can
|
||||
establish a worsened flow at an unchanged sink. Unknown novelty stays in the diagnostic artifact
|
||||
with one aggregate comparison-coverage notice. Initial checkout is not automatic acceptance of debt.
|
||||
|
||||
Deduplicate by defect, retaining distinct witnesses and ref associations. Explain input/operation,
|
||||
security consequence, why this change matters and a specific repair locus; do not require automatic
|
||||
patch generation. Templates grounded in facts/witnesses are sufficient. Admission rejects generic
|
||||
"sanitize input" advice without a supported context/operation. An LLM explanation must cite the
|
||||
same witness and cannot raise the rung or invent exploitability.
|
||||
|
||||
### Result and Hook Adapter
|
||||
|
||||
`PushResult` separates `finding_status` (`actionable` / `none`), `coverage_status` (`complete_within_scope`
|
||||
/ `incomplete` / `unavailable` / `not_applicable`) and `push_disposition` (`allow` / `block`). Zero
|
||||
selected questions cannot mean complete when applicable questions were deferred or context is unknown.
|
||||
The detailed manifest retains per-ref/per-capability coverage, supported and unsupported populations,
|
||||
all admitted defects, rejected-candidate reasons, exact scope and timings.
|
||||
|
||||
Initial profile default: advisory, with the same admission policy as blocking. Blocking is opt-in and
|
||||
returns nonzero only for admitted new/worsened findings; incomplete coverage defaults to allow plus
|
||||
a notice, with a separately configurable strict coverage policy. Show up to three concise defect
|
||||
summaries plus the remaining count/artifact path; the cap never changes the exit decision. Do not
|
||||
render the full SAST backlog. No actionable findings: at most one bounded status line. Incomplete:
|
||||
one notice naming the primary reason and a concrete retry/diagnostic action. Unchanged results may
|
||||
be reused, but a still-relevant admitted defect is not dismissed merely because it was printed before.
|
||||
|
||||
Expose a proposed `ousast pre-push` command receiving Git's stdin and remote arguments, plus explicit
|
||||
base/head replay. Hook installation is explicit: preserve `core.hooksPath`, existing hook exit
|
||||
semantics, and stdin for other consumers. Provide an integration snippet and refuse automatic
|
||||
overwrite of an existing hook. Analysis runs no project code and performs no source modification.
|
||||
|
||||
## Requirements Traceability
|
||||
|
||||
| Requirements | Design elements |
|
||||
|---|---|
|
||||
| 1.1, 1.2, 1.3, 1.4 | Snapshot Adapter: immutable objects, per-ref comparisons, special update cases |
|
||||
| 2.1, 2.2, 2.3, 2.4 | Ranker Scope Contract: change context, vendor exclusion, boundary/guard handling |
|
||||
| 3.1, 3.2, 3.3, 3.4, 3.5 | Delta and Actionability Policy: admission, witnesses, comparison and deduplication |
|
||||
| 4.1, 4.2, 4.3, 4.4 | Cache and Engine Boundary: shared deadline, complete artifact identity and leases |
|
||||
| 5.1, 5.2, 5.3, 5.4, 5.5 | Result and Hook Adapter: separate statuses, compact rendering, explicit policies |
|
||||
| 6.1, 6.2, 6.3, 6.4, 6.5 | Testing Strategy and Rollout: capability admission and joint gates |
|
||||
| 7.1, 7.2, 7.3, 7.4 | Local runtime, model redaction/budget, manifests, non-destructive integration |
|
||||
| 8.1, 8.2, 8.3, 8.4 | Language and Framework Contract; frozen-core transfer and workload matrix in evaluation.md |
|
||||
|
||||
## Error Handling
|
||||
|
||||
Unreadable input or failed census invalidates the affected graph. Missing base invalidates novelty
|
||||
comparison. Missing context invalidates affected checks. Deadline exhaustion preserves completed
|
||||
findings and records deferred work. These states remain visible even when the hook permits the push.
|
||||
If result persistence fails, print one artifact-write notice and retain the real finding/coverage
|
||||
decision in the terminal; do not fabricate a successful cache entry or hang while retrying.
|
||||
|
||||
## Testing Strategy and Rollout
|
||||
|
||||
1. Snapshot contracts (1.1–1.4, 7.1, 7.4): dirty trees, non-HEAD pushes, multiple refs with different
|
||||
bases, force-push/new-branch/missing-object cases, tags, deletion-only pushes, unusual paths,
|
||||
symlinks/gitlinks, and coexistence with an existing hook.
|
||||
2. Ranker/context tests (2.1–2.4): handler-to-unchanged-sink, field/hook flows, removed guards,
|
||||
first-party cross-file context, vendor source absent from both graph and targets, unresolved
|
||||
vendor boundary, polyglot partition coverage. No second selection heuristic in the push adapter.
|
||||
3. Admission tests (3.1–3.5, 5.1–5.5): high rung with wrong/unsupported semantics is rejected;
|
||||
unchanged backlog and missing-base claims stay out; new supported defects include actionable
|
||||
witnesses; one defect reached by many regions prints once; advisory and blocking share admission.
|
||||
4. Deadline/cache tests (4.1–4.4, 7.3): source/context/fact/query/version invalidation, corrupt/partial
|
||||
entries, concurrent pushes, timeout during build/overlay/query/retry, process-group termination,
|
||||
no repeated per-ref budget reset, exact selected/deferred IDs rather than slicing old order.
|
||||
5. Real push evaluation (6.1–6.5, 8.1–8.4): use PMPro for core development, then freeze the core
|
||||
and replay labeled introducing/fixing/benign changes on a JavaScript/Express Node API workload.
|
||||
Preserve Python regressions and a separate untouched Node API repository; training applications
|
||||
and cases used to fix mapping are not fresh holdouts. Include injection, path and authorization/
|
||||
configuration where supported, plus middleware and cross-module/async controls. Test equivalent
|
||||
normalized evidence for identical core decisions. Keep libpng's envelope and unsupported bounds
|
||||
separate from a future C/C++ positive control. See `evaluation.md` for selection and labeling gates.
|
||||
|
||||
Measure the accepted experimental acceptance profile in requirements on declared hardware and fixed workloads.
|
||||
Warm means prior reusable work from the preceding state, not only repeating the identical tip.
|
||||
Report identical-tip hits separately from a one-function change and a dependency/configuration
|
||||
change. Include first installs, cold graphs, growing repository size, multi-ref pushes and incomplete
|
||||
outcomes. Freeze the applicable population before ranker selection; deferred questions count against
|
||||
completion. Report p50/p95 plus timeout counts, observed precision with confidence intervals, recall,
|
||||
benign interruption rate and fixed-side silence. A low-noise score with low recall/coverage fails.
|
||||
|
||||
Rollout order: reconcile engine pins and benchmark honesty; prove one end-to-end change/witness/fix
|
||||
with the existing ranker; establish deadline/cache feasibility; run untouched and benign evaluation;
|
||||
admit passing capabilities in advisory mode; consider opt-in blocking only after these gates pass.
|
||||
LLM ranking is a later experiment, not a prerequisite. If realistic changed-unit rebuilds cannot
|
||||
meet the deadline/coverage gates, keep the hook experimental and revisit the graph strategy with
|
||||
measurements; do not solve the miss by silently shrinking context or lowering alert standards.
|
||||
Reference in New Issue
Block a user