Skip to content

feat(context): tool report doors for traust's adapters CLI (XWING-2440) - #225

Merged
smith-xyz merged 1 commit into
developmentfrom
feat/context-tool-doors
Oct 27, 2026
Merged

smith-xyz merged 1 commit into
developmentfrom
feat/context-tool-doors

Conversation

@smith-xyz

@smith-xyz smith-xyz commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What

Wave 2 lane (b) of plan traust depends on traust-core only. This adds traust_core.v1.context.tool_reports: one function per tool. Each returns exactly the document traust adapters <tool> and traust util elf write today, run on the binary and timeout the caller passes. traust can't import providers under the consumer ratchet, and the registered ctx.tools return AdapterResultArtifact without the CLI's flags, which is why these doors are needed. #211 and #212 named this gap ("lane (b) needs context doors"). The registered tools and run_tools don't change.

These doors are interim. The Tool execution API follow-up and plan 19 (preflight) reshape them later.

Plan: plans/traust-standardization/0-continuous-ops/17-shaun-traust-core-only.md (part)

Chain: this PR ← traust-security/traust#286 (lane b, pinned to this branch's head), which is stacked on traust-security/traust#279 (lane a).

Doors (context/tool_reports.py)

Door Replaces (traust-engine @ 7c6aa58, as traust's CLI called it) Built on
opengrep_report(ctx, target, rules, *, pack, rule_allow, timeout, binary) _execute_opengrep_scan + resolve_rule_pack_dir + allowlist_for_opengrep opengrep.execute_scan, ctx.rule_pack / ctx.rule_pack_supplements, load_rule_allowlist
govulncheck_report(ctx, repo, *, timeout, binary, harness_version) _run_govulncheck + the CLI's candidates document (metadata, summary) govulncheck.run_govulncheck on ctx.safe_exec (go-scan)
gitleaks_report(ctx, repo, *, mode, config, timeout, binary) resolve_gitleaks_config, _run_gitleaks, parse_report, repo_head, tool_version Gitleaks.run / repo_head / version, gitleaks_config
yara_report(ctx, target, rules, *, timeout, binary, max_file_mb) _execute_yara_scan Yara.execute (new context.tools.yara_engine, shared with scanner_tools)
k8s_hardening_report(target) checkov.Scanner(target).run(); .report() k8s_hardening.Scanner + yaml_documents_with_lines
joern_report(ctx, repo, *, symbols, language, packages, module, timeout, max_sites) _run_joern_scan + emit's soundness block joern.run_scan + with_soundness (JOERN_JAVA_OPTS read in context)
osv_report(ctx, repo, *, scanner=None, timeout, freeze) osv.scan_api(OsvClient, DepsDevClient), and the CLI's _osv_binary_doc (run_scanner, parse_output, _tool_version, the failed-body rules) osv.scan_api(…, osv_lookup(ctx), resolver=ctx.deps_dev), osv.run_scanner / parse_output / scanner_version
osv_sbom_report(ctx, sbom, *, scanner=None, timeout, grype) the CLI's _sbom_body and --grype handling (scan_sbom, collect_sbom_packages, run_offline, parse_offline, _with_stats, grype_os_installed_matches, mark_grype_duplicates) the same functions in providers.tools.osv_sbom
elf_report(path, symbols, *, max_scan) _util/elf.ElfAnalyzer as traust util elf used it providers.tools.elf.ElfAnalyzer
populatable_tools(), toolchain_failures(checks), tool_stamp(check) HarnessEngine.toolchain.populatable_tools / preflight_failures / stamp_string POPULATABLE_TOOLS, preflight_failures, ToolStamp.render

context/tools.py: _osv is now the public osv_lookup(ctx), and yara_engine(ctx, process) builds the Yara engine for both scanner_tools and the door. Nothing else changes.

Each door builds its own ProcessRunner (keep_env = config.process.keep_env), allowing the binary the caller names. Its cap is the larger of the caller's --timeout and the tool's default, so --timeout is never cut down to the runner default. A missing binary is a FileNotFoundError naming it. A timeout is a TimeoutError (" timed out after Ns"), and a refused rules source is a ValidationError. A rule pack or gitleaks config that isn't a file is a ConfigError. An osv-scanner run that is missing, times out or fails becomes the failed body naming the air-gapped setup (never a clean scan), as the CLI built it.

Parity

traust's PR carries tests/test_adapters_core_parity.py: each op runs on the same fixtures and fake binaries through the engine path and through these doors, and the outputs must be equal (27 cases: opengrep ×2, govulncheck, KHS, gitleaks dir/history, yara, joern ×2, osv-scanner, osv SBOM + grype, ELF ×2, safe-exec verdicts ×14). Locally it also ran with the real opengrep 1.25 and gitleaks 8.30: the outputs were equal.

Behaviour changes (against the engine path traust called)

Change Who is affected
Every tool runs through ProcessRunner. The child env is config.process.keep_env (default PATH HOME LANG LC_ALL TMPDIR), not the parent's whole env, so proxies and tokens must be listed in keep_env `traust adapters opengrep
A missing binary or a timeout keeps the old exit path but changes its message: "<tool> not found on PATH (<bin>)" and "<tool> timed out after Ns" instead of the raw FileNotFoundError / TimeoutExpired text `adapters gitleaks
The yara report's version is traust-core's version, not traust-engine's (as #212 noted); opengrep's likewise (#211) report readers
gitleaks_report with no usable rules is a ConfigError; the engine raised a bare ValueError adapters gitleaks (traust maps it to exit 1, as the traceback was)
The OSV API path uses the feeds registry's lookup (ctx.advisories) and core's HTTP policy, so the CLI's --timeout no longer sets the per-request timeout. deps.dev answers are cached for a day in the object store (ctx.deps_dev), not under <feeds cache>/deps-dev adapters osv (API)
govulncheck_report's commit is read through GitCheckout.head (rev-parse --verify HEAD^{commit}), still None outside a git checkout none in practice
opengrep's coverage block counts the pack the scan used. The engine always counted the locations/bundled pack, even with --rule-pack adapters opengrep --rule-pack

Unchanged on purpose: every document's shape and keys, all argv, every failure message the CLI printed (the osv failed-body details with the air-gapped help, govulncheck's "produced no scan output (exit N)", yara's "no .yar/.yara rule files found", opengrep's "produced no JSON"), the KHS report, joern's soundness block, and the ELF report.

Legacy files

The engine copies stay until wave 3. Verdicts for what traust called:

Engine symbol Verdict Where
adapters/opengrep._execute_opengrep_scan, resolve_rule_pack_dir Reuse opengrep.execute_scan via opengrep_report; the pack is ctx.rule_pack
adapters/govulncheck._run_govulncheck, harness_version, repo_head Reuse govulncheck_report; harness_version is the caller's
adapters/gitleaks.* (CLI path) Reuse gitleaks_report
adapters/yara._execute_yara_scan Reuse yara_report
adapters/checkov.Scanner Reuse k8s_hardening_report
adapters/joern._run_joern_scan, emit Reuse joern_report; traust writes the file
adapters/osv, adapters/osv_sbom, osv.OsvClient, depsdev.DepsDevClient (CLI path) Reuse osv_report / osv_sbom_report over ctx.advisories / ctx.deps_dev
_util/elf.ElfAnalyzer Reuse elf_report
toolchain ops (populatable_tools, preflight_failures, stamp_string) Reuse populatable_tools, toolchain_failures, tool_stamp

Tests

  • tests/v1/context/test_tool_reports.py (new, 30 cases, 100% line and branch coverage of the module), all on fake binaries. It covers each document's shape, the binary override, the rule allowlist and pack override, refusals as ValidationError, a pack that isn't a directory, govulncheck with no config record, gitleaks dir/history plus missing rules, binary, failure and timeout, yara local rules, refusals, no rule files and timeout, KHS, joern skipped and run (symbols, Java opts, runner), osv API through the lookup and deps.dev, osv-scanner success/no JSON/bad exit/timeout/missing, SBOM offline with grype dedupe, a failed SBOM scan keeping its counts, the API SBOM path, a malformed SBOM, ELF and non-ELF, and the toolchain doors.
  • make lint-fix && make test: 6811 passed, 1 skipped. The pre-push coverage gate passed.
  • Postgres (make test-integration, lane DB traust_test_w2_b): 2374 passed, 3 skipped.
  • Ratchet against development: clean, .ratchet.json unchanged.
  • No queries (item 8 n/a).

Changed tests

None (only added).

  • A person reviewed each changed test and the behaviour change is intended

Quality checklist

  • Comments only for the non-trivial
  • Docstrings don't bloat
  • Don't reinvent (each door calls the existing provider function; osv_lookup and yara_engine are shared with scanner_tools)
  • Shared logic is a helper, in one place
  • Tests cover the edges
  • Integration tests (n/a: no storage)
  • Minimal, clean code
  • Queries (n/a)
  • Tests change only on purpose (none changed)

@smith-xyz
smith-xyz requested a review from chubirka as a code owner October 27, 2026 16:49
@smith-xyz
smith-xyz merged commit 96e0733 into development Oct 27, 2026
3 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant