Agent Risk Scan

August 13, 2026 · View on GitHub

helm-ai-kernel scan is the local-first AI agent risk audit command. It reads local Claude, Codex, MCP, source, and optional workstation receipt evidence, then emits an anonymized RiskEnvelope plus local preview and evidence-pack artifacts.

Audience

Use this command when you need a safe first-pass audit of agent access before installing an in-path boundary, or when you need to turn observe-mode receipts into the same risk vocabulary used by static scan.

Outcome

After running scan, you can show:

  • an A–F boundary grade for the scanned tree, with its reason;
  • which agent surface was detected;
  • which risk codes were emitted;
  • how many MCP servers and config files were observed;
  • a content hash for the exported envelope;
  • a local preview generated only from the envelope;
  • an evidence pack containing only anonymized scan artifacts.

Capability Matrix

CapabilityCommand or artifact
Static local scanhelm-ai-kernel scan --path .
RiskEnvelope JSON--risk-envelope out.json
Markdown preview--preview out.md
HTML preview--preview out.html
Evidence pack tar--evidence-pack pack.tar
Offline evidence-pack verificationhelm-ai-kernel verify-scan --bundle pack.tar
Explicit upload--upload --upload-url <url> --yes
Receipt projection--from-receipts <dir>
Local salt--salt-file <path>
Exclude user config--no-user-config
Content hashenvelope_content_hash
Boundary gradeboundary_grade, Boundary grade: on stdout and in both previews

Boundary Grade

A static scan reports one A–F grade for the scanned tree. The grade is deterministic — the same tree produces the same letter and the same reason — and it is computed from the shadow scanner findings plus whether HELM was detected in the tree:

GradeCondition
Aboundary present with no ungoverned signals, or no agent surface at all
Bboundary present; MEDIUM signals not yet routed through it
Cboundary present; HIGH-severity exposures remain
Dagent surface detected with no boundary, no HIGH findings
Fagent surface with no boundary and HIGH-severity exposure

The grade appears on stdout, in boundary_grade inside the envelope, and as the headline of the Markdown and HTML previews:

Boundary grade: F — 3 agent signal(s) with no execution boundary and 1 HIGH-severity exposure(s)

boundary_grade is optional in the schema. Receipt projections observe traffic rather than a static tree, so they omit it rather than report a default letter.

The reason is not a free-text field. The envelope and the schema both accept only the grader's own deterministic sentences, so no scanned path, repository name, or secret can reach an upload body through it.

Scope, as with the rest of a static scan: the grade describes declared and locally discoverable configuration. It does not establish what an agent executed at runtime.

Static Scan

Run a local scan without upload:

helm-ai-kernel scan \
  --path . \
  --cohort unknown \
  --salt-file ~/.config/helm-ai-kernel/scan_salt.hex \
  --risk-envelope out/risk-envelope.json \
  --preview out/risk-report.md \
  --preview out/risk-report.html \
  --evidence-pack out/risk-scan-pack.tar

For a static scan, the scanner reads recognized config shapes from the declared --path tree:

  • .mcp.json
  • mcp.json
  • .claude.json
  • claude_desktop_config.json
  • .claude/settings*.json
  • .codex/config.toml

It also reads this bounded set of user-level candidates by default, rather than walking the home directory:

  • ~/.claude.json
  • ~/.claude/settings.json and ~/.claude/settings.local.json
  • ~/.codex/config.toml
  • ~/Library/Application Support/Claude/claude_desktop_config.json on macOS or ~/.config/Claude/claude_desktop_config.json on Linux
  • .mcp.json from an enabled Claude plugin selected through its local installed plugin inventory; when more than one installation is present, the latest RFC3339 timestamp wins

Project settings take precedence over a user setting when determining the reported permission mode. Use --no-user-config to exclude only the bounded user-level candidates; it does not skip configuration below --path. The flag has no effect in --from-receipts mode.

Missing optional user config is normal. A discovered recognized config file that cannot be read or parsed stops the static scan without exporting an artifact, so a partial configuration observation is not presented as complete.

It also uses the local shadow scanner findings to project risk codes such as MCP_WRITE_SCOPE_WITHOUT_APPROVAL, SECRET_CLASS_AGENT_READABLE, NO_MANAGED_SETTINGS, and NO_AUDIT_EXPORT.

Receipt Projection

Project observe-mode receipts into the same envelope shape:

helm-ai-kernel scan \
  --from-receipts ./receipts \
  --salt-file ~/.config/helm-ai-kernel/scan_salt.hex \
  --risk-envelope out/risk-envelope.json \
  --preview out/risk-report.md

Receipt mode reads .json and .ndjson files containing agent_run_receipt.v1 or workstation policy decision receipts. It maps observed effect classes into the existing RiskCode, Severity, and ToolClass vocabulary. It does not change runtime dispatch and does not add enforce behavior.

Privacy Boundary

scan is private by non-collection plus a local-only salt. The salt is generated with CSPRNG bytes, persisted with 0600 permissions, and never serialized into the envelope, preview, evidence pack, or upload body.

These values are not exported:

  • raw paths;
  • raw repository names;
  • raw MCP server names;
  • raw commands or command bodies;
  • raw prompts;
  • source snippets;
  • metadata targets;
  • secret values;
  • local salts.

source_pack_hash is not a hash of source files, config bytes, receipt files, or other raw scan input. It is sha256: plus the SHA-256 digest of the HELM canonical JSON form of an anonymized projection summary, as produced by canonicalize.CanonicalHash. Static scans hash sourceSummary: files_scanned, files_skipped, aggregate vendor and severity counts, boundary_grade, helm_present, mcp_server_count, and static_config_files_read. Receipt scans hash the corresponding anonymized receiptSourceSummary: receipt, action, denial, and observe counts plus effect-type, tool-class, and risk-code buckets. Raw file content and raw identifiers enter neither summary.

The evidence-pack tar contains exactly:

  • evidence-pack.json, using the generic contracts.EvidencePack contract;
  • risk-envelope.json;
  • zero or more requested .md or .html previews under previews/.

It adds no independent archive index, seal, signature, or trust format. The raw source pack, raw config files, and raw receipts stay local.

Offline Verification

Verify an archive without uploading it:

helm-ai-kernel verify-scan --bundle out/risk-scan-pack.tar
helm-ai-kernel verify-scan --bundle out/risk-scan-pack.tar --json

The verifier checks:

  • the generic EvidencePack validator's required identifiers and status fields and required attestation.pack_hash, comparing that stored hash with a legacy JCS projection; the projection excludes the entire attestation object, including attestation.kernel_version, and also omits correlation_id, threat_scan, security_findings, network_logs, secret_events, port_exposures, git_diffs, and replay_manifest; verify-scan separately rejects attestation.signature and attestation.signer_id, but this path does not independently apply the complete EvidencePack JSON Schema or bind those omitted fields;
  • the canonical RiskEnvelope representation, content hash, schema, and privacy non-collection fields;
  • the pack-to-envelope IDs and hashes, plus every declared artifact path and SHA-256 hash;
  • the supported final extracted-file layout, rejecting missing, unexpected, unsupported, or hash-mismatched files and unsafe archive paths, entry types, and sizes; duplicate normalized archive member paths are not rejected, so this check does not establish archive-entry uniqueness.

A signature or signer in this local risk-scan pack is rejected as unsupported: the scan has no independently trusted signer. VERIFIED therefore means structural validation and internal hash consistency for the final extracted file set. Because the expected hashes are stored in the same unsigned archive, it does not establish artifact integrity against an actor who can rewrite that archive. It also does not prove that execution occurred or was governed or authorized, nor does it establish runtime provenance or live posture.

Upload Contract

Upload is off by default. When --upload is used, --upload-url is required. The command prints the destination URL, exact body hash, body size, and privacy summary before sending. Without --yes, upload is not sent.

Only the anonymized RiskEnvelope JSON body is posted. No backend ingestion route is implied by this command; operators must provide the explicit upload URL.

Test Coverage

BehaviorTest
salt generation, 0600 persistence, and local-only salt behaviorcore/pkg/riskenvelope/envelope_test.go
Go enum to JSON Schema paritycore/pkg/riskenvelope/envelope_test.go
grade reason is a closed sentence set in both Go and the schemacore/pkg/riskenvelope/envelope_test.go, core/pkg/riskscan/scan_test.go
grade reaches the envelope, previews, and stdoutcore/pkg/riskscan/scan_test.go, core/cmd/helm-ai-kernel/scan_cmd_test.go
content hash changes when findings or any posture field changes, and stale hashes fail validationcore/pkg/riskenvelope/envelope_test.go
static projection omits raw paths, repo names, commands, and secretscore/pkg/riskscan/scan_test.go
Markdown, HTML, and evidence pack outputs omit raw inputscore/pkg/riskscan/scan_test.go
deterministic evidence pack tar contentscore/pkg/riskscan/scan_test.go
upload sends the exact printed envelope bodycore/pkg/riskscan/scan_test.go, core/cmd/helm-ai-kernel/scan_cmd_test.go
--upload-url and --yes gatescore/cmd/helm-ai-kernel/scan_cmd_test.go
user config opt-in, project-over-user precedence, and CLI opt-outcore/pkg/riskscan/scan_test.go, core/cmd/helm-ai-kernel/scan_cmd_test.go
receipt-derived risk mapping and raw receipt leakage checkscore/pkg/riskscan/scan_test.go, core/cmd/helm-ai-kernel/scan_cmd_test.go

Run the focused test set:

cd core
go test ./pkg/riskenvelope ./pkg/riskscan ./pkg/shadow ./cmd/helm-ai-kernel

Then run repository gates:

make verify-boundary
make docs-coverage
make docs-truth

Limits

Static scan shows declared and locally discoverable surface area; it does not prove what an agent actually used. Receipt projection shows observed traffic only for receipts supplied to --from-receipts. Enforce mode remains the runtime boundary path and is not enabled by scan.

Do not market RiskEnvelope as k-anonymity. Suppression metadata exists in the schema, but v1 does not implement an aggregation or suppression model.