fix(cron): scan Python script bodies with source detectors, not shell heuristic - #8555
fix(cron): scan Python script bodies with source detectors, not shell heuristic#8555bolichen97 wants to merge 3 commits into
Conversation
Issue #7912: _vet_script_contents scanned a cron script BODY (Python source) by running is_sensitive_bash_command over the raw whole file, which falsely denied benign source bodies two ways: pass 1b's separator-run collapse turned a source backslash ESCAPE into a manufactured credential path, and the fail-closed stage/substitution budgets (_ALT_MAX_STAGES, _FIND_SUBSTITUTION_BUDGET) tripped on file SIZE because every source line was staged as a pipeline stage. Scope the shell-grammar scan to bodies that do NOT parse as Python (ast.parse SyntaxError, or any other unexpected parse error -> fail CLOSED to the raw-text scan). A parseable Python body is left to the source-appropriate detectors already run (_CRON_CRED_PATH_RE, _CRON_SECRET_ENV_RE, _CRON_SECRET_NAME_RE, scan_exfiltration_urls), which independently catch every credential-read / secret-env / exfil case. The command-line surface (is_sensitive_bash_command over a real shell command) keeps the collapse and budgets byte-for-byte unchanged. Tests: add the four issue reproduction bodies to BENIGN_SCRIPTS, a non-parsing fallback test, and a command-surface regression test. Document the scan in learn-cron-dashboard.md.
…dies The v1 semantic review flagged a coverage regression: scoping the shell-grammar scan to non-parsing bodies dropped the one spelling only that scan knew -- %LOCALAPPDATA%\kiro-cli\data.sqlite3 / %APPDATA%\amazon-q\... -- because the source-side _CRON_CRED_PATH_RE only anchors POSIX paths. Add _CRON_WIN_IDENTITY_STORE_RE (derived from the canonical identity_stores table, mirroring security.py's fence) to the source-detector layer in _vet_script_contents. It matches an ACCESS of a file UNDER a store, not a bare mention of the store directory, so the original #7912 false positive (a benign redaction regex naming the store dir) stays allowed. Does not re-enable the shell scan over parseable source; the command-line surface is untouched. Tests: pin the Windows access spelling blocks in valid Python while the mention stays allowed, plus the except-Exception (non-SyntaxError) parse-failure branch falls closed to the raw-text fallback.
…gex behavior Address v2 review of _CRON_WIN_IDENTITY_STORE_RE: ISSUE 1: the detector separated only on backslash runs, so the forward-slash and mixed-separator spellings of the same Windows identity-store read (%LOCALAPPDATA%/kiro-cli/data.sqlite3 and %LOCALAPPDATA%\kiro-cli/...) that the base-HEAD shell scan blocked slipped through a parseable body. Change both separator runs to [\\/]+ so every separator form is caught again. A forward-slash bare store-dir mention stays allowed. ISSUE 2: the ACCESS-vs-mention rule is syntactic, so a redaction regex that anchors under the store re-trips it. A static source pattern cannot distinguish that from a real read without giving up the coverage that closed the v1 gap, and the task's primary invariant (the plain store-dir mention stays allowed) is preserved. Chose to keep blocking the access-shaped spelling; documented the limitation and the POSIX anchor workaround in code and docs, and pinned it with a test. Update learn-cron-dashboard.md so the coverage claim is accurate.
Superseded by #7913 + #8550 — recommend closing without mergingI rebased this branch onto current
1. The mechanism opens five credential holes that both this PR's own base and current main closeAll five bodies are valid Python, so this PR's
Rows 3 and 4 are the keystone: 2. Applying this PR's scoping onto current main fails 50 of main's own testsSimulated the conflict resolution in this PR's favour ( Every one is a fence-bypass regression guard from #7913/#8550. Getting this branch green would mean deleting them, which is not on the table. 3. The new detector is redundant, and in one direction worseRunning this PR's own test file against unmodified It also loses one case: this PR blocks 4. The one genuine residual is a decision main already made explicitlyOf the three reproductions in #7912, main allows 1 and 2 and still refuses 3 (the prose docstring). That is not an oversight — main pins it:
This PR's 5. The docs commit is superseded too
RecommendationClose as superseded by #7913 + #8550; #7912 is already closed as completed. If the prose-docstring over-block is worth revisiting, it belongs in a fresh issue against No push. Branch left at |
|
Closing as superseded by #8550, which merged as This branch also could not be validated as it stood: it is |
Fixes #7912.
Problem
mcp_cron._vet_script_contentsscanned a cron script body (Python source) by runningsecurity.is_sensitive_bash_commandover the raw whole file. That is a shell-grammar heuristic, and it falsely denied benign Python source two ways:re.compile(r"%LOCALAPPDATA%\\kiro-cli"), a regex literalr"/home/\S*/\.kiro/...", and even a docstring mentioning a store were all denied as though they read a credential path._ALT_MAX_STAGES=512,_FIND_SUBSTITUTION_BUDGET=64) trip on file size, because_alt_collect_stagestreats every source line as a pipeline stage. A few hundred lines of ordinary Python exceed 512 stages with no shell content, so the job is refused on every tick indefinitely (the fire-time gate keeps the job and does not feed the auto-pause counter).Note: PR #7913 (referenced in the issue thread) is not merged into current
main; there is nosubject_is_shell_grammarflag or literal-walk vehicle present, so this fix was built from scratch.Fix
Scope the shell-grammar scan to the surface it is designed for.
_vet_script_contentsnow:is_sensitive_bash_commandonly on a body that does NOT parse as Python (viaast.parse).SyntaxErrorand any other parse error fail closed to the raw-text scan, so a non-Python body is never exonerated._CRON_CRED_PATH_RE,_CRON_SECRET_ENV_RE,_CRON_SECRET_NAME_RE, exfiltration URL scan), which independently catch every existing malicious script._CRON_WIN_IDENTITY_STORE_RE, a table-derived detector (built fromidentity_stores.IDENTITY_STORE_ROOTSWIN32 rows) that restores blocking of the Windows env-var identity-store access spelling (%LOCALAPPDATA%/%APPDATA%,kiro-cli/amazon-q) across backslash, forward-slash and mixed separators, while a bare store-dir mention stays allowed.security.pyis intentionally not modified, so the command-line surface (pass 1b collapse + both budgets) is byte-for-byte unchanged. This avoids the fail-open direction the issue explicitly warned against (exempting a source subject from the budgets would let budget exhaustion re-express as "inspect less").Testing
Nonefrom_vet_script_contents(the"x = 1\n" * 600size body and the three pass-1b bodies).re.compile(r"%LOCALAPPDATA%\\kiro-cli")stays allowed.AWS_SECRET_ACCESS_KEYexfil,/home/u/.netrc); a non-parsing body containing a shell credential read is still blocked via the raw-text fallback (incl. non-SyntaxErrorparse errors, pinned by a monkeypatch test).is_sensitive_bash_commandstill blocks a real%LOCALAPPDATA%command and a >512-stage pipeline.pytest test/test_mcp_cron_security.py test/test_security.py=> 1998 passed, 1 skipped (baseline 1983; +15 new tests). flake8 / mypy / isort clean on changed files.Files changed
src/kiro_crew/mcp_cron.py— parse-aware scan scoping + table-derived Windows store detectortest/test_mcp_cron_security.py— +15 testsdocs/system-specs/modules/learn-cron-dashboard.md— documents the previously-undocumented body scan and its scopingAccepted limitation (documented)
A benign redaction regex anchored under a Windows store (e.g.
r"%LOCALAPPDATA%\\kiro-cli\\S+") is blocked as access-shaped: a static source pattern cannot distinguish it from a real read. Workaround is to anchor the redaction on the POSIX spelling. This is a narrow, low-severity edge (cron bodies run undersh -cwhere the%VAR%spelling does not resolve).