Conversation
|
The AST9 upgrade costs the AST7 net on every name outside the five-entry allowlist. Base reached AST7 via Ran The other direction moved too: |
8f123b3 to
77b79a5
Compare
rng1995
left a comment
There was a problem hiding this comment.
[SkillSpector Review]
Reviewed current head 77b79a5a4aa1d3bf9f4cbf3340d9993d659d8605. Joined exec is correctly promoted to AST9 and all hosted checks pass, but the new constant resolver changes the existing AST7 boundary in both directions.
A constructed callable name which resolves outside the five-name set now falls through entirely, so common execution surfaces such as getattr(subprocess, ''.join(['P', 'o', 'p', 'e', 'n']))(...) and joined check_output lose the AST7 signal they had on main. Conversely, a non-string literal such as getattr(obj, 42) changes from silent to AST7 because None conflates not a string constant with unresolved/dynamic.
Preserve the prior AST7 signal for constructed/dynamic names unless they are promoted to AST9, keep direct non-string constants out, and add regressions for joined dangerous non-allowlisted names, joined benign names, and non-string literals. The inline finding identifies the conflated state.
0df162d to
dc389cd
Compare
rng1995
left a comment
There was a problem hiding this comment.
[SkillSpector Review]
Re-reviewed current head dc389cd2131b82a8a9949297504a70c89a1e0c18 against the prior finding, complete analyzer/test diff, surrounding AST7/AST9 behavior, and exact-head checks.
The requested state distinction remains unimplemented. _constant_string() still returns None for both a direct non-string literal and an unresolved/dynamic name. Consequently, getattr(obj, 42) is still newly reported as AST7, while constructed names outside the five-name promotion set—including joined Popen or check_output—still lose the AST7 coverage present on main. Preserve construction/dynamic provenance separately from direct non-string constants and add the requested dangerous, benign, and non-string regressions. I have not duplicated the existing inline finding.
All six exact-head checks pass, but the unresolved behavioral regression and active change requests block merging.
Priority: P1 — this changes reflective-execution detection in both false-negative and false-positive directions.
Signed-off-by: Deepak Jain <deepujain@gmail.com>
Signed-off-by: Deepak Jain <deepujain@gmail.com>
dc389cd to
f1917b5
Compare
Signed-off-by: Deepak Jain <deepujain@gmail.com>
|
All review comments are addressed on the current head
CI is green on this head: https://github.com/NVIDIA/SkillSpector/actions/runs/35485787526 (lint, test-unit, docker-smoke, DCO). Per your 2026-09-18 note, this PR is ready for re-review. |
rng1995
left a comment
There was a problem hiding this comment.
[SkillSpector Review]
Re-reviewed current head 34c179213e1a3bfd01120704a0860a586f9aa77d, the complete two-file diff, surrounding analyzer/AST parsing/resource-budget code, all prior reviews and discussion, the resolved inline thread, regression tests, and hosted checks.
The earlier provenance finding is addressed: direct non-string constants remain silent; constructed names outside the AST9 set (including joined Popen, check_output, __globals__, benign names, and the empty join) retain AST7; joined exec is promoted to AST9. The added tests cover the two previously regressed classification branches and the intended joined-exec promotion. I am not repeating that resolved finding.
One new P1 issue blocks approval: _constant_string() materializes an unbounded joined value from untrusted source before any budget check. A parseable 130,024-character source containing a 100,000-character literal separator and 10,000 empty literal elements passes the 1,000,000-character input gate, but this join expands to 999,900,000 characters. That can exhaust scanner memory even though the only consumer compares the result against five short sink names. See the inline comment for the required bounded-resolution fix and regression coverage.
Validation: all six current-head checks pass (changes, lint, test-unit, OpenCode TypeScript Tests, DCO Check, docker-smoke). The review used static inspection and a reviewer-authored parse-only calculation of the allocation case; contributor code/tests and the large join were not executed locally. gh pr checks --required reports no required checks. The original thread is resolved; GitHub currently reports MERGEABLE/BLOCKED.
Decision: Changes Requested for the new resource-exhaustion regression, not for the resolved prior finding.
| ): | ||
| parts = [_constant_string(item) for item in node.args[0].elts] | ||
| if all(part is not None for part in parts): | ||
| return node.func.value.value.join(part for part in parts if part is not None) |
There was a problem hiding this comment.
[P1] Bound the resolved string before allocating the join. This runs on untrusted skill source, and the source-size gate does not bound the expanded value: a literal 100,000-character separator joined over 10,000 empty string literals fits in 130,024 source characters, but allocates 999,900,000 characters here. _BehavioralBudget.check_runtime() cannot interrupt this allocation, and node() does not handle memory exhaustion, so a small scanned file can exhaust the scanner process. Calculate/cap the prospective output length before each join, including nested joins, and return unresolved so the existing AST7 fallback is preserved when the cap is exceeded; a bound based on the longest dangerous name is sufficient for this consumer. Add bounded-rejection regressions for a large separator/list and nested joins without actually allocating the expanded payload, while retaining the AST7/AST9 classification tests.
There was a problem hiding this comment.
Addressed in 07274d4: _constant_string() now computes the prospective join length before allocating, capped at the longest dangerous getattr name (10 chars, "import"), including nested joins via the recursive unresolved return. Over-cap joins stay unresolved so the AST7 fallback is preserved; bounded joins keep their AST7/AST9 classification. Added regressions: reviewer-shape huge separator/list and nested joins (resolve to None without allocating), plus AST7 fallback assertions. Focused lane (127 tests), ruff lint, and format-check all green.
rng1995 P1 on NVIDIA#544: _constant_string() materialized an unbounded joined value from untrusted source before any budget check. A 100,000-char separator joined over 10,000 empty literals fits in 130,024 source chars but expands to ~999,900,000 chars. Cap the prospective join length at the longest dangerous getattr name; over-cap joins return unresolved so the AST7 dynamic-name fallback is preserved. Adds bounded-rejection regressions for large separator/list and nested joins. Signed-off-by: Deepak Jain <deepujain@gmail.com>
|
Ran it at Scaled 100x down from your example (10,000-char separator, 1,000 empty literals). Nesting costs 4x the flat case at the parent, not 2x. Your first-round case holds: joined Python 3.14.0 under Full scale I did not measure; that rests on the PR's own test, which reports no memory. |
|
Thanks for the independent verification - the tracemalloc comparison against 34c1792 confirms the cap holds (0.0088 MB vs 9.5 MB), and the 12-char AST7 finding still resolving correctly is exactly the intended behavior. |
rng1995
left a comment
There was a problem hiding this comment.
[SkillSpector Review]
Re-reviewed current head 07274d44851ef62aa191fe6bad125e0981b903ff, the complete analyzer/test diff, surrounding caller and source-size/resource-budget behavior, all previous reviews and author replies, review-thread state, and current CI results. Both blocking findings are resolved.
- The original AST7/AST9 provenance regression remains fixed: direct non-string literals remain silent; constructed names outside the sink set retain AST7, including over-cap names such as
check_output; joinedexecis promoted to AST9. - The resource-exhaustion finding is fixed: each supported join calculates its prospective output length before allocation and only joins values up to the longest dangerous name (10 characters). Over-cap nested joins return unresolved recursively, preserving AST7. The large-separator regression exercises the reported amplification case, and end-to-end over-cap tests verify the fallback classification.
No required code or documentation changes remain. The inline test-fixture suggestion is non-blocking: existing direct tests exercise the shared length gate that every recursive join uses, and source inspection confirms the nested protection.
All six hosted checks pass at this head: changes, lint, test-unit, OpenCode TypeScript Tests, DCO Check, and docker-smoke. gh pr checks --required reports no required checks. Tests were inspected, not executed locally; no contributor code was run. One outdated conversation on the now-fixed allocation finding remains unresolved in GitHub and may need maintainer/author resolution before merging.
Decision: Approved for this commit.
Fixes #479.
Resolve constant string construction in reflective
getattrsink names, including''.join(['e', 'x', 'e', 'c']), so AST9 reports dynamic execution sinks.Validation:
python3 -m py_compile src/skillspector/nodes/analyzers/behavioral_ast.pyandgit diff --check. The isolated checkout does not include pytest; hosted CI will run the focused and full suites.