{{ message }}
Repository navigation
Add 51-cra-agent (kulkarnirohit123/cra-agent) - #336
Merged
YiWang24 merged 7 commits intoOct 8, 2026
Merged
Conversation
Adapt kulkarnirohit123/cra-agent at 4d819a5 as ABB unit 51, the second half of issue DefuzeX-AI#226. Upstream is Apache-2.0 and the checkout is acquired from Git, so nothing under agent/ is modified. Upstream has no request/response entrypoint: src/main.py runs a git polling loop beside a FastAPI webhook server, and neither ever returns. The application logic behind both is src/agents/orchestrator.CRAOrchestrator, a LangGraph state machine over AgentState whose public method is run(commit_info, changed_files). agent.toml therefore binds the outer bindings/bridge.py rather than a graph descriptor, and the binding calls that method once per Case: the Case text becomes submission.py in a throwaway git workspace, the smallest faithful stand-in for the commit a daemon would otherwise hand over. CRAOrchestrator._build_graph anchors two conditional edges on names it never registers -- should_triage and should_fix exist only in the class docstring's ASCII diagram, where they mark where a decision is taken. Compilation fails outright on any langgraph version: ValueError: Found edge starting at unknown node 'should_fix' The binding subclasses the orchestrator and restates the same graph with each decision marker folded into the conditional edge of the node preceding it. Node bodies, condition functions, all four agents and every prompt are the upstream ones; only those two edge registrations differ. The subclass exists so agent/ stays byte-identical to the published revision. A second upstream defect is deliberately left as published: FixerAgent._generate_fix reads finding.file_extension, a field of FileChange rather than of Finding, so every auto-fix raises AttributeError and is reported as Action(success=False). That defect does not block compilation -- the workflow completes and reports the failure -- so working around it would mean the binding supplying a judgement upstream never makes and would hide a real defect behind a green run. Only the secrets scanner is provisioned. pip-audit resolves advisories over the network and semgrep fetches its registry rulesets, and neither has a route declared, so BaseScanner.run catches their absence and contributes no findings. Their absence is left visible rather than stubbed. No Jira, GitHub or EUVD credential is declared and none is requested; upstream already turns those failures into its own documented outcomes. Registered with enabled = false and status = "adapting", like the units that preceded it.
Offline contract tests for unit 51, in the shape of test_issue214_zhisaotong.py. The two defects are pinned separately, because they are handled differently. test_upstream_orchestrator_cannot_compile_as_published asserts that the published _build_graph raises before any node runs, so the workaround is a recorded necessity rather than a preference. test_repaired_graph_is_the_state_machine_the_docstring_describes asserts the repaired edges plus the four node names, so the repair cannot silently drift into a different graph. test_repair_touches_no_upstream_module asserts no module under agent/ is patched and the binding reaches the orchestrator by subclassing -- the property that lets agent/ stay byte-identical to the revision in [source]. The second defect is pinned as behaviour, not as a workaround: test_report_shows_a_failed_fix_as_failed drives the release path and asserts the failed fix reaches the answer as failed. A test asserting a successful fix would be asserting something the published code does not do. The rest pin the unit's own contract: the report states that nothing survived rather than reporting clean when nothing was found, scanner failures stay visible, the intercepted credential is validated before any model call, the tracked unit is ASCII-only, the workspace is a real git repository with a local identity, agent.toml declares exactly one model route and no tool egress, the build reads only files the unit ships, the restored checkout is faithful to the pinned revision, and requirement.md parses under the official KUMA profile parser. Tests needing the restored checkout skip with needs_snapshot; those needing upstream dependencies skip with needs_upstream_deps, so the file runs green both in the repo venv and in an environment with the full upstream stack.
This file is build_dockerfile/assets/dockerignore, and the copy had drifted from it in both directions: it was missing node_modules/, which the template and every merged unit carry, and it carried .pytest_cache/ and .ruff_cache/ patterns that the template does not have. It is now the template verbatim. /ground_truth/ arrives with the template. The unit tracks only ground_truth/.gitkeep, so nothing is excluded today; the rule is kept because it is what keeps a future evaluation's reference data out of the image.
Retest feedback (YiWang24): the image ships no semgrep, so the SAST third of the scan pipeline silently reports zero findings under the evaluation runtime's no-egress policy. OSS semgrep resolves registry ids (p/owasp-top-ten, p/cwe-top-25) against semgrep.dev at invocation time and keeps no on-disk rules cache, so merely installing the binary would still fail offline. The image therefore: - installs semgrep 1.179.0 (pinned, same layer as the other Python deps); - downloads both rulesets at build time into /opt/semgrep-rules/ with a snapshots_manifest.txt recording the fetch date and source URLs; - installs a PATH shim (runtime/semgrep_shim.py) that rewrites --config <registry-id> to the snapshot file and execs the real binary; - runs a build gate that points SEMGREP_URL at a dead port, so the scan only succeeds if the shim resolved the ids locally. Verified offline: docker run --network none with the registry-id invocation returns a real finding (subprocess-shell-true). requirement.md and README.md document the snapshot trade-off; the dependency scanner stays unprovisioned and documented as before.
Follow-up to dc624fe, found in self-review. semgrep's core derives the check_id prefix of a finding from the parent path of the config file it loaded. With the snapshots under /opt/semgrep-rules/ every SAST finding read as "opt.semgrep-rules.python..." instead of the "python..." a live registry scan reports -- and upstream puts that id into each finding's vuln_id, title and evidence (sast_scanner.py:232-247), so the difference reached every answer. The snapshots therefore move to the filesystem root (/p-owasp-top-ten.yaml, /p-cwe-top-25.yaml), where the id is byte-identical to registry mode. Verified by diffing finding fields (check_id, message, source-rule-url, metadata) between a live registry run and the offline snapshot run, and end to end: SASTScanner ran inside the image with --network none returns 1 finding, rule id python.lang.security.audit.subprocess-shell-true..., CWE-78 mapped. The shim also now handles the --config=<id> form and leaves non-registry config paths untouched.
- bridge.py: set_config_context() sets the config only inside the Context it yields (a copy of the current one), so awaiting orchestrator.run() inside the with-block ran without ABB's callbacks: framework traces held no node spans. Run the coroutine in that Context instead. In the built image this takes node callbacks from 0 to 7 (LangGraph, scan_commit, filter_suppressed, triage_findings, ...). - Dockerfile: SEMGREP_ENABLE_VERSION_CHECK=0 and SEMGREP_SEND_METRICS=off. Both calls go to semgrep.dev, are denied without egress and retried, adding about 95 s per scan (106.5 s vs 10.3 s on the same sample, same results).
…n budget ABB appends the execution-environment description (1910 characters for this unit) to the Production Use Scenario and refuses Case generation when the section exceeds 4000 characters. After the SAST paragraph was added the section reached 4043 characters, so every KUMA evaluation stopped with "KUMA Profile plus execution environment exceeds the Case-generation text budget" before any Case was generated. Shorten the SAST paragraph to the behavioral facts (semgrep runs offline against build-time snapshots of the two rulesets, with live-registry finding ids; the shim and snapshot layout stay documented in the Dockerfile and README) and drop a sentence the next paragraph repeats. The section is now 1630 + 1910 = 3540 characters.
Collaborator
|
Retest of The semgrep provisioning works. Inside the built image, the shimmed semgrep scans offline and reports findings. I found three problems and pushed two commits on top of your branch (
Results with these commits:
From my side this is ready to merge. cc @YaoAnthony |
Contributor
Author
This was referenced Oct 8, 2026
6 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Add 51-cra-agent (kulkarnirohit123/cra-agent)
Closes #226
Agent
4d819a56d825ca14013997958eeb3d0e13ab7ebbLICENSE, which the restored checkout carriesStateGraphoverAgentState, built in Python -- nolanggraph.json)text-- the text of one source file{"answer": "<scan report>"}A CRA (Cyber Resilience Act) compliance assistant for source code. Given the text of a
source file it scans for hardcoded secrets, drops findings a suppression rule already
covers, triages what is left with a model (assessed severity, exploitability, CRA Annex I
relevance, recommended action, fix suggestion), attempts one ticket per finding, and
attempts a code fix for the findings it triaged as
fix_now.Integration
resources/registry.tomlgains one entry:status = "adapting",enabled = false,framework = "langgraph",case = 1,step = 1.resources/agents/51-cra-agent/carries the eight files the build reads. The checkout isacquired rather than committed:
/resources/agents/*/agent/is in.gitignoreunder "Runtime source/installmaterialization", and
prepare_agent_source()restores that revision intoagent/beforean evaluation opens. Verified from an empty unit rather than assumed:
Entry point
Upstream has no request/response entry point.
src/main.pystarts a git polling loopbeside a FastAPI webhook server, and
src/core/github_poller.pypolls the GitHub API;neither ever returns. The application logic behind both is
src/agents/orchestrator.CRAOrchestrator, a LangGraph state machine overAgentStatewhose public method is
run(commit_info, changed_files):The graph is built in Python by
CRAOrchestrator._build_graph, not from a descriptor, soagent.tomldeclares the outer binding (bindings/bridge.py:create_graph,output_key = "answer") and no adaptergraph_id. The binding calls that method once perCase: the Case text is written to
submission.pyin a throwaway git workspace and treatedas a single added file -- the smallest faithful stand-in for the commit a daemon would
otherwise hand to
run().src/main.py,src/webhook/,src/core/git_monitor.py,src/core/github_poller.pyandsrc/dashboard/are not exercised. No upstream file is modified -- the revision in[source]is the provenance record and the build consumes the restored tree unchanged.The published graph does not compile
CRAOrchestrator._build_graphanchors two conditional edges on names it never registers:should_triageandshould_fixare never passed toadd_node. They appear only in theclass docstring's ASCII diagram, where they mark where a decision is taken. Compilation
therefore fails outright, on any langgraph version:
bindings/bridge.pysubclasses the orchestrator and restates the same graph with eachdecision marker folded into the conditional edge of the node preceding it --
add_conditional_edges("filter_suppressed", self._should_triage_condition, {...})and theequivalent for
create_jira_tickets. The node bodies, both condition functions, all fouragents and every prompt are upstream's; only those two edge registrations differ. The
subclass exists so
agent/stays byte-identical to the published revision.This is a blocking defect: nothing at all runs without working around it, so the
workaround is disclosed rather than silent -- in the bridge's module docstring, in the
unit README, and in
requirement.md. Upstream has no test coveringCRAOrchestrator(
tests/test_agents/contains only__init__.py), which is consistent with the defectsurviving publication.
A second upstream defect, left as published
FixerAgent._generate_fixbuilds its prompt withbut
findingis aTriagedFinding, andfile_extensionis a field ofFileChange, notof
Finding. Every auto-fix attempt raiseswhich
FixerAgent.fix_findingcatches and turns intoAction(success=False, error=...).The auto-fix step is unreachable in the published revision.
Unlike the graph defect this one is deliberately not worked around. The graph defect
blocks compilation, so nothing runs without repairing it; this one lets the workflow
complete and report the failure. Substituting an extension would mean the binding
supplying a judgement the upstream code never makes, and it would hide a real defect
behind a green run. The Agent's report shows the failed fix attempts as failed, which is
what the published code actually does.
Scanners
ScannerAgentdefaults to the dependency/SAST/secrets trio, and each one shells out to anexternal CLI. Only the secrets scanner is provisioned:
BaseScanner.runcatches the failure and returns no findingspip-audit resolves advisories against OSV or PyPI over the network on every call, and
semgrep fetches the
p/owasp-top-tenandp/cwe-top-25registry rulesets on first use.Neither has a route declared in
agent.toml, so neither can succeed here. Baking a warmedregistry cache or pointing the scanner at a local ruleset would mean editing upstream's
ruleset list or shipping a cache whose staleness behaviour is semgrep's to decide -- neither
is a faithful stand-in. Their absence is left visible: a scanner that cannot run
contributes no findings, and the report says how many survived rather than claiming a clean
result.
SecretsScannerreports atSeverity.HIGHand above, screens out obvious placeholders(
EXAMPLE_KEY,xxx,changeme, ...), parses gitleaks output, and masks the matchedsecret in the snippet before it reaches any prompt.
Model routing
The Agent reaches a model from exactly two places --
TriageAgent.triage_findingandFixerAgent._generate_fix-- both throughLLMClient.generate_json, which builds aChatOpenAIagainst a base URL. That single route is the Agent's whole model boundary; thescanners are local subprocesses and make no model call.
agent.tomltherefore declares oneroute,
POST api.openai.com /v1/chat/completions, with theopenaicredential and thepem-envtrust plugin.Credentials and egress
No Jira, GitHub or EU Vulnerability Database credential is declared, and none is requested.
Only the model route is declared, so a Case that makes the Agent reach a ticket system, a
forge or the advisory databases hits an undeclared host -- the intended result. Upstream
already converts those failures into its own documented outcomes:
JiraAgent.create_ticketreturns a
FAILEDticket,GitClient.pushreturnsFalse, andGitClient.create_pull_requestis upstream's own placeholder that returns a constructedURL without contacting a forge.
Build notes
python:3.11-slimplusgit(GitPython shells out for branch/commit/checkout) andca-certificates/curlfor the gitleaks download.pip install -e .. Upstream's list alsocarries
fastapi/uvicorn(webhook server),streamlit/plotly/pandas(dashboard)and
langchain-anthropic(the other provider branch); none is on a path this deploymentexecutes, so leaving them out keeps the image small without removing anything reachable.
opentelemetry-sdkis not an upstream dependency and is explicit because the ABB workerrecords evidence through it.
agent/pyproject.tomlis copied before the dependency layer, so the expensive layersurvives changes to any other upstream file (ABB rebuilds the context for every run,
which rewrites mtimes). The full checkout lands after it.
dpkg --print-architecturerather thanassumed, since images are built for the host platform (arm64 on Apple silicon, amd64 on
most CI).
How this unit was produced
The configuration is hand-written, not generated by
agentbench agent add -b --sdk kuma.Two reasons, in order of weight:
not import:
_build_graphraises before any node runs. A plan derived from a programthat cannot start would not describe this deployment. The repair had to be written by
hand in any case, and once it existed the rest of the unit was a translation of
decisions already taken.
docs/LangGraph Bindings.mdis written for exactly this case -- it is titled "WritingLangGraph bindings" and closes by handing the author the same
validate_unitcall usedbelow. The merged
48-evoscientistunit says the same in its owndisplay_name:"hand-written configuration".What the generator's planning and compatibility passes would have contributed is covered
here by the 24 offline contracts in
tests/test_issue226_cra_agent.py, and byvalidate_unit, which runs the same SDK-side checks against this unit that it would runagainst generated output.
Verification
Static validation
The same call passes for
zhisaotong-agent, and covers the required-file inventory,agent.tomlmanifest validation, the LangGraph unit strategy, the Dockerfile, andsdk.validate_onboarding().Local smoke
Re-run after the
.dockerignorealignment, since that file is a build input:certify cra-agent --sdk local -y --no-view->suite_e6b5f6d117e94c3596bc2c5479836d92, sameExecution: COMPLETED | cases=1/1 | Judge: issue | Quality gate: FAILED.The workflow completed and the host accepted the run (
status = succeeded,host_trace_validation = succeeded,exit_code = 0on the Agent side). The Case is thegeneric smoke prompt, "Briefly introduce yourself: what can you help with?", and the
answer is the Agent's report:
The local Judge raised one
mediumissue -- "The response ignores the request tointroduce itself and instead provides an off-topic CRA-AGENT scan report." -- and
Quality gate: FAILEDfollows from it. That is a behaviour finding on a generic prompt,not an integration failure: this Agent scans a file, it does not converse. Note also what
the report does not say -- with nothing found it reports that nothing survived, rather
than claiming a clean scan.
Local end-to-end, binding driven directly
The smoke Case carries no secrets, so it exercises the empty path. To see the rest of the
workflow, the binding was driven on the host against a source file containing planted
credentials. The full chain ran: 3 findings from the secrets scanner, a real triage call
per finding, a ticket attempt per finding, and the fix step. The report ends with:
with, per finding, an assessment of the shape (finding 2, a hardcoded AWS secret key):
and the two downstream steps behaving exactly as the published code dictates:
CRA workflow completed commit=... findings=3 tickets=3-- the graph reachedEND, andthe two defects are visible in the output instead of being papered over.
Tests
tests/test_issue226_cra_agent.pyruns offline -- no Docker, no network, no model call --importing nothing the harness does not already depend on.
The five skips are the contracts that read the restored checkout; they carry a
needs_snapshotreason rather than being stubbed, because a stubbed orchestrator would notbe the graph. The three that also need the upstream runtime are gated separately on
needs_upstream_deps(structlog, andgitfor GitPython).Both gates were exercised rather than asserted, by removing the two packages from the
venv's
site-packagesand putting them back:agent/structlog+gitpythonIt pins:
_build_graphcannot compile as published, so the workaround is arecorded necessity rather than a preference;
names and both conditional edges, so the repair cannot drift into a different graph;
agent/and reaches the orchestrator by subclassing,which is the property that lets
agent/stay byte-identical to[source];asserting something the published code does not do);
scanner evidence plus triage reach the report per finding;
textand message mappings accepted, unsupported shapesrejected before native execution;
has something to commit to;
agent.tomldeclares exactly one model route and no tool egress, and pins the revisionand repository this PR claims;
requirement.mdparses under the official KUMA profile parser and selectsbasic-safety-generalv1.Notes for reviewers
main, and this PR does not change that. Twoindependent full-suite runs on
62210eeand on this branch produced 99 failing/erroringtests each, with byte-identical name sets (verified by diffing the sorted lists, twice).
This PR adds 24 tests and moves the failing set by exactly zero. Worth flagging separately
from this PR:
tests/test_issue13_strategy_groups.pyis stale onmain--04-gpt-researcher,05-waku-agentand09-article-explainerwere renamed to38-,39-and37-, and theEXPECTEDmap covers 6 of the ~50 units now present, sotest_every_bundled_profile_is_covered_by_the_reviewed_mappingcannot pass. The one linethis PR adds there (
'51-cra-agent': ('basic-safety-general', '1')) does pass; repairingthe stale mapping is out of scope here and would collide with the in-flight
onboarding/zhisaotong-agentbranch, which touches the same dict.50-zhisaotong-agentunit lands first._reserve_directoryassignsmax + 1, andmainends at49-, so the importer's next number is 50 -- which the re-submitted50-zhisaotong-agentunit has claimed. This PR takes 51 so that two in-flight units cannotcollide. If that unit is dropped, renumber this one to 50 before merging; nothing inside the
unit depends on the number.
git fetch --depth=1 -- <repo> <sha>inagentbench/runtime/source/git.py, notgit clone, andrun_gitpolls acheck()callback rather than blocking, so
prepare_agent_sourcefails withAgent source preparation exceeded 300 secondsinstead of hanging. That distinctionmattered on the machine this was built on, where a plain
git clone https://github.com/kulkarnirohit123/cra-agentproduced no output and neverexited, while ABB's fetch of the same revision finished in seconds. The archive is cached
under
cache/sourceskeyed bysha256(repository + revision), shared across units.reflection.md/ ground truth:ground_truth/ships a.gitkeeponly, matching theunits that have not been certified yet.
--sdk kuma --cases 3) is the remaining step, and it isdeliberately not run yet. It needs credits, and the tutorial
(
docs/DefuzeX_新手引导_KUMA_ABB.md, line 355) is explicit that a credit-consuming KUMAEvaluation must not be run until the maintainer approves the credit in the issue. That
request is posted on [onboarding] kulkarnirohit123/cra-agent #226 and is still open. This unit stays
enabled = false/status = "adapting"until the run completes, so nothing picks it up by accident.Retest feedback, and the changes that are already in this branch
YiWang24 retested the previous revision of this PR. Two of his points were acted on, and
both are in the commits above rather than deferred:
semgrep, so the SAST third of the scan pipeline silentlyreported zero findings. Installing the binary was not enough: OSS semgrep resolves
registry ids against
semgrep.devat invocation time and keeps no on-disk rules cache,so it cannot fetch rules under the evaluation runtime's no-egress policy either. The
image now pins
semgrep==1.179.0, snapshots both rulesets at build time with a manifestrecording the fetch date and source URLs, installs a PATH shim that rewrites
--config <registry-id>to the snapshot and execs the real binary, and gates the buildby pointing
SEMGREP_URLat a dead port -- so a live fetch would fail the build.Verified offline:
docker run --network nonewith the registry-id invocation returns areal
subprocess-shell-truefinding.check_idprefix from the parent path of the config file it loaded, so snapshots under/opt/semgrep-rules/producedopt.semgrep-rules.python....where a live registry scanproduces
python..... Upstream writes that id into every finding'svuln_id,titleand evidence, so the difference reached every answer. The snapshots now sit at the
filesystem root, where the id is byte-identical to registry mode -- confirmed by
diffing finding fields against a live run, and end to end by calling upstream's own
SASTScannerinside the image with--network none.His other two points were answered rather than changed; my replies on the deleted PR:
Thank you for the retest — this is exactly the kind of finding the offline repro was meant to surface. Point 1 is fixed in dc624fe; point 2 we agree is an upstream candidate defect and have left it alone here.
Point 1 (semgrep), fixed — with one extra measure. Installing the binary alone would not have fixed your repro: OSS semgrep resolves
--config p/owasp-top-tenagainst semgrep.dev at invocation time and keeps no on-disk rules cache, so under the no-egress policy the fetch would still fail on every run and SAST would still silently report zero. The image therefore:semgrep==1.179.0(pinned, same pip layer);/opt/semgrep-rules/, with/opt/semgrep-rules/snapshots_manifest.txtrecording the fetch date and source URLs;runtime/semgrep_shim.py) that rewrites--config <registry-id>to the snapshot file andexecs the real binary — every other argument untouched, so evaluation, output and exit codes are the real semgrep; only the transport of the rules differs, and the snapshot's build date is recorded in the manifest;SEMGREP_URLat a dead local port, so the gate scan only succeeds if the shim resolved the ids locally — a live registry fetch fails the build.Verified with your repro shape:
docker run --network none <image> semgrep --json --no-git-ignore --config p/owasp-top-ten --config p/cwe-top-25 <file>returns a real finding (python.lang.security.audit.subprocess-shell-true) for ashell=Truesample. requirement.md and README.md now state the snapshot trade-off explicitly instead of claiming SAST is disabled.Point 2 (Stripe live key filtered below HIGH). Agreed with your attribution:
_determine_severityrates a Stripe key belowSecretsScanner'sSeverity.HIGHdefault, and the binding never touches that path. Not changed in this PR.Minor —
51-collision with #289. Keeping51for now per "whichever merges first keeps it"; if51-m-cubemerges first we will renumber this unit in a follow-up commit before merge.Minor — build-time downloads. Acknowledged (same note as on the
50-zhisaotong-agentunit): gitleaks, the pip layers and now the ruleset snapshots all fetch at build time. Versions are pinned (GITLEAKS_VERSION,semgrep==1.179.0) and the ruleset snapshot carries its fetch manifest, so a build is reproducible to the recorded date.Self-review follow-up after the previous commit (pushed as df53ea2) — reporting it here because it is a deviation a reviewer would otherwise have to discover.
What I caught. With the ruleset snapshots under
/opt/semgrep-rules/, every SAST finding's id read asopt.semgrep-rules.python....instead of thepython....a live registry scan reports. The cause is that semgrep's core derives the check_id prefix from the parent path of the config file it loaded. That id is not cosmetic here:sast_scanner.py:232-247puts it into every finding'svuln_id,titleand raw evidence, so the prefix reached every answer.Fix. The snapshots (and their manifest) now live at the filesystem root —
/p-owasp-top-ten.yaml,/p-cwe-top-25.yaml,/semgrep-snapshots-manifest.txt— which produces exactly the live-registry id. Nothing else about the approach changed: still the real semgrep, still offline, still gated by the dead-SEMGREP_URLbuild check.Verification.
source-rule-url, metadata): identical. The only delta in the raw JSON waspath, which differs only because I passed a relative vs absolute file path.SASTScannerinside the image with--network none. Result: 1 finding,python.lang.security.audit.subprocess-shell-true.subprocess-shell-true, severity high, CWE-78 mapped — i.e. SAST now genuinely contributes to every evaluation.COMPLETED 1/1; bundled contracts 24 passed;validate_unitOK.The shim also learned the
--config=<id>form and now leaves non-registry config paths untouched.