Repository navigation
Classify provider policy refusals instead of parsing them as findings JSON - #137
ValentinTorassa wants to merge 2 commits into
Conversation
|
@ValenTorassaColomberoTeramot is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
… JSON A hard provider refusal (Anthropic AUP block, OpenAI safety-system rejection, content_policy_violation) arrives as prose, fails JSON.parse, and surfaced as a generic parse-failure — a formatting problem on the surface, zero coverage underneath. Worse, the JSON-repair follow-up then asked the declining model to re-emit conclusions it never produced and accepted an empty findings array as a valid answer, recording unanalyzed files as investigated and clean. classifyPolicyRefusal() detects explicit refusal strings inside the parseAgentJsonArray catch (before jsonrepair, so valid JSON quoting refusal text as evidence still parses) and throws AgentPolicyRefusalError. All six investigate/revalidate parse sites rethrow it before the repair follow-up. Per-batch failure, matching the existing parse-failure semantics; the rest of the run continues. Fixes vercel-labs#92.
74e6c1c to
f86fdb8
Compare
…-refusals # Conflicts: # packages/processor/src/__tests__/shared.test.ts # packages/processor/src/agents/claude-agent-sdk.ts # packages/processor/src/agents/codex-sdk.ts # packages/processor/src/agents/pi-sdk.ts
|
Merged Checked locally on the merged branch: |
🤖 auto-maintain reviewAutomated, advisory triage for
Review panel: 🟡 medium highest severity deepsec maintainer code review: 🟡 medium
General code review: 🟡 medium
Scanner and matcher quality: 🟡 medium
Sandbox and credential boundaries: 🟡 medium
Durability and distributed state: 🟡 medium
Posted by auto-maintain. This automated code review is advisory; a human maintainer makes the call. |
|
Gentle follow-up: the branch is still up to date with |
Fixes #92
What changed
parseAgentJsonArraynow classifies hard provider policy refusals (AnthropicAUP blocks, OpenAI safety-system rejections,
content_policy_violation)before attempting
jsonrepair, and throws a dedicatedAgentPolicyRefusalError. All six investigate/revalidate parse sites acrossthe three agent backends rethrow it immediately instead of sending the
JSON-repair follow-up.
Why
When a provider refuses on policy grounds, the refusal text currently reaches
the findings parser and fails as "wasn't a parseable JSON findings array" —
reported as a formatting problem when the real condition is zero coverage
over the batch.
Worse, the parse-failure path then sends
buildInvestigateJsonRepairPromptto the same session. That prompt asks the model to "re-output the same
conclusions" and explicitly accepts an empty findings array for files with no
issues. If a model that just declined complies, the batch parses cleanly and
those files are recorded as investigated-with-no-findings — a silent coverage
gap, indistinguishable from a clean batch. With models that decline security
work as a documented behavior (e.g. Fable, per the DeepsecBench post), this
is an expected operating condition, not an edge case.
Unlike
QuotaExhaustedError, this stays per-batch: a refusal depends onbatch content, so the rest of the run continues and a rerun picks the failed
batch up.
Verification
pnpm testpasses (added regression tests: refusal text →AgentPolicyRefusalErrorfrom bothparseInvestigateResultsandparseRevalidateVerdicts; valid findings JSON quoting refusal text asevidence still parses normally)
pnpm lintpassespnpm knippassesAlso ran
pnpm -r build,pnpm test:bundle, andpnpm typecheck:deepsec—all green.
Notes for reviewer
classifyQuotaError: a false positive would relabel ordinary malformed JSONas zero-coverage, which is worse than the status quo. Only explicit
provider-refusal strings match; anything else degrades to the existing
fail-loud parse error.
JSON.parsecatch, so valid findingsJSON that merely quotes refusal text (e.g. as evidence in a description)
parses normally — covered by a regression test.
quota) if you'd rather treat a hard refusal as a stop condition.
category in the end-of-run summary (they currently collapse into
"N batch(es) failed"), and classifying refusals that arrive as SDK error
results rather than result text (that path already fails loud, just with a
less precise message).
Found this while preparing a hands-on review of deepsec for my
security-engineering YouTube channel
(@vtcibersecurity, dev/appsec
audience) — happy to adjust anything here, and to take on the
summary-category follow-up if you want it.