diff --git a/crosswalks/cfgaudit-to-ave.json b/crosswalks/cfgaudit-to-ave.json index 5038f32..c092995 100644 --- a/crosswalks/cfgaudit-to-ave.json +++ b/crosswalks/cfgaudit-to-ave.json @@ -3,23 +3,24 @@ "source": { "tool": "cfgaudit", "vendor": "cfgaudit", - "version": "1.11.0", + "version": "1.13.0", "url": "https://github.com/cfgaudit/cfgaudit", "license": "Apache-2.0", "tool_class": "static configuration auditor", - "rules_total": 97, - "rules_mapped": 53 + "rules_total": 108, + "rules_mapped": 64, + "commit": "ac9f2a5314f7ae64242a10cade836d83f304987e" }, "target": { "standard": "AVE", "version": "1.1.0", "url": "https://aveproject.org", - "record_count": 70, - "static_record_count": 51, - "commit": "71b3e53c2e3f1de94c8c98c9ba2a388e513606ce" + "record_count": 80, + "static_record_count": 58, + "commit": "cd1010e81ad608d0e066ccec387dd5dad7dd3056" }, - "generated": "2026-08-05", - "note": "cfgaudit is a static auditor of committable AI-agent CONFIGURATION files. It does not connect to running servers or observe runtime, so it maps only to AVE's static_detection records. Each cfgaudit rule emits its primary AVE id in JSON/SARIF output (see github.com/cfgaudit/cfgaudit/blob/main/docs/cfgaudit-to-ave.md). Mappings are class-level behavioral equivalence, not asserted identity. cfgaudit now maps 53 config-surface rules onto 23 AVE behavioral classes, up from 35 onto 19 at v1.10.0. Most of that growth is not new cfgaudit rules: it is previously unmapped rules finding a home in AVE-2026-00061 through AVE-2026-00064, the four config classes AVE added from this crosswalk's own gap list (aveproject/ave#68). CFG091 moved from AVE-2026-00021 to AVE-2026-00063: 00021 describes a component that explicitly INSTRUCTS the agent to bypass confirmation, while qwen's tools.approvalMode is a setting, which is what 00063 covers 'independent of any instruction text'.", + "generated": "2026-08-16", + "note": "cfgaudit is a static auditor of committable AI-agent CONFIGURATION files. It does not connect to running servers or observe runtime, so it maps only to AVE's static_detection records. Each cfgaudit rule emits its primary AVE id in JSON/SARIF output (see github.com/cfgaudit/cfgaudit/blob/main/docs/cfgaudit-to-ave.md). Mappings are class-level behavioral equivalence, not asserted identity. cfgaudit now maps 61 config-surface rules onto 27 AVE behavioral classes, up from 53 onto 23 at v1.11.0. Only two of the eight new mappings are new cfgaudit rules; the other six are rules that existed all along and are mapped here for the first time, because AVE-2026-00071, 00072, 00073 and 00076 were published on 2026-08-06 and later, after the v1.11.0 crosswalk was generated on 2026-08-05. Three of those four came out of this crosswalk's own gap list (aveproject/ave#68), which is now down to one open surface. Three cfgaudit rules new in v1.12.0 are deliberately left unmapped rather than fitted to an approximate class; each is listed under config_surfaces_beyond_ave with the reason. Both sides are pinned by commit, so every count here can be re-derived. The cfgaudit tree read is two commits past the v1.12.0 tag, and the delta is stated rather than smoothed over: cfgaudit/cfgaudit#516 corrects CFG071 from unmapped to AVE-2026-00073, and cfgaudit/cfgaudit#518 collapses CFG101's findings to one per permission list. Neither adds or removes a rule, so rules_total holds for the tag as well; rules_mapped at the tag itself is 60, not 61.", "mappings": [ { "ave_id": "AVE-2026-00003", @@ -184,7 +185,8 @@ "CFG010", "CFG055", "CFG074", - "CFG089" + "CFG089", + "CFG098" ] }, { @@ -200,7 +202,9 @@ "CFG087", "CFG091", "CFG093", - "CFG096" + "CFG096", + "CFG104", + "CFG105" ] }, { @@ -211,6 +215,39 @@ "CFG067", "CFG086" ] + }, + { + "ave_id": "AVE-2026-00071", + "title": "Container daemon redirected off-host", + "cfgaudit_rules": [ + "CFG082" + ] + }, + { + "ave_id": "AVE-2026-00072", + "title": "MCP server bound to every interface with no authentication step", + "cfgaudit_rules": [ + "CFG018" + ] + }, + { + "ave_id": "AVE-2026-00073", + "title": "Endpoint redirect via a static configuration value", + "cfgaudit_rules": [ + "CFG005", + "CFG046", + "CFG071", + "CFG099" + ] + }, + { + "ave_id": "AVE-2026-00076", + "title": "Natural-language steering of an approval classifier", + "cfgaudit_rules": [ + "CFG094", + "CFG103" + ], + "note": "CFG103 maps for one of its three findings only: features.guardianv2.classifier_instructions replaces the prompt of Codex's own reviewer, which is this record's mechanism, a committed file aiming natural language at a separate non-primary classifier. cfgaudit reports the stronger form, the whole prompt replaced, where the record describes steering." } ], "gaps": [ @@ -226,24 +263,28 @@ "ave_id": "AVE-2026-00059", "note": "fragmented cross-description injection. Needs multi-source correlation; cfgaudit checks each file independently." }, - { - "ave_id": "AVE-2026-00065", - "note": "A2A agent card poisoning. cfgaudit reaches the committed pointer but not the card. A .gemini/agents/*.md may carry an inline agent_card_json, which cfgaudit recognises well enough to classify the file as a remote agent, but it does not audit the card's contents. It does flag a cleartext agent_card_url and a credential literal in the same file's auth block (CFG097)." - }, { "ave_id": "AVE-2026-00060", "note": "STDIO transport shell injection. Server-side implementation flaw, requires SAST of the MCP server source rather than reading its launch configuration. Same layer as AVE-2026-00052 and AVE-2026-00053; see the static_detection note below." }, + { + "ave_id": "AVE-2026-00065", + "note": "A2A agent card poisoning. cfgaudit reaches the committed pointer but not the card. A .gemini/agents/*.md may carry an inline agent_card_json, which cfgaudit recognises well enough to classify the file as a remote agent, but it does not audit the card's contents. It does flag a cleartext agent_card_url and a credential literal in the same file's auth block (CFG097)." + }, { "ave_id": "AVE-2026-00069", "note": "image-hidden instructions in a skill package. Requires binary content analysis of a bundled image; cfgaudit reads text configuration only. Same layer as AVE-2026-00024." + }, + { + "ave_id": "AVE-2026-00077", + "note": "cross-origin tool and resource declaration in one MCP manifest. New gap. cfgaudit reads MCP launch configuration (command, args, env, url, headers), not the manifest a server returns once it is running, so the two declarations this record correlates are never both in view." } ], "coverage": { - "ave_static_records": 51, - "cfgaudit_rules_total": 97, - "cfgaudit_rules_mapped": 53, - "ave_classes_covered": 23, + "ave_static_records": 58, + "cfgaudit_rules_total": 108, + "cfgaudit_rules_mapped": 64, + "ave_classes_covered": 27, "cfgaudit_rules_unmapped": 44 }, "validation": { @@ -257,15 +298,6 @@ "note": "5 of the 10 instruction-content rules that share a scan surface with Bawbel: both scanners independently emit the same ave_id. The 5 divergences are detection-pattern differences, not mapping errors. Re-checked at cfgaudit v1.11.0 against AVE record set 1.1.0 (70 records). A false-positive pass over 432 real repositories preceded this release and changed two rules, neither of them mapped: CFG015 and CFG009 stopped firing on hook idioms, and a Cursor build-cache warning was withdrawn." }, "config_surfaces_beyond_ave": [ - { - "surface": "telemetry / endpoint redirect", - "example_rules": [ - "CFG005", - "CFG046", - "CFG071" - ], - "note": "Still open. Distinct from AVE-2026-00002: nothing is injected into the model's context. A committed key/value changes where the process sends data, and the model never sees it, so detection is a value comparison rather than content analysis. OTEL_EXPORTER_OTLP_*ENDPOINT to a non-local collector, ANTHROPIC_BASE_URL off Anthropic (CVE-2026-21852), a model or provider base URL over cleartext http." - }, { "surface": "sandbox weakening in config", "example_rules": [ @@ -275,43 +307,50 @@ "CFG079", "CFG095" ], - "note": "Still open. sandbox.excludedCommands with a wildcard or shell, bwrapPath/socatPath helper substitution, network.allowUnixSockets naming docker.sock, filesystem.allowWrite on $PATH or a shell rc; Gemini tools.sandboxAllowedPaths exposing / or ~; Codex sandbox_mode danger-full-access and [sandbox_workspace_write] network_access; Cursor .cursor/sandbox.json type insecure_none and an inverted networkPolicy." + "note": "Still open, and the only one of the eight surfaces from aveproject/ave#68 that is. The field list this record would enumerate has now settled, which was the stated reason for holding it: CFG064 reached its final shape in v1.12.0 and no further expansion is planned. Full field list, per agent, in the issue thread. Four mechanisms: (1) the sandbox switched off outright (Codex sandbox_mode danger-full-access, Cursor type insecure_none, Claude Code sandbox.filesystem.disabled). (2) The sandbox left on but widened (Codex [sandbox_workspace_write] network_access and outside-workspace writable_roots, Gemini tools.sandboxAllowedPaths reaching / or ~, tools.sandboxNetworkAccess). (3) The confinement helpers repointed (sandbox.bwrapPath, sandbox.socatPath, sandbox.excludedCommands with a wildcard or a shell, network.allowUnixSockets naming a privileged daemon socket). (4) The same posture reached without touching any sandbox key at all, through Codex's named permission profiles: default_permissions selects a [permissions.] profile whose network block carries enabled, proxy_url, socks_url, dangerously_allow_all_unix_sockets and dangerously_allow_non_loopback_proxy, and whose filesystem block grants ':root' or a credential path. An indicator list keyed on sandbox_mode misses that fourth one entirely." }, { - "surface": "container / daemon posture", + "surface": "cleartext endpoint, distinct from TLS verification disabled", "example_rules": [ - "CFG082", - "CFG084", - "CFG083" + "CFG049", + "CFG097" ], - "note": "Still open, and it is THREE mechanisms with no shared detection logic, not one class. (1) daemon redirected off-host: DOCKER_HOST or a -H/--host flag pointing at a remote tcp:// or ssh:// daemon. (2) image trust verification disabled: DOCKER_CONTENT_TRUST=0, --disable-content-trust, --insecure-registry. (3) browser subprocess replaced: an MCP server's args carrying --utility-cmd-prefix, --renderer-cmd-prefix, --gpu-launcher or --browser-subprocess-path. The first is the highest-value single record." + "note": "Mostly closed, and narrower than this row used to claim. AVE-2026-00073 names a cleartext model or provider base URL outright, which is why CFG071 now maps to it and no longer appears here. What is left is a committed http:// MCP server URL (CFG049) and an A2A agent_card_url (CFG097's second half), both reached only by that record's catch-all 'an equivalent traffic-destination value'. Whether to name them explicitly is the open question from aveproject/ave#123. AVE-2026-00061 stays a different failure: verification disabled, not no TLS at all." }, { - "surface": "MCP network / transport posture", + "surface": "plugin auto-load from a committed manifest", "example_rules": [ - "CFG018", - "CFG066", - "CFG058", - "CFG021", - "CFG069" + "CFG100" ], - "note": "Still open, and likewise a surface rather than a class: FIVE mechanisms. Bind address 0.0.0.0 or [::] (NeighborJack); a wildcard CORS origin in env, escalating when auth is disabled in the same env (CVE-2026-33010); type sse, the deprecated transport; HTTP_PROXY/HTTPS_PROXY/ALL_PROXY resolving off loopback; HTTP transport enabled without log redaction, so request bodies with bearer tokens reach the logs (CVE-2026-42282, CVE-2026-41495). The bind-all case is the highest-value single record." + "note": "New, and deliberately unmapped rather than forced. Grok's committed config carries a [plugins] table with enabled and paths, which points the agent at plugin code the repository ships. AVE-2026-00064 would be the class, but it requires that the loader runs that code at project load with no prompt, and cfgaudit has not verified that against the shipped Grok build. Mapping it on the strength of the shape alone would assert a mechanism nobody measured. Note the polarity: 'disabled' in that table hardens, so a record enumerating field names should not list it as an indicator." }, { - "surface": "cleartext endpoint, distinct from TLS verification disabled", + "surface": "a guardrail that does not hold, as distinct from an attacker behaviour", "example_rules": [ - "CFG049", - "CFG071", - "CFG097" + "CFG101" + ], + "note": "New, and probably outside AVE's model by construction rather than a gap to fill. Claude Code matches a Bash(...) permission pattern as a literal prefix, so a deny rule naming bundled short flags is walked past by writing the same flags in another order: Bash(rm -rf *) never covered rm -fr x. Measured against Claude Code 2.1.231, and roughly 44% of 22,016 indexed settings.json files carrying a deny block leave the gap open. AVE-2026-00063 is a flag that removes a gate and AVE-2026-00068 is composition through shell state; neither is 'the denylist misses an equivalent spelling'. Recorded here because a taxonomy of agent vulnerabilities may want a place for ineffective controls, not because cfgaudit is asking for a record." + }, + { + "surface": "two committed skills claiming one name", + "example_rules": [ + "CFG102" + ], + "note": "New. Name shadowing between two skill files in the same repository, where load order decides which body runs. AVE-2026-00017 is the closest class but is explicitly MCP server identity, and AVE-2026-00066 is registry squatting on hallucinated names; neither covers two local files. Reported as a gap rather than mapped to either." + }, + { + "surface": "automated security reviewer switched off or blunted by config", + "example_rules": [ + "CFG103" ], - "note": "New in this revision, and it has no class in either direction. AVE-2026-00061 covers verification being switched off; this is the case where there is no TLS at all. A committed http:// MCP server URL, model base URL, or A2A agent_card_url." + "note": "Codex's [features.guardianv2] decides whether its own reviewer runs (enabled), at what score it escalates to a blocking review (review_threshold, default 0.5), and what prompt it is given. The prompt half maps to AVE-2026-00076. Switching the reviewer off or raising the threshold does not: AVE-2026-00063 is explicitly a bypassed *human*-approval step, and Guardian v2 is automated. features is not on Codex's project-layer denylist, so a committed .codex/config.toml sets all three." }, { - "surface": "natural-language steering of an approval classifier", + "surface": "repository grants the agent browser or desktop-application access", "example_rules": [ - "CFG094" + "CFG106" ], - "note": "New in this revision. Cursor's .cursor/permissions.json autoRun.allow_instructions is prose the repository feeds to the classifier that decides, in Auto-review mode, whether a tool call runs without asking. It falls between AVE-2026-00063, which is explicitly 'independent of any instruction text', and AVE-2026-00021, which is an instruction to the agent rather than to a gatekeeper." + "note": "Codex's [browser_use] grants per-origin access including full_cdp_access (script, cookie and storage access inside the browser session) and [computer_use] grants desktop application control. Deliberately not filed under AVE-2026-00063: whether a prompt is skipped is unverified, since the value is observable in the effective config but the tools live in the client app, so the class would assert more than the detection does." } ] } diff --git a/crosswalks/cfgaudit-to-ave.md b/crosswalks/cfgaudit-to-ave.md index 38d49c9..b5d358d 100644 --- a/crosswalks/cfgaudit-to-ave.md +++ b/crosswalks/cfgaudit-to-ave.md @@ -8,19 +8,21 @@ cfgaudit emits each rule's primary AVE id in its JSON and SARIF output (`AVEID` | | Version | |---|---| -| cfgaudit | 1.11.0 | -| AVE record set | 1.1.0 (70 records) | +| cfgaudit | 1.13.0, read at `ac9f2a5` | +| AVE record set | 80 records (AVE-2026-00001 .. 00080), read at `1d1d197` | | Bawbel Scanner (validation, below) | 1.3.0 | ## Coverage -cfgaudit has **97 rules** in total. **53 of them map onto 23 AVE behavioral classes**, up from 35 onto 19 at v1.10.0. It is a many-to-one mapping: several cfgaudit rules land on the same AVE class, because cfgaudit slices threats by config surface where AVE slices by behavior. For example, cfgaudit has five distinct secret-detection rules (a secret in `settings.json` env, in an MCP `env`/`headers` block, an entropy fallback, a Continue inline `apiKey`, a crypto signing key), and all five map to the single AVE class `AVE-2026-00047` (hardcoded credentials in component). +cfgaudit has **108 rules** in total. **64 of them map onto 27 AVE behavioral classes**, up from the 53 onto 23 this file carried at v1.11.0. It is a many-to-one mapping: several cfgaudit rules land on the same AVE class, because cfgaudit slices threats by config surface where AVE slices by behavior. For example, cfgaudit has five distinct secret-detection rules (a secret in `settings.json` env, in an MCP `env`/`headers` block, an entropy fallback, a Continue inline `apiKey`, a crypto signing key), and all five map to the single AVE class `AVE-2026-00047` (hardcoded credentials in component). The other 44 rules have no AVE class: they check config surfaces AVE's skill and MCP-server records do not enumerate (see "Config surfaces beyond AVE's model" below). -**Most of that growth is not new cfgaudit rules.** Five rules were added in v1.11.0 and three of them map. The other fifteen new mappings are rules that existed all along and finally have a home, in `AVE-2026-00061` through `AVE-2026-00064`, the four config classes AVE added from this crosswalk's own gap list ([#68](https://github.com/aveproject/ave/issues/68)). Four of the eight surfaces listed below at v1.10.0 are therefore now closed. +**This revision spans two cfgaudit releases,** v1.12.0 and v1.13.0, because the v1.12.0 refresh was still open when v1.13.0 shipped. Eleven rule-to-class pairs are new and four classes are new to this file. Seven of the eleven are rules that existed all along, mapped here for the first time because `AVE-2026-00071`, `00072`, `00073` and `00076` were published after the v1.11.0 crosswalk was generated; three of those four came out of this crosswalk's own gap list ([#68](https://github.com/aveproject/ave/issues/68)). The record set has since grown to 80, but `AVE-2026-00078` through `AVE-2026-00080` are `runtime_observed` and `runtime_drift_detected`, so they give no static rule a home. -**One mapping moved.** `CFG091` (qwen `tools.approvalMode: "yolo"`) was mapped to `AVE-2026-00021`, whose text describes *"a component that explicitly **instructs** the agent to bypass this confirmation step"*. It is a setting, not an instruction, and `AVE-2026-00063` is explicit that it covers the declarative case *"independent of any instruction text"*. `AVE-2026-00021` keeps the instruction-driven rule (`CFG029`). +**Five rules are left unmapped on purpose.** `CFG100`, `CFG101` and `CFG102` from v1.12.0, and `CFG106` plus the other two findings of `CFG103` from v1.13.0. In each case the nearest class is wrong on a stated detail; reasons are under "Config surfaces beyond AVE's model". + +**Both sides are pinned by commit**, so the counts can be re-derived. The cfgaudit tree read is the `v1.13.0` tag itself. ## Rule mapping @@ -47,9 +49,13 @@ The other 44 rules have no AVE class: they check config surfaces AVE's skill and | CFG052, CFG059 | AVE-2026-00017 | server impersonation / spoofing | MCP name shadowing, typosquat | | CFG019, CFG020, CFG070 | AVE-2026-00055 | command exec via untrusted MCP launch config | inline-script, env-code, repo-relative launcher | | CFG075 | AVE-2026-00061 | TLS verification disabled in config | `NODE_TLS_REJECT_UNAUTHORIZED=0`, `GIT_SSL_NO_VERIFY`, `--insecure` in MCP `env`/`args` | -| CFG010, CFG055, CFG074, CFG089 | AVE-2026-00062 | unpinned dependency / supply-chain substitution | unpinned `@latest`/`:latest`, `skills-lock.json` with no integrity pin, marketplace source without a full-SHA pin | -| CFG003, CFG004, CFG048, CFG053, CFG063, CFG079, CFG087, CFG091, CFG093, CFG096 | AVE-2026-00063 | approval gate bypassed by declarative config | `enableAllProjectMcpServers`, `defaultMode: bypassPermissions`, VS Code `chat.permissions.default`, blanket MCP-trust keys, Codex `approval_policy`/`approvals_reviewer`, `autoMode` classifier, a hook answering a permission gate, qwen `approvalMode: yolo`, Cursor allowlists, Gemini MCP `trust` | +| CFG010, CFG055, CFG074, CFG089, CFG098 | AVE-2026-00062 | unpinned dependency / supply-chain substitution | unpinned `@latest`/`:latest`, `skills-lock.json` with no integrity pin, marketplace source without a full-SHA pin; CFG098 adds a `marketplace.json` archive source with no `sha256` and an npm source at a non-default registry | +| CFG003, CFG004, CFG048, CFG053, CFG063, CFG079, CFG087, CFG091, CFG093, CFG096, CFG104, CFG105 | AVE-2026-00063 | approval gate bypassed by declarative config | `enableAllProjectMcpServers`, `defaultMode: bypassPermissions`, VS Code `chat.permissions.default`, blanket MCP-trust keys, Codex `approval_policy`/`approvals_reviewer`, `autoMode` classifier, a hook answering a permission gate, qwen `approvalMode: yolo`, Cursor allowlists, Gemini MCP `trust`, Devin `permissions.allow`, OpenCode `permission` | | CFG047, CFG067, CFG086 | AVE-2026-00064 | zero-click project-load auto-run | `.vscode/tasks.json` `runOn: folderOpen` and Zed `create_worktree` hook tasks, committed project hooks, zero-click hook events | +| CFG082 | AVE-2026-00071 | container daemon redirected off-host | `DOCKER_HOST` in a `settings.json` or MCP `env`, or `docker -H` in a command site, naming a remote `tcp://`/`ssh://` daemon | +| CFG018 | AVE-2026-00072 | MCP server bound to every interface, no auth step | bind address `0.0.0.0` or `[::]` in an MCP server's `args`/`env`, which the record also calls NeighborJack | +| CFG005, CFG046, CFG071, CFG099 | AVE-2026-00073 | endpoint redirect via a static config value | `ANTHROPIC_BASE_URL` off Anthropic (CVE-2026-21852), `OTEL_EXPORTER_OTLP_*ENDPOINT` to a non-local collector, a model or provider base URL over cleartext `http://`, qwen top-level `proxy` | +| CFG094, CFG103 | AVE-2026-00076 | natural-language steering of an approval classifier | Cursor `.cursor/permissions.json` `autoRun.allow_instructions`; Codex `features.guardianv2.classifier_instructions`, which replaces the reviewer's prompt outright (that finding of CFG103 only) | Mappings are class-level behavioral equivalence, not asserted identity. Where a cfgaudit rule covers more than one AVE class, only the canonical primary is emitted (matching AVE's one-`ruleId`-per-class SARIF model); the full multi-mapping is in cfgaudit's own crosswalk doc. @@ -57,28 +63,50 @@ Mappings are class-level behavioral equivalence, not asserted identity. Where a AVE's records enumerate behavior in skills and MCP servers. cfgaudit additionally audits config-file classes that carry no corresponding AVE behavioral class today. -Four of the eight surfaces listed here at v1.10.0 have since been closed by AVE records: permission/approval config and the committed-hook auto-approve case by `AVE-2026-00063`, zero-click auto-run by `AVE-2026-00064`, TLS verification disabled by `AVE-2026-00061`, and supply-chain pinning by `AVE-2026-00062`. What remains: +Seven of the eight surfaces listed here at v1.10.0 have since been closed by AVE records: permission/approval config and the committed-hook auto-approve case by `AVE-2026-00063`, zero-click auto-run by `AVE-2026-00064`, TLS verification disabled by `AVE-2026-00061`, supply-chain pinning by `AVE-2026-00062`, container/daemon posture by `AVE-2026-00071`, MCP network posture by `AVE-2026-00072`, and telemetry/endpoint redirect by `AVE-2026-00073`. The natural-language-steering surface added in the last revision was closed by `AVE-2026-00076` in the same window. What remains: + +| Config surface | Example files / keys | Example rules | Status | +|---|---|---|---| +| Sandbox weakening in config | four mechanisms, listed below | CFG022, CFG061, CFG064, CFG079, CFG095 | **the one still open** | +| Cleartext endpoint, distinct from TLS verification disabled | a committed `http://` MCP server URL, an A2A `agent_card_url` | CFG049, CFG097 | mostly closed by `AVE-2026-00073`, two fields reached only by its catch-all | +| Plugin auto-load from a committed manifest | Grok `[plugins]` `enabled` / `paths` | CFG100 | unmapped on purpose, see below | +| A guardrail that does not hold | a `deny` pattern walked past by flag reordering | CFG101 | probably outside AVE's model by construction | +| Two committed skills claiming one name | two `SKILL.md` files, load order decides | CFG102 | no class fits without stretching one | +| An **automated** security reviewer switched off or blunted | Codex `[features.guardianv2]` `enabled`, `review_threshold` | CFG103 | new gap: `AVE-2026-00063` is a bypassed **human**-approval step | +| Repository grants browser or desktop-application access | Codex `[browser_use]` `full_cdp_access`, `[computer_use]` app access | CFG106 | new gap, and not filed under `AVE-2026-00063` on purpose | + +### Sandbox weakening, the one surface still open + +This is the last of the eight, and the reason it is still open is that the field list was in motion when it was last picked up ([#68](https://github.com/aveproject/ave/issues/68)). **It has since settled.** CFG064 reached its current shape in v1.12.0 and nothing further is queued against it. The mechanisms, which are four rather than one: + +1. **The sandbox switched off outright.** Codex `sandbox_mode = "danger-full-access"`, Cursor `type: "insecure_none"`, Claude Code `sandbox.filesystem.disabled: true`. +2. **The sandbox left on but widened.** Codex `[sandbox_workspace_write]` `network_access = true` and `writable_roots` reaching outside the workspace; Gemini `tools.sandboxAllowedPaths` exposing `/` or `~`, and `tools.sandboxNetworkAccess: true`. +3. **The confinement helpers repointed.** Claude Code `sandbox.bwrapPath` and `sandbox.socatPath` (honored only from managed settings, so anomalous anywhere else), `sandbox.excludedCommands` carrying a wildcard or a shell, `sandbox.network.allowUnixSockets` naming a privileged daemon socket such as `docker.sock`, `sandbox.filesystem.allowWrite` on `$PATH` or a shell rc file. +4. **The same posture reached without touching a sandbox key at all.** Codex's named permission profiles: `default_permissions` selects a `[permissions.]` profile whose `network` block carries `enabled`, `proxy_url`, `socks_url`, `dangerously_allow_all_unix_sockets` and `dangerously_allow_non_loopback_proxy`, and whose `filesystem` block can grant `":root"` or a credential path. `extends` genuinely inherits, so a profile cannot be judged from its own table. An indicator list keyed on `sandbox_mode` misses this fourth mechanism entirely. + +**Is "widened" a different class from "off"?** Earlier read was maybe. After building it: no. Both are the same check, read a declared value and compare it to the default. The difference is blast radius, not kind. Mechanism 4 is the one that is genuinely different, because it reaches the same posture through a key with no "sandbox" in the name. + +**The hardening-not-weakening trap, extended.** Beyond `disableTmpWrite`, `exclude_tmpdir_env_var` and `exclude_slash_tmp`, three more instances turned up while building v1.12.0, all measured: Grok's `[plugins] disabled` (naming a plugin to discover but not activate hardens); qwen's `memory.autoSkillConfirm`, whose default is **true**, so the weakening value there is `false`, the inverse of every `disable*` key; and Codex's profile `filesystem` block, where across 69 real `.codex/config.toml` files the decisions are `deny` 177, `write` 72, `read` 53, `none` 51, so the block's presence is overwhelmingly hardening and only a granting direction on a sensitive target is a finding. One more for an `indicators_of_compromise` list: a read grant on `~/.ssh/id_ed25519.pub` is not credential exposure, and a real profile in that corpus carries exactly that. + +### The two new gaps + +**An automated reviewer switched off by config.** Codex's `[features.guardianv2]` decides whether its own security reviewer runs (`enabled`), at what score it escalates to a blocking review (`review_threshold`, default `0.5`, a number the reviewer's own prompt states), and what prompt it is given (`classifier_instructions`). The prompt half is `AVE-2026-00076` and is mapped above. The other two are not: `AVE-2026-00063` is explicitly a bypassed *human*-approval step, and Guardian v2 is an automated reviewer. `features` is not on Codex's project-layer denylist, so a committed `.codex/config.toml` sets all three; verified against codex `0.150.0-alpha.7` by reading the effective config back through its app server in a trusted directory, with a denylisted key and an ordinary key as controls. -| Config surface | Example files / keys | Example rules | -|---|---|---| -| Telemetry / endpoint redirect | `OTEL_EXPORTER_OTLP_*ENDPOINT` to a non-local collector, `ANTHROPIC_BASE_URL` off Anthropic, model or provider `base_url` over cleartext | CFG005, CFG046, CFG071 | -| Sandbox weakening in config | `sandbox.excludedCommands` wildcard/shell, `bwrapPath`/`socatPath`, `allowUnixSockets` naming `docker.sock`; Gemini `tools.sandboxAllowedPaths` exposing `/`; Codex `danger-full-access` and `[sandbox_workspace_write] network_access`; Cursor `type: insecure_none` | CFG022, CFG061, CFG064, CFG079, CFG095 | -| Container / daemon posture | `DOCKER_HOST` or `-H` at a remote `tcp://`/`ssh://` daemon; `DOCKER_CONTENT_TRUST=0`, `--disable-content-trust`, `--insecure-registry`; Chromium `--utility-cmd-prefix`, `--renderer-cmd-prefix`, `--gpu-launcher`, `--browser-subprocess-path` in MCP `args` | CFG082, CFG084, CFG083 | -| MCP network / transport posture | bind `0.0.0.0`/`[::]`; wildcard CORS origin, escalating when auth is disabled in the same `env`; `type: sse`; `HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY` off loopback; HTTP transport without log redaction | CFG018, CFG066, CFG058, CFG021, CFG069 | -| Cleartext endpoint, distinct from TLS verification disabled | a committed `http://` MCP server URL, model base URL, or A2A `agent_card_url` | CFG049, CFG071, CFG097 | -| Natural-language steering of an approval classifier | Cursor `.cursor/permissions.json` `autoRun.allow_instructions` | CFG094 | +**A repository granting browser or desktop access.** Codex's `[browser_use]` grants per-origin `access`, `downloads`, `uploads` and `full_cdp_access` (full Chrome DevTools Protocol, so script execution plus cookie and storage access inside the browser session), and `[computer_use]` grants control of desktop applications by bundle id, AUMID or executable. The value type is an enum of exactly `allow` and `deny`, so there is no ask state and `allow` is unambiguously the weakening direction. This is deliberately **not** filed under `AVE-2026-00063`: whether a prompt is skipped is unverified, because the value is observable in the effective config but the tools themselves live in the client app. Filing it there would assert more than the detection does. -The last two are new in this revision. +### The three deliberately unmapped rules -**Container posture and MCP network posture are each several mechanisms, not one class.** Container posture is three with no shared detection logic: the daemon redirect, image-trust verification being off, and a launcher flag replacing the browser subprocess. MCP network posture is five. If either becomes a record, the daemon redirect and the bind-all case are the highest-value single ones. This was the specific question in [#68](https://github.com/aveproject/ave/issues/68), answered there at field level. +- **CFG100**, Grok `[plugins]`. `AVE-2026-00064` would be the class, but it requires the loader to run plugin code at project load with no prompt. That is not verified against the shipped Grok build, so mapping it would assert a mechanism nobody measured. +- **CFG101**, a deny rule walked past by flag reordering. Claude Code matches a `Bash(...)` pattern as a literal prefix, so `Bash(rm -rf *)` never covered `rm -fr x`. Measured against Claude Code 2.1.231; about 44% of 22,016 indexed `settings.json` files with a `deny` block leave the gap open. `AVE-2026-00063` is a flag that removes a gate, `AVE-2026-00068` is composition through shell state. Neither is "the denylist misses an equivalent spelling". This is an ineffective control, not an attacker behavior, so it may be outside AVE's scope. +- **CFG102**, two committed skills claiming one name. `AVE-2026-00017` is the closest class but is explicitly MCP server identity, and `AVE-2026-00066` is registry squatting on hallucinated names. Neither covers two local files where load order decides which body runs. -These are not gaps in this crosswalk; they are config classes outside AVE's current skill/MCP-behavioral scope. They are listed here so the taxonomy's coverage against a config-auditor is visible. +These are not gaps in this crosswalk. They are config classes outside AVE's current skill/MCP scope, listed so coverage stays visible in both directions. ## Cross-implementation validation (cfgaudit vs Bawbel Scanner) To test whether the shared ids actually interoperate, cfgaudit **1.9.0** and [Bawbel Scanner](https://github.com/bawbel/scanner) **1.3.0**, which share no code and no ruleset and only the AVE taxonomy, were run on the same `SKILL.md` files using cfgaudit's canonical trigger text unmodified (not tuned for agreement). Static engines only (`pattern`+`yara`+`semgrep`, no LLM), both reading `ave_id` from JSON. -Of cfgaudit's 33 AVE-mapped rules, **10 instruction/skill-content rules share a scan surface with Bawbel's file scan** (the other 23 read command sites or config files Bawbel's file scan does not cover). Of those 10: +Of the 33 AVE-mapped rules cfgaudit had at that version, **10 instruction/skill-content rules share a scan surface with Bawbel's file scan** (the other 23 read command sites or config files Bawbel's file scan does not cover). Of those 10: **Both scanners independently emit the same `ave_id` on 5 of the 10.** @@ -109,3 +137,4 @@ Static `static_detection` classes cfgaudit does not map, with the reason: | AVE-2026-00060 | STDIO transport shell injection. A server-side implementation flaw: it needs SAST of the MCP server's source, not a read of its launch configuration. Same layer as AVE-2026-00052 and AVE-2026-00053. | | AVE-2026-00065 | A2A agent card poisoning. cfgaudit reaches the committed pointer but not the card. A `.gemini/agents/*.md` may carry an inline `agent_card_json`, which cfgaudit recognises well enough to classify the file as a remote agent, but it does not audit the card's contents. It does flag a cleartext `agent_card_url` and a credential literal in the same file's `auth` block (CFG097). | | AVE-2026-00069 | Image-hidden instructions in a skill package. Needs binary content analysis of a bundled image; cfgaudit reads text configuration only. Same layer as AVE-2026-00024. | +| AVE-2026-00077 | Cross-origin tool and resource declaration in one MCP manifest. New gap. cfgaudit reads MCP *launch* configuration (`command`, `args`, `env`, `url`, `headers`), not the manifest a server returns once it is running, so the two declarations this record correlates are never both in view. |