Repository navigation
fix(2.13.x): parse-limit regression, gateway LLM output and OSV advisory cap - #1010
mohgupta-ship-it wants to merge 4 commits into
Conversation
Ledger rows for a rejected structured response all said ValidationError, which hid whether the model answered in prose, dropped a required field, returned the wrong tool, or was cut short. Tag every rejection with a fixed check code and add a content-free diagnostic to the ledger row and log line: the check, the answer channel (tool call or text), the provider's completion label, where prose or code fences surrounded a JSON object, schema field paths with their validation error types, and the check that rejected each bounded attempt. Diagnostics are validated against a fixed grammar and never carry model output, field values or undeclared keys. Backport of the diagnostics change prepared for main; retries, recovery and fail-closed outcomes are unchanged. Signed-off-by: Mohit Gupta <mohgupta@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A widely used package can carry more than sixteen advisories for one locked version (filelock 4.1.1 has 17), and truncating them left the dependency manifest partially inspected with output_limit. Raise the per-package cap to 64; the per-scan detail-request and result budgets still bound latency, and exceeding any cap still reports output_limit. Signed-off-by: Mohit Gupta <mohgupta@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The OpenAI provider binds a strict json_schema response format, and the analyzer prompts carry no output-format instruction. An OpenAI-compatible gateway can serve that format for Claude through a tool the model is not forced to call (Claude 5.5 rejects forced tool calls), so a prose answer leaves no JSON object and is rejected as llm_structured_response_invalid after the bounded retries. For Claude model IDs on OpenAI-protocol clients bound with json_schema, append an instruction to return exactly one JSON object matching the schema, without prose or code fences. Other models and methods keep their prompts; validation is unchanged. Signed-off-by: Mohit Gupta <mohgupta@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 2.13.3 shell-ownership backport (cd0e579) stopped treating Python quotes as shell quotes, so a backtick word inside a string, such as "else `tool` on PATH" in a docstring, became its own command-word candidate. Its operands are bounded by the string's value, but the 2.13 string-value guard keeps the whole-source fallback whenever the module has any recognized shell path or an unlisted os/dunder use, so ordinary helpers that were complete in 2.12 and on main became static_parse_limit. - A docstring or other bare string statement is evaluated and dropped, so its own body bounds such a word whatever else the module does, unless the module reads docstrings back (__doc__, getdoc). - The guard treats os calls that run no program (makedirs, kill, readlink, ...) and name attributes (__name__, __qualname__, __module__, __file__) as inert. Shell sinks, reflection, dynamic imports and object escapes still keep the fallback, and a destructive operand inside a docstring still exhausts the bounded check. Signed-off-by: Mohit Gupta <mohgupta@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rng1995
left a comment
There was a problem hiding this comment.
[SkillSpector Review]
Decision: Changes Requested.
Value and readiness
The maintenance work usefully reduces unnecessary partial scans, makes rejected model answers diagnosable, and retains more advisories under the existing aggregate budget. The new discarded-string exception still treats reachable docstrings as inert in a supported readback form, so it needs one correction before approval.
Material findings
Blocker — src/skillspector/python_tokens.py:941-951 and src/skillspector/nodes/analyzers/static_patterns_tool_misuse.py:3661-3665: preserve the fallback for aliased or indirect docstring readback. The new helper recognizes a literal "__doc__", an attribute named __doc__/getdoc, or a name spelled getdoc. It does not recognize from inspect import getdoc as doc followed by run(doc(find) + ' -rf /'), although that retrieves and executes the same function docstring as the tested inspect.getdoc(find) case. The imported helper is an ast.alias, and the call name is doc; neither matches the guard. getattr(find, '__' + 'doc__') similarly escapes the literal-only check. With a backtick word in find's docstring, python_discarded_string_spans therefore returns its span even when the whole-module shell/reflection guard has correctly refused isolated bounds. The new _discarded(owner) override then admits that span, and the operand check at lines 2627-2635 can limit inspection to the docstring body while excluding the later destructive operands. This is an unsound ownership proof for a value that actually reaches a shell, rather than a harmless documentation false positive. Separate genuinely discarded bare-string statements from retrievable docstrings, retain the conservative path when introspection aliases/indirect attribute access cannot be excluded, and add regressions for the aliased getdoc and computed __doc__ forms. The existing tests cover only explicit attribute/name/literal spellings.
No separate material blocker found in the other three changes. The gateway instruction is restricted to OpenAI-protocol clients, final model-ID components naming Claude, and json_schema/its default; validation, refusal/completion checks and bounded retries remain in place. The diagnostics derive channel, completion and shape from fixed codes, redact undeclared schema keys to *, exclude validation inputs/context, cap schema entries, and carry the field through batch, TP4, meta and ledger projection paths. The OSV per-package cap rises to 64 while the 64-detail-request, 256-result, response-byte and runtime budgets remain enforced, with limitations retained when exhausted.
PIC tradeoffs
Raising the per-package cap can spend more of the unchanged shared detail budget on an advisory-heavy early package, leaving later details partial; the implementation reports the limitation. Live gateway behavior remains unvalidated, so the additional instruction is a compatibility measure, not evidence of model-quality acceptance. Neither tradeoff requires weakening the existing validation boundary.
Verification and gaps
Read the complete 17-file diff and pinned implementation/test context for discarded-string ownership, the global shell guard and bounded operand consumer, gateway binding, structured-response checks, diagnostic propagation and OSV budget enforcement. Read the complete empty review/comment/thread history. Fresh metadata confirms the head is unchanged, open and non-draft, and all six current checks report success in CI run 41781149578. The older local failures described in the PR body are not being presented as current failures. The readback finding is a static source trace; no end-to-end scan result or runtime reproduction is claimed. No contributor code, tests, snippets, imports, builds, setup, hooks, scanner invocations or model calls were executed.
Reviewed head: 09c81a42b285ee9c19711c408ad2eb637ec89fd1.
Summary
Maintenance fixes for the 2.13.x line. A downstream CI pinned to 2.13.3 reported 193 incomplete scans in one day. The most common causes were
static_parse_limitfrom the tool-misuse parser andllm_structured_response_invalidfromsemantic_developer_intent. This PR fixes the parser regression that 2.13.3 introduced, hardens LLM JSON output on gateway Claude routes, reports why each model answer was rejected, and raises the OSV per-package advisory cap. Each change is its own commit.fix(parser)static_parse_limitfix(llm)json_schemafix(llm)diagnosticto rejected-response ledger rows and logsfix(sc4)Root causes
main. All 18 bisect tocd0e5791, the 2.13.3 backport of fix(static): preserve shell and browser template boundaries #948.else `tool` on PATHin a docstring, became its own command-word candidate.0e96f345, not onmain) keeps the whole-source fallback for many ordinary constructs, such asre.compile,os.makedirs,getattrandtype(e).__name__. The scan therefore fails closed.__doc__orgetdoc.oscalls and the__name__,__qualname__,__module__and__file__attributes as inert. Shell sinks, reflection, dynamic imports and object escapes keep the fallback, and destructive operands inside a docstring still exhaust the check. All existing guard tests pass unchanged.json_schema, and the analyzer prompts carry no output-format instruction. A gateway can serve that format for Claude through a tool the model is not forced to call, because Claude 5.5 rejects forced tool calls. A prose answer then fails every bounded retry as "not a JSON object". Every failure in the CI sample is of that class. The instruction is limited to Claude model IDs on OpenAI-protocol clients bound withjson_schema; validation is unchanged.output_limitonpoetry.lock:filelock==4.1.1has 17 OSV advisories, one more than the per-package cap. The per-scan detail and result budgets are unchanged.Validation
Replay of 59 production skills fetched at the commits from the failing jobs, static analysis only. Private inputs and reports are retained locally.
reference_missingis treated as advisory, as in the downstream CI.static_parse_limitrowsoutput_limitrowsNo skill gained a blocking row. Mapped onto the 193 incomplete CI scans: 62 are in skills not available locally. Of the rest, 24 now clear on static grounds, against 4 on 2.13.3, and 10 more are left with only LLM rows.
Unit tests: 13,386 passed. 23 failures and 51 errors are identical on unmodified 2.13.3 in the local sandbox: the TP4 HTTP fixtures cannot bind a local port, the real-
uvlauncher tests cannot run, and three tests are timing-bound.New tests: 24 parser tests, including docstrings read back through
__doc__,getattrandgetdoc, destructive docstrings, and assigned strings that reach a shell. Also 8 tests for the LLM output instruction, one OSV test, and the diagnostics tests.ruff checkandruff format --checkpass.Not validated
Out of scope
importlibor a bundled import; the guard stays closed by design.mainby fix(analyzer): keep Python and Perl syntax from exhausting the shell parser #778, fix(analyzer): stop prose apostrophes and Python report strings from marking files partial #849, fix(static): stop backticks/apostrophes in comments and prose from tripping static_parse_limit #902, fix(analyzer): recognize JavaScript template literals #801 and fix(analyzer): avoid false parse limits on quoted shell assignments #634; the 2.13 line carries different re-implementations of those changes.obfuscated_instruction_textfalse positives on removal verbs inside inline code, such as subcommand lists and HTTPDELETEroutes. These also occur onmain.🤖 Generated with Claude Code