mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
Requirements approved by the maintainer in session. Design: closed obligation facts (operations and dischargers), obligation shapes as a second family in the mechanism store, sibling-set consistency and an optional declared policy as the policy sources, a Dominance protocol with an order-based default so the checker works function-local before reachability and guard dominance land, findings at suspicion carrying consistency_violation / declared_policy_violation / function_local as data, reports, manifest and ranking fields, handler_context harvest mode, leave-one-out per obligation kind, and an obligations lever with pending policy clauses under the existing gate. All 44 acceptance criteria traced. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
10 KiB
10 KiB
Research & Design Decisions
Summary
- Feature:
authorization-obligations - Discovery Scope: Extension of the semantic layer (facts, shapes, store, MAP proposers), the entry-point mapper, reports, the pair corpus, leave-one-out and the improve lever; complex integration because it consumes two unimplemented upstream specs.
- Key Findings:
- The absence pairs are real and structured.
vampi-books-get-by-titlehas identical code on both sides except a constraintuser=userbound fromtoken_validator(request.headers.get('Authorization'))['sub']; the vulnerable branch queriesBookby title alone. The difference is the provenance of a constraint, not a flow. 15 of 29agent-vfcrows and 6 of the 19 non-teachingvibe-pypairs carry absence labels (missing_auth_guard,permissive_default,identity_from_request_body,secret_in_client,validation_strength). - The entry-point mapper already classifies every route as
public,authenticatedorrole-restrictedfrom its decorators (mapping._python_route_access), records the decorators asaccess_evidence, and listsconditions(iftests that look like checks). That is the sibling detector's upstream for path guards and needs no change. FileIRbinds carryvalue_text,namesandcall_name;variants.source_kind_of_namealready traces a name through binds to a fact source, a parameter or a container read. Provenance of a constraint's value (authenticated context versus request input versus constant) is the same trace with one more source class.MechanismStorerows carry a freeshapedict and deterministic ids fromShape.key(); a second shape family fits without schema change.MechanismEdit,apply_mechanism_edits,StoreSnapshot, the tombstone andevaluate_mechanism_profilesare reusable as-is for a second lever family.reachability-flow-model(unapproved) definesPathRecord(entry, hops, sink, sink_line, sink_node, incomplete_at, ...)andcompose(graph, summaries, entry_nodes, max_depth). Obligations need paths from an entry point to an operation, which is a sink-shaped target; the samecomposeworks when obligated operations are passed as the sink set.guard-dominance-regimehas no spec directory yet; only the roadmap line exists. Dominance must therefore be consumed through a narrow interface this design defines and the two upstream specs later satisfy.- The evidence ladder is closed:
OverlayRecordallowsstatic_corroborationandsuspicion; proof rungs are forbidden outside the sandbox. Obligation findings areStaticFindings atsuspicionwith the obligation label carried as data, so no ladder change is needed and Req 6.1 holds by construction. - The pair scorer counts promote and coverage-with-source records as detections; an obligation finding has no source or sink. Detection for obligation-labeled rows must be an additive rule keyed on the obligation label (allowed by the corpus spec's "one additive label field").
- The absence pairs are real and structured.
Research Log
What the absence pairs actually differ by
- Context: Decide whether absence bugs can be a shape family at all.
- Sources Consulted:
benchmarks/pairs/vibe-pyexcerpts formissing_auth_guardandpermissive_defaultrows;benchmarks/pairs/agent-vfc/recipes.tomlmechanisms;mechanisms.tomldefinitions. - Findings: three recurring differences: (a) a constraint added to a data access whose value comes from the authenticated context; (b) a guard call or decorator added before the operation (early return on a failed token check,
login_required); (c) a literal changed from permissive to restrictive (CORS(app)→ origins list,DEBUG=True→False, RLS enabled). A fourth, rarer: identity read from the body replaced by identity from the token (same as (a) seen from the other side). - Implications: discharger kinds
identity_constraint,path_guard,ownership_check,non_permissive_value,validated_input; the value provenance is the discriminating feature for (a) and (d).
Where the policy comes from
- Context: Absence is relative to intent; the tool must not invent intent.
- Sources Consulted:
mapping.analyze_entry_points(routes, access level, evidence), Real-Vuln repositories (many handlers per resource), Clearwing's sibling-comparison practice. - Findings: in every Real-Vuln web app the handlers on one resource share a router and a model; the vulnerable handler is the odd one out. Route access classification already exists. A declared policy is the only way to say "this route is meant to be public"; the hunter can only guess it.
- Implications: consistency first (no declaration needed, measurable now), declared policy second (stronger rung), hunter last (records intent, never raises).
Dominance before guard-dominance-regime exists
- Context: Req 5 needs "a discharger dominates the path"; the spec that owns dominance is not written.
- Findings: within one handler, the existing
FunctionIRorder of binds and calls gives statement order; a guard call followed by an early return before the operation line is dominance in the common case; decorators dominate the whole handler; middleware attached to the router dominates every route under it. Across calls,PathRecord.hopsgives the sequence of nodes; a discharger on any hop before the operation node dominates the path when the hop's function returns early on failure. - Implications: define
Dominanceas a narrow protocol (dominates(witness, operation) -> bool) with a built-inOrderDominanceimplementation (statement order in the handler, decorators, router middleware, hop order) thatguard-dominance-regimemay replace with nesting-aware dominance. The degraded mode (function_local) isOrderDominancewithout path records.
Store and lever reuse
- Context: Avoid a second store and a second lever machinery.
- Findings:
Mechanism.shapeis a dict validated where used;corpus_mechanism_idhashesshape.key();validate_mechanismchecks guard and source kinds againstvariantsconstants. A second family needs its own closed kinds check and afamilyfield in the shape dict; the tombstone, snapshot and profile clause need nothing. - Implications:
ObligationShape.to_dict()carriesfamily = "obligation"; the lever validator dispatches onfamily;search_treeignores obligation rows (they are not sink shapes) and the obligation checker ignores sink rows.
Architecture Pattern Evaluation
| Option | Description | Strengths | Risks / Limitations | Notes |
|---|---|---|---|---|
| Absence as a taint sink ("sink with a missing sanitizer") | Treat protected access as a sink needing an identity "sanitizer" | Reuses taint | Wrong semantics: the constraint is not a sanitizer of a flow; unconstrained access has no tainted argument | Rejected |
| Model-authored authorization rules | LLM writes per-project rules | Flexible | Violates no-model-authored-patterns; unverifiable | Rejected |
| Obligations with dischargers, policy from consistency plus declaration, path-aware | Closed facts, learned shapes, sibling anomaly, optional declared policy, dominance protocol | Zero-dep, measurable leave-one-out, honest rungs, grows with corpus | Coarse resource grouping; dominance approximated until the guard spec lands | Selected |
| Framework-specific authorization linters | Per-framework hard-coded checks | High precision | Regex growth, no learning, no policy | Rejected |
Design Decisions
Decision: Obligation findings are suspicion with a data label, not new ladder rungs
- Context: Req 6.1 names three labels; the ladder is closed and owned elsewhere.
- Selected Approach:
StaticFinding.evidence_level = "suspicion", tagsobligation:<kind>,discharger:<kind>,obligation_evidence:<label>; reports render the label. Dispositions andALLOWED_EVIDENCEunchanged. - Trade-offs: SARIF consumers see
suspicionplus properties; honest and non-invasive.
Decision: Dominance is a protocol with an order-based default
- Context:
guard-dominance-regimeis unwritten; this spec cannot wait for it to be useful. - Selected Approach:
Dominanceprotocol;OrderDominancedefault (decorator, router middleware, statement order before the operation, hop order along a path). The guard spec replaces it without touching callers. - Trade-offs: order-based dominance misses guards inside conditionals; recorded as the known coarseness and measured on the fixed twins.
Decision: Policy is never written by the tool
- Selected Approach:
declared policyis a hand-maintained file with a closed schema; the lever may only propose a clause, journaled as pending until a human accepts; scans never read pending clauses.
Decision: Resource identity for sibling grouping
- Selected Approach: a handler's resource is the trailing segment of its route path with parameters stripped, joined with the operation's target token (model, table, collection name from the operation fact match); router or module is the second key. Coarse but deterministic and text-free.
Risks & Mitigations
- Sibling grouping over-merges unrelated handlers → minimum sibling count, both keys required, siblings listed in the finding for review.
- Consistency leaks on legitimately public handlers → declared public routes suppress path-guard violations; leak rate on fixed twins reported per slice; the lever's holdout gate rejects shapes that leak.
- Order dominance misses conditional guards →
function_localandconsistency_violationlabels stay below proof; guard spec replaces the default. - Corpus excerpts lack registration context → new
handler_contextharvest mode; pointer pairs fetch whole files anyway. - Two unimplemented upstream specs → this design degrades to function-local mode without path records, so tasks 1–6 are implementable before reachability lands; only path-aware evaluation waits.
References
.kiro/specs/reachability-flow-model/design.md(PathRecord, compose).kiro/specs/corpus-seeded-mechanisms/design.md(store, lever, LOO)src/openultrasast/mapping.py(EntryPointRecord,_python_route_access)src/openultrasast/semantic/variants.py(source_kind_of_name,classify_guard)