From 214d43cad6242f5bcee20e053ab403de488b6a87 Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Sun, 6 Sep 2026 07:32:07 +0200 Subject: [PATCH] docs(authorization-obligations): record task 2.6 and the measured blockers before 4.3 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG --- .kiro/specs/authorization-obligations/design.md | 4 ++++ .kiro/specs/authorization-obligations/tasks.md | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/.kiro/specs/authorization-obligations/design.md b/.kiro/specs/authorization-obligations/design.md index 492e6ac..c660622 100644 --- a/.kiro/specs/authorization-obligations/design.md +++ b/.kiro/specs/authorization-obligations/design.md @@ -363,6 +363,10 @@ def findings_to_static(result) -> list[StaticFinding]: ... # id "obligation: consistency > function_local; a declared public route suppresses `path_guard` obligations for that route; an operation with a matching store shape gains `known_fix`; a discharge that binds `request_input` where `authenticated_context` is required is reported with `provenance = "request_input"`. +### EntryPoints (registration-based) + +`mapping.analyze_entry_points` names a handler from a decorator today. Real projects register handlers by call (`add_url_rule(path, view_func=fn)`, `api.add_resource(Cls, path)`, `router.get(path, requireAuth, handler)`) or by convention (`Resource` verb methods, Next.js `route.ts` exports). Because both the sibling grouping (Req 3.1) and the function-local reach test (Req 5.3) key on a named entry-point handler, an unnamed registration makes every obligation unreachable. The mapper gains those four forms; access classification, evidence, conditions and the record shape are unchanged, and middleware identifiers on a registration line are access evidence exactly as decorators are. + ### IntentAdjudication When a hunter client is available (the same resolution as `pairs --hunter`), the checker asks one closed question per finding with a route: "Is this route meant to be public?" with the handler text and siblings as context; the answer is recorded as `intent` and in the rationale. It never changes the label or the evidence level; when the client is absent a degradation `intent_adjudication_unavailable` is recorded once. diff --git a/.kiro/specs/authorization-obligations/tasks.md b/.kiro/specs/authorization-obligations/tasks.md index 753c1db..5a3af5c 100644 --- a/.kiro/specs/authorization-obligations/tasks.md +++ b/.kiro/specs/authorization-obligations/tasks.md @@ -53,6 +53,13 @@ - _Requirements: 3.6, 4.3, 4.4, 5.1, 5.2, 5.3, 5.4, 6.1_ - _Depends: 2.2, 2.3, 2.4_ +- [ ] 2.6 Recognize registration-based entry points (P) + - The entry-point mapper names a handler when its route is registered by a call or by convention, not only by a decorator on it: Python `add_url_rule(path, view_func=fn)`, `api.add_resource(Cls, path)` and HTTP-verb methods of a `Resource`/`MethodView`/`APIView`/`ViewSet` subclass; JavaScript and TypeScript `router.(path, ...middleware, handler)` naming the handler identifier with the middleware identifiers as access evidence, and exported `GET`/`POST`/`PUT`/`PATCH`/`DELETE` functions in a `route.ts`/`route.js` file (Next.js App Router). Access classification, evidence and conditions follow the existing rules. + - Observable: on the vendored `vampi-books-get-by-title`, `threatbyte-api-v1-get` and `supplieradvisor-mvp-route-a69e8f` excerpts the mapper names the labeled function as a route entry (today it names none, so the obligation checker reports nothing on any real absence pair); a `requireAuth` middleware argument classifies the route `authenticated`; existing decorator routes, the map gate and the map artifact are unchanged. + - _Requirements: 3.1, 5.3_ + - _Boundary: mapping_ + - _Depends: 2.5_ + - [ ] 3. Integration: scan, reports, ranking, intent - [ ] 3.1 Run the checker in MAP with config and manifest counters - `[obligations] enabled=true, min_siblings=3, policy_path`; MAP runs `_check_obligations` after variant search in standard and deep only, with `PathRecord`s when the reachability artifact is present and `OrderDominance` otherwise; findings appended; manifest `obligations = {sibling_sets, under_populated, operations, findings_by_label, policy_version, degradations}`; obligation findings never enter the sandbox candidate set. @@ -134,5 +141,12 @@ - Task 4.1 (2026-09-05, RED first: `test_handler_context_mode_keeps_decorators_and_registration` (`KeyError: extract_handler_context`), `test_obligation_labeled_row_loads_and_is_detected_by_an_obligation_finding` (vocabulary lacked the three ids), `test_catalog_rejects_an_obligation_kind_outside_the_closed_set` (no rejection); then green): `harvest.py` mode `handler_context` = `extract_handler_context(source, function, language)`: the function with its decorators (via `extract_function`) followed by every statement outside it that names the function as an identifier in the masked text (comments and strings never count; `def`/`function`/`sub` declarations excluded), one line per registration statement (a multi-line `urlpatterns = [...]` contributes only the line naming the handler; recorded limitation); `validate_recipe` requires `function` for the mode; `extract_pair(_with_lines)` dispatch; README lists the mode. `mechanisms.toml` version 2 adds `unconstrained_protected_read`, `unconstrained_protected_write`, `unguarded_privileged_action` with definitions naming the operation kind. `ExpectedFinding.obligation: str | None = None` (additive, default keeps every manifest and catalog loading unchanged); `pairs._parse_expected` accepts `obligation` only from the closed `OPERATION_KINDS` and rejects others by name. Scorer: `_overlay_scan(..., obligations=)` computes obligation statics through `_obligation_scan` (function-local `check_obligations`, no policy, no store, `ObligationsConfig().min_siblings`) only when a row carries the label, so rows without it run byte-identical code; the one additive rule `_obligation_matches_expected` counts a finding tagged `obligation:` inside the labeled function; obligation findings on the fixed side are leaks under `fix_policy = silent`; `detection_kinds` gains `obligation`; the inventory fallback is not taken when obligation statics exist. Both matrices green (572 passed / 4 skipped), ruff and mypy clean, gate stdout byte-identical to HEAD (no gate row carries the label). - Review round 3 (2026-09-05) APPROVED 2.2; REJECTED 2.5, 3.1, 3.2, 3.3. Remediated RED-first (6 tests failed for the stated reasons, then green): (2.5) the declared-public waiver now covers the operation's own requirement, not only sibling anomalies (Req 4.4): the checker drops `path_guard` from `requires` when the route is declared public and falls through to the next accepted kind, pinned by `test_a_declared_public_route_never_reports_its_missing_path_guard_but_keeps_the_resource_obligation` on a `privileged_action` handler; the tautological static-id assertion is now exact for two findings; a malformed store row is an `obligations_store_row_invalid` degradation naming the record ids instead of a silent skip (design Error Handling). (3.1) the manifest block carries `degradations` and `sets` (`{module, resource, handlers}`) as the design requires; the scan test asserts the block's `obligations_function_local` entry names the language and file. (3.2) the `## Obligations` section prints `Sibling sets evaluated: N (under-populated: M)`, one line per sibling set and the policy version, fed by a new `obligations_summary=` parameter carrying the manifest payload; the proven-finding cap is keyed on verdict ids (`rank_obligations(findings, proven_ids=)`) because proof rungs live on `CandidateVerdict` and never on `StaticFinding`, and the scan re-ranks after REGRESS when the sandbox proved anything. (3.3) the intent prompt is passed through `redaction.redact_secrets` before it reaches the client (design Security Considerations); a test pins that the `leaky` prompt carries its own decorator and body and none of the other three handlers, and that a runtime-assembled key literal in the handler reaches the model redacted. Design amended: the static id contract carries the `:` suffix, the manifest lists `sets`, and the rank cap states the verdict-id keying. Both matrices green (578 passed / 4 skipped; extra-free 563 / 19), ruff and mypy clean, gate stdout byte-identical to HEAD. - Task 4.2 (2026-09-05, RED first: 2 failing tests in `tests/test_loo.py` — `LooOutcome` had no `obligations`, obligation rows produced no hits; then green): `score_pair_with_store` runs `check_obligations` on both materialized sides for rows carrying an obligation label, with the held-out store's obligation shapes as `store_shapes`; a row is detected only when a finding inside the labeled function carries both the labeled `obligation:` tag and a `mechanism:` tag naming a store record, so the checker's own facts-only finding is never counted as the corpus teaching; obligation findings on the fixed side are leaks; `LooOutcome.obligations` and `LooResult.per_obligation_kind` join the existing groupings in the payload. A three-pair toy slice measures 2/2 on `protected_read` (each pair found by the other) and 0/1 on `security_setting` (nobody else teaches the kind). +- Blocking measurement before 4.3 (2026-09-05): the checker reports nothing on any real vendored absence pair. `analyze_entry_points` on the `vampi-books-get-by-title`, `threatbyte-api-v1-get/delete`, `vampi-users-update-password` and `dvpwa-app-init` excerpts returns only the two file-level tag records (`auth boundary file tag`, `network entry file tag`) with `function_name = None`, because `_python_entry_points` recognizes a route only from a decorator on the function and these projects register routes with `add_url_rule` or through `flask_restx.Resource` methods. Function-local mode gates on "the operation's function is an entry point", so no operation is ever reached. The research log's claim that the mapper "needs no change" holds for decorator frameworks only. Task 2.6 added to repair it; 4.3 waits on it, because labeling a corpus for a detector that cannot fire would measure the harness, not the detector. +- Task 2.6 (2026-09-05, RED first: `test_entry_points_name_handlers_registered_by_call_or_convention` failed with an empty route set; then green): `mapping._python_registered_routes` names handlers registered by `add_url_rule(view_func=)`, `add_resource`/`register_blueprint`/`include_router`/`add_view` and the HTTP-verb methods of a `Resource`/`MethodView`/`APIView`/`ViewSet` subclass; `_js_registration` names the handler identifier on a `router.(path, ...middleware, handler)` line and classifies the route from the middleware identifiers; `_js_route_module` names the exported HTTP-verb functions of a `route.` module. Record shape, access rules and the map gate are unchanged. +- Measurement after 2.6 (2026-09-05, the honest picture before 4.3). Operations are found on the Python absence pairs (`vampi-books-get-by-title` sees three `protected_read` sites, `threatbyte-api-v1-delete` three), so the facts work; what is missing is the context around them: + - **No registration in the excerpt.** Every vendored Python absence excerpt is the function alone, so even the repaired mapper names no entry point and the checker reports nothing. Appending the real `app.add_url_rule('/books/v1/', view_func=get_by_title)` line to the vampi excerpt makes the whole chain fire end to end (`obligation:protected_read:...:identity_constraint`, label `function_local`). This is exactly what task 4.3's `handler_context` re-harvest supplies, and it is the binding constraint for the Python rows. + - **TypeScript has no grammar.** `parse_file` returns `language_unsupported` for TypeScript in this environment (`tree_sitter_typescript` is not installed), so every TypeScript absence row (supplieradvisor, capgo, zoonk, new-erp, and 18 agent-vfc rows) is unscorable for obligations regardless of labels. Carry-forward of `overlay-ir-completeness`, not of this spec. + - **Five vibe-py twins are byte-identical.** `dsvw-dsvw-do-get`, `python-insecure-app-main-try-hack-me`, `pythonssti-main-read-root`, `vampi-books-get-by-title` and `vulnpy-deserialization-do-pickle-load` have identical vulnerable and fixed excerpts once the provenance header is stripped. No detector can be both loud on the vulnerable side and silent on the fixed one, so these five are permanently counted as failures and silently cap the measured ceiling at 30/35. The other slices have none. This is a corpus defect and belongs to the pair corpus, not to this spec. + - **The unit fixtures are easier than the corpus.** The fixture binds identity in one hop (`user_id = request.user.id`); the real vampi fix binds it in two (`resp = token_validator(...)` then `user = User.query.filter_by(username=resp['sub']).first()` then `filter_by(user=user)`). Green unit tests therefore do not evidence corpus behaviour, which is why 4.2 and 4.3 measure and 6.1 reports. - Path-aware evaluation (5.1) is wired through the `Dominance` protocol and an optional `PathRecord` input; until `reachability-flow-model` lands, every run is function-local by construction and tests use a stub `PathRecord`. No task here waits on the upstream specs. - Sibling grouping keys on (router or module, resource token). If the maintainer prefers data-model-only grouping, only task 2.2 and its tests change.