From 1295b26e58b89a15884027bb958e1a661b4636b1 Mon Sep 17 00:00:00 2001 From: Conal <33135619+Conalh@users.noreply.github.com> Date: Fri, 22 May 2026 09:31:03 -0700 Subject: [PATCH] Namespace finding kinds: prefix with 'capability_echo.' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopts agent-gov-core v0.2.0 and prefixes every Finding.kind with the 'capability_echo.' namespace per the suite-wide v1.0 wire-format wave (agent-gov-core#1). 24 kinds renamed across detectors (JS/TS, Python, shell, Dockerfile, package scripts, package deps, workflow permissions). Also updated the SUMMARY_LABELS map keys in src/report.ts to use the prefixed kinds. Pure rename — no detector semantics, severities, or messages changed. 64/64 tests passing on the new prefixed assertions. --- dist/detectors/dockerfile-capability.js | 4 +-- dist/detectors/js-capability.js | 8 ++--- dist/detectors/package-deps.js | 4 +-- dist/detectors/package-scripts.js | 6 ++-- dist/detectors/py-capability.js | 10 +++--- dist/detectors/shell-capability.js | 4 +-- dist/detectors/workflow-permissions.js | 22 ++++++------ dist/report.js | 48 ++++++++++++------------- package-lock.json | 2 +- package.json | 2 +- src/detectors/dockerfile-capability.ts | 4 +-- src/detectors/js-capability.ts | 8 ++--- src/detectors/package-deps.ts | 4 +-- src/detectors/package-scripts.ts | 6 ++-- src/detectors/py-capability.ts | 10 +++--- src/detectors/shell-capability.ts | 4 +-- src/detectors/workflow-permissions.ts | 22 ++++++------ src/report.ts | 48 ++++++++++++------------- test/cli-output.test.mjs | 8 ++--- test/detectors.test.mjs | 42 +++++++++++----------- test/dockerfile-capability.test.mjs | 4 +-- test/git-diff.test.mjs | 18 +++++----- test/package-deps.test.mjs | 6 ++-- test/py-capability.test.mjs | 26 +++++++------- test/shell-capability.test.mjs | 4 +-- test/workflow.test.mjs | 2 +- 26 files changed, 163 insertions(+), 163 deletions(-) diff --git a/dist/detectors/dockerfile-capability.js b/dist/detectors/dockerfile-capability.js index e7f7c94..cb5476f 100644 --- a/dist/detectors/dockerfile-capability.js +++ b/dist/detectors/dockerfile-capability.js @@ -16,7 +16,7 @@ function detectRemoteAdd(added) { } return [ { - kind: 'dockerfile_remote_add', + kind: 'capability_echo.dockerfile_remote_add', surface: 'container', severity: 'high', file: added.file, @@ -33,7 +33,7 @@ function detectPipeToShell(added) { } return [ { - kind: 'dockerfile_pipe_to_shell', + kind: 'capability_echo.dockerfile_pipe_to_shell', surface: 'container', severity: 'critical', file: added.file, diff --git a/dist/detectors/js-capability.js b/dist/detectors/js-capability.js index 95fb5e4..e71ec6a 100644 --- a/dist/detectors/js-capability.js +++ b/dist/detectors/js-capability.js @@ -53,7 +53,7 @@ function detectFetch(added, testFile) { } return [ { - kind: 'external_fetch_added', + kind: 'capability_echo.external_fetch_added', surface: 'source', severity: testFile ? 'low' : 'medium', file: added.file, @@ -71,7 +71,7 @@ function detectSecretExfil(added, testFile, secretVariables) { } return [ { - kind: 'source_secret_exfil_pattern', + kind: 'capability_echo.source_secret_exfil_pattern', surface: 'source', severity: testFile ? 'medium' : 'high', file: added.file, @@ -101,7 +101,7 @@ function detectSubprocess(added, testFile) { } return [ { - kind: 'subprocess_spawn_added', + kind: 'capability_echo.subprocess_spawn_added', surface: 'source', severity: testFile ? 'low' : 'high', file: added.file, @@ -118,7 +118,7 @@ function detectDynamicEval(added, testFile) { } return [ { - kind: 'dynamic_eval_added', + kind: 'capability_echo.dynamic_eval_added', surface: 'source', severity: testFile ? 'medium' : 'critical', file: added.file, diff --git a/dist/detectors/package-deps.js b/dist/detectors/package-deps.js index 3028a43..5647947 100644 --- a/dist/detectors/package-deps.js +++ b/dist/detectors/package-deps.js @@ -50,7 +50,7 @@ function compareDeps(file, oldText, newText) { } if (HIGH_CAPABILITY_DEPS.has(name)) { findings.push({ - kind: 'high_capability_dep_added', + kind: 'capability_echo.high_capability_dep_added', surface: 'package', severity: 'high', file, @@ -63,7 +63,7 @@ function compareDeps(file, oldText, newText) { } if (TELEMETRY_DEPS.has(name)) { findings.push({ - kind: 'telemetry_dep_added', + kind: 'capability_echo.telemetry_dep_added', surface: 'package', severity: 'medium', file, diff --git a/dist/detectors/package-scripts.js b/dist/detectors/package-scripts.js index e052a93..98d93b3 100644 --- a/dist/detectors/package-scripts.js +++ b/dist/detectors/package-scripts.js @@ -67,7 +67,7 @@ function compareScripts(file, oldScripts, newScripts, newText) { } const line = lineOfJsonKey(newText, key) ?? lineOfJsonStringValue(newText, newValue); findings.push({ - kind: 'lifecycle_script_added', + kind: 'capability_echo.lifecycle_script_added', surface: 'package', severity: 'high', file, @@ -95,7 +95,7 @@ function analyzeScriptContent(file, key, script, newText) { const line = lineOfJsonStringValue(newText, script) ?? lineOfJsonKey(newText, key); if (/(?:curl[^\n|]*\|\s*(?:ba)?sh|wget[^\n|]*\|\s*sh|Invoke-Expression|iex\s*\()/i.test(script)) { findings.push({ - kind: 'script_pipe_to_shell', + kind: 'capability_echo.script_pipe_to_shell', surface: 'package', severity: 'critical', file, @@ -107,7 +107,7 @@ function analyzeScriptContent(file, key, script, newText) { } if (/\b(curl|wget|npm publish)\b/i.test(script) || /\bnpx\b(?![^\s]*@\d+\.\d+\.\d+)/i.test(script)) { findings.push({ - kind: 'script_network_command', + kind: 'capability_echo.script_network_command', surface: 'package', severity: 'medium', file, diff --git a/dist/detectors/py-capability.js b/dist/detectors/py-capability.js index 2e96e4a..c00cac4 100644 --- a/dist/detectors/py-capability.js +++ b/dist/detectors/py-capability.js @@ -63,7 +63,7 @@ function detectPyNetwork(added, testFile) { } return [ { - kind: 'external_fetch_added', + kind: 'capability_echo.external_fetch_added', surface: 'source', severity: testFile ? 'low' : 'medium', file: added.file, @@ -81,7 +81,7 @@ function detectPySecretExfil(added, testFile, secretVariables) { } return [ { - kind: 'source_secret_exfil_pattern', + kind: 'capability_echo.source_secret_exfil_pattern', surface: 'source', severity: testFile ? 'medium' : 'high', file: added.file, @@ -115,7 +115,7 @@ function detectPySubprocess(added, testFile) { } return [ { - kind: 'subprocess_spawn_added', + kind: 'capability_echo.subprocess_spawn_added', surface: 'source', severity: testFile ? 'low' : 'high', file: added.file, @@ -136,7 +136,7 @@ function detectPyDynamicExec(added, testFile) { } return [ { - kind: 'dynamic_eval_added', + kind: 'capability_echo.dynamic_eval_added', surface: 'source', severity: testFile ? 'medium' : 'critical', file: added.file, @@ -157,7 +157,7 @@ function detectPyUnsafeDeserialize(added, testFile) { } return [ { - kind: 'unsafe_deserialize_added', + kind: 'capability_echo.unsafe_deserialize_added', surface: 'source', severity: testFile ? 'medium' : 'critical', file: added.file, diff --git a/dist/detectors/shell-capability.js b/dist/detectors/shell-capability.js index 85815b7..ca87d13 100644 --- a/dist/detectors/shell-capability.js +++ b/dist/detectors/shell-capability.js @@ -16,7 +16,7 @@ function detectPipeToShell(added) { } return [ { - kind: 'shell_pipe_to_shell', + kind: 'capability_echo.shell_pipe_to_shell', surface: 'source', severity: 'critical', file: added.file, @@ -33,7 +33,7 @@ function detectExternalDownload(added) { } return [ { - kind: 'shell_external_download', + kind: 'capability_echo.shell_external_download', surface: 'source', severity: 'medium', file: added.file, diff --git a/dist/detectors/workflow-permissions.js b/dist/detectors/workflow-permissions.js index b7fd808..5dd4039 100644 --- a/dist/detectors/workflow-permissions.js +++ b/dist/detectors/workflow-permissions.js @@ -60,7 +60,7 @@ function detectWritePermissions(added) { if (githubTokenWritePermissionPattern.test(content)) { return [ { - kind: 'workflow_permission_write', + kind: 'capability_echo.workflow_permission_write', surface: 'workflow', severity: 'high', file: added.file, @@ -74,7 +74,7 @@ function detectWritePermissions(added) { if (/^\s*permissions\s*:\s*(?:write|write-all|admin)\b/i.test(content)) { return [ { - kind: 'workflow_permission_write', + kind: 'capability_echo.workflow_permission_write', surface: 'workflow', severity: 'high', file: added.file, @@ -93,7 +93,7 @@ function detectPullRequestTarget(added) { } return [ { - kind: 'workflow_pull_request_target', + kind: 'capability_echo.workflow_pull_request_target', surface: 'workflow', severity: 'high', file: added.file, @@ -110,7 +110,7 @@ function detectPullRequestHeadCheckoutOnTarget(added, hasPullRequestTarget) { } return [ { - kind: 'workflow_pr_head_checkout_on_target', + kind: 'capability_echo.workflow_pr_head_checkout_on_target', surface: 'workflow', severity: 'high', file: added.file, @@ -137,7 +137,7 @@ function detectSelfHostedRunner(added) { } return [ { - kind: 'workflow_self_hosted_runner', + kind: 'capability_echo.workflow_self_hosted_runner', surface: 'workflow', severity: 'high', file: added.file, @@ -163,7 +163,7 @@ function detectMutableActionRef(added) { } return [ { - kind: 'workflow_mutable_action_ref', + kind: 'capability_echo.workflow_mutable_action_ref', surface: 'workflow', severity: 'medium', file: added.file, @@ -189,7 +189,7 @@ function detectExternalCurl(added) { } return [ { - kind: 'workflow_external_curl', + kind: 'capability_echo.workflow_external_curl', surface: 'workflow', severity: 'medium', file: added.file, @@ -206,7 +206,7 @@ function detectSecretsInherit(added) { } return [ { - kind: 'workflow_secrets_inherit', + kind: 'capability_echo.workflow_secrets_inherit', surface: 'workflow', severity: 'high', file: added.file, @@ -228,7 +228,7 @@ function detectSecretExfil(added, secretEnvVars) { } return [ { - kind: 'workflow_secret_exfil_pattern', + kind: 'capability_echo.workflow_secret_exfil_pattern', surface: 'workflow', severity: 'high', file: added.file, @@ -251,7 +251,7 @@ function detectDockerHostControl(added) { const content = added.content; if (/\/var\/run\/docker\.sock(?::\/var\/run\/docker\.sock)?/i.test(content)) { findings.push({ - kind: 'workflow_docker_socket_mount', + kind: 'capability_echo.workflow_docker_socket_mount', surface: 'workflow', severity: 'critical', file: added.file, @@ -263,7 +263,7 @@ function detectDockerHostControl(added) { } if (/\bdocker\s+run\b.*\s--privileged(?:\s|$)/i.test(content)) { findings.push({ - kind: 'workflow_privileged_container', + kind: 'capability_echo.workflow_privileged_container', surface: 'workflow', severity: 'high', file: added.file, diff --git a/dist/report.js b/dist/report.js index 178d17a..34ebd5c 100644 --- a/dist/report.js +++ b/dist/report.js @@ -14,30 +14,30 @@ const severityRank = { critical: 4 }; const SUMMARY_LABELS = { - external_fetch_added: 'external network fetch calls', - source_secret_exfil_pattern: 'source secret exfiltration patterns', - subprocess_spawn_added: 'subprocess or shell spawn calls', - dynamic_eval_added: 'dynamic code execution', - shell_pipe_to_shell: 'shell pipe-to-shell downloads', - shell_external_download: 'shell external downloads', - dockerfile_remote_add: 'Dockerfile remote ADD instructions', - dockerfile_pipe_to_shell: 'Dockerfile pipe-to-shell builds', - workflow_permission_write: 'GitHub Actions write permissions', - workflow_pull_request_target: 'GitHub Actions pull_request_target triggers', - workflow_pr_head_checkout_on_target: 'GitHub Actions PR-head checkout under pull_request_target', - workflow_self_hosted_runner: 'GitHub Actions self-hosted runners', - workflow_mutable_action_ref: 'GitHub Actions mutable action references', - workflow_secrets_inherit: 'GitHub Actions inherited secrets', - workflow_external_curl: 'workflow external network requests', - workflow_secret_exfil_pattern: 'workflow secret exfiltration patterns', - workflow_docker_socket_mount: 'workflow Docker socket mounts', - workflow_privileged_container: 'workflow privileged containers', - lifecycle_script_added: 'npm lifecycle scripts', - script_pipe_to_shell: 'pipe-to-shell install scripts', - script_network_command: 'network or publish npm scripts', - high_capability_dep_added: 'high-capability dependency additions', - telemetry_dep_added: 'telemetry dependency additions', - unsafe_deserialize_added: 'unsafe deserialization' + 'capability_echo.external_fetch_added': 'external network fetch calls', + 'capability_echo.source_secret_exfil_pattern': 'source secret exfiltration patterns', + 'capability_echo.subprocess_spawn_added': 'subprocess or shell spawn calls', + 'capability_echo.dynamic_eval_added': 'dynamic code execution', + 'capability_echo.shell_pipe_to_shell': 'shell pipe-to-shell downloads', + 'capability_echo.shell_external_download': 'shell external downloads', + 'capability_echo.dockerfile_remote_add': 'Dockerfile remote ADD instructions', + 'capability_echo.dockerfile_pipe_to_shell': 'Dockerfile pipe-to-shell builds', + 'capability_echo.workflow_permission_write': 'GitHub Actions write permissions', + 'capability_echo.workflow_pull_request_target': 'GitHub Actions pull_request_target triggers', + 'capability_echo.workflow_pr_head_checkout_on_target': 'GitHub Actions PR-head checkout under pull_request_target', + 'capability_echo.workflow_self_hosted_runner': 'GitHub Actions self-hosted runners', + 'capability_echo.workflow_mutable_action_ref': 'GitHub Actions mutable action references', + 'capability_echo.workflow_secrets_inherit': 'GitHub Actions inherited secrets', + 'capability_echo.workflow_external_curl': 'workflow external network requests', + 'capability_echo.workflow_secret_exfil_pattern': 'workflow secret exfiltration patterns', + 'capability_echo.workflow_docker_socket_mount': 'workflow Docker socket mounts', + 'capability_echo.workflow_privileged_container': 'workflow privileged containers', + 'capability_echo.lifecycle_script_added': 'npm lifecycle scripts', + 'capability_echo.script_pipe_to_shell': 'pipe-to-shell install scripts', + 'capability_echo.script_network_command': 'network or publish npm scripts', + 'capability_echo.high_capability_dep_added': 'high-capability dependency additions', + 'capability_echo.telemetry_dep_added': 'telemetry dependency additions', + 'capability_echo.unsafe_deserialize_added': 'unsafe deserialization' }; export function createReport(findings, context) { return { diff --git a/package-lock.json b/package-lock.json index 0544865..a2e5d58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "agent-gov-core": "github:Conalh/agent-gov-core#v0.1.2" + "agent-gov-core": "github:Conalh/agent-gov-core#v0.2.0" }, "bin": { "capabilityecho": "dist/index.js" diff --git a/package.json b/package.json index 7694493..d43a852 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test": "node --test test/*.test.mjs" }, "dependencies": { - "agent-gov-core": "github:Conalh/agent-gov-core#v0.1.2" + "agent-gov-core": "github:Conalh/agent-gov-core#v0.2.0" }, "devDependencies": { "@types/node": "^24.0.0", diff --git a/src/detectors/dockerfile-capability.ts b/src/detectors/dockerfile-capability.ts index 062553d..f6936bd 100644 --- a/src/detectors/dockerfile-capability.ts +++ b/src/detectors/dockerfile-capability.ts @@ -23,7 +23,7 @@ function detectRemoteAdd(added: AddedLine): Finding[] { return [ { - kind: 'dockerfile_remote_add', + kind: 'capability_echo.dockerfile_remote_add', surface: 'container', severity: 'high', file: added.file, @@ -42,7 +42,7 @@ function detectPipeToShell(added: AddedLine): Finding[] { return [ { - kind: 'dockerfile_pipe_to_shell', + kind: 'capability_echo.dockerfile_pipe_to_shell', surface: 'container', severity: 'critical', file: added.file, diff --git a/src/detectors/js-capability.ts b/src/detectors/js-capability.ts index 17549af..6b346b0 100644 --- a/src/detectors/js-capability.ts +++ b/src/detectors/js-capability.ts @@ -71,7 +71,7 @@ function detectFetch(added: AddedLine, testFile: boolean): Finding[] { return [ { - kind: 'external_fetch_added', + kind: 'capability_echo.external_fetch_added', surface: 'source', severity: testFile ? 'low' : 'medium', file: added.file, @@ -93,7 +93,7 @@ function detectSecretExfil(added: AddedLine, testFile: boolean, secretVariables: return [ { - kind: 'source_secret_exfil_pattern', + kind: 'capability_echo.source_secret_exfil_pattern', surface: 'source', severity: testFile ? 'medium' : 'high', file: added.file, @@ -133,7 +133,7 @@ function detectSubprocess(added: AddedLine, testFile: boolean): Finding[] { return [ { - kind: 'subprocess_spawn_added', + kind: 'capability_echo.subprocess_spawn_added', surface: 'source', severity: testFile ? 'low' : 'high', file: added.file, @@ -152,7 +152,7 @@ function detectDynamicEval(added: AddedLine, testFile: boolean): Finding[] { return [ { - kind: 'dynamic_eval_added', + kind: 'capability_echo.dynamic_eval_added', surface: 'source', severity: testFile ? 'medium' : 'critical', file: added.file, diff --git a/src/detectors/package-deps.ts b/src/detectors/package-deps.ts index 9825bd1..c1929db 100644 --- a/src/detectors/package-deps.ts +++ b/src/detectors/package-deps.ts @@ -65,7 +65,7 @@ function compareDeps(file: string, oldText: string, newText: string): Finding[] if (HIGH_CAPABILITY_DEPS.has(name)) { findings.push({ - kind: 'high_capability_dep_added', + kind: 'capability_echo.high_capability_dep_added', surface: 'package', severity: 'high', file, @@ -79,7 +79,7 @@ function compareDeps(file: string, oldText: string, newText: string): Finding[] if (TELEMETRY_DEPS.has(name)) { findings.push({ - kind: 'telemetry_dep_added', + kind: 'capability_echo.telemetry_dep_added', surface: 'package', severity: 'medium', file, diff --git a/src/detectors/package-scripts.ts b/src/detectors/package-scripts.ts index 4c7a7a0..6e8da0f 100644 --- a/src/detectors/package-scripts.ts +++ b/src/detectors/package-scripts.ts @@ -95,7 +95,7 @@ function compareScripts( const line = lineOfJsonKey(newText, key) ?? lineOfJsonStringValue(newText, newValue); findings.push({ - kind: 'lifecycle_script_added', + kind: 'capability_echo.lifecycle_script_added', surface: 'package', severity: 'high', file, @@ -130,7 +130,7 @@ function analyzeScriptContent(file: string, key: string, script: string, newText if (/(?:curl[^\n|]*\|\s*(?:ba)?sh|wget[^\n|]*\|\s*sh|Invoke-Expression|iex\s*\()/i.test(script)) { findings.push({ - kind: 'script_pipe_to_shell', + kind: 'capability_echo.script_pipe_to_shell', surface: 'package', severity: 'critical', file, @@ -143,7 +143,7 @@ function analyzeScriptContent(file: string, key: string, script: string, newText if (/\b(curl|wget|npm publish)\b/i.test(script) || /\bnpx\b(?![^\s]*@\d+\.\d+\.\d+)/i.test(script)) { findings.push({ - kind: 'script_network_command', + kind: 'capability_echo.script_network_command', surface: 'package', severity: 'medium', file, diff --git a/src/detectors/py-capability.ts b/src/detectors/py-capability.ts index 84658a5..2bb71eb 100644 --- a/src/detectors/py-capability.ts +++ b/src/detectors/py-capability.ts @@ -81,7 +81,7 @@ function detectPyNetwork(added: AddedLine, testFile: boolean): Finding[] { return [ { - kind: 'external_fetch_added', + kind: 'capability_echo.external_fetch_added', surface: 'source', severity: testFile ? 'low' : 'medium', file: added.file, @@ -103,7 +103,7 @@ function detectPySecretExfil(added: AddedLine, testFile: boolean, secretVariable return [ { - kind: 'source_secret_exfil_pattern', + kind: 'capability_echo.source_secret_exfil_pattern', surface: 'source', severity: testFile ? 'medium' : 'high', file: added.file, @@ -148,7 +148,7 @@ function detectPySubprocess(added: AddedLine, testFile: boolean): Finding[] { return [ { - kind: 'subprocess_spawn_added', + kind: 'capability_echo.subprocess_spawn_added', surface: 'source', severity: testFile ? 'low' : 'high', file: added.file, @@ -172,7 +172,7 @@ function detectPyDynamicExec(added: AddedLine, testFile: boolean): Finding[] { return [ { - kind: 'dynamic_eval_added', + kind: 'capability_echo.dynamic_eval_added', surface: 'source', severity: testFile ? 'medium' : 'critical', file: added.file, @@ -196,7 +196,7 @@ function detectPyUnsafeDeserialize(added: AddedLine, testFile: boolean): Finding return [ { - kind: 'unsafe_deserialize_added', + kind: 'capability_echo.unsafe_deserialize_added', surface: 'source', severity: testFile ? 'medium' : 'critical', file: added.file, diff --git a/src/detectors/shell-capability.ts b/src/detectors/shell-capability.ts index eb2246a..41e18d5 100644 --- a/src/detectors/shell-capability.ts +++ b/src/detectors/shell-capability.ts @@ -23,7 +23,7 @@ function detectPipeToShell(added: AddedLine): Finding[] { return [ { - kind: 'shell_pipe_to_shell', + kind: 'capability_echo.shell_pipe_to_shell', surface: 'source', severity: 'critical', file: added.file, @@ -42,7 +42,7 @@ function detectExternalDownload(added: AddedLine): Finding[] { return [ { - kind: 'shell_external_download', + kind: 'capability_echo.shell_external_download', surface: 'source', severity: 'medium', file: added.file, diff --git a/src/detectors/workflow-permissions.ts b/src/detectors/workflow-permissions.ts index 06f7829..bd94efb 100644 --- a/src/detectors/workflow-permissions.ts +++ b/src/detectors/workflow-permissions.ts @@ -78,7 +78,7 @@ function detectWritePermissions(added: AddedLine): Finding[] { if (githubTokenWritePermissionPattern.test(content)) { return [ { - kind: 'workflow_permission_write', + kind: 'capability_echo.workflow_permission_write', surface: 'workflow', severity: 'high', file: added.file, @@ -93,7 +93,7 @@ function detectWritePermissions(added: AddedLine): Finding[] { if (/^\s*permissions\s*:\s*(?:write|write-all|admin)\b/i.test(content)) { return [ { - kind: 'workflow_permission_write', + kind: 'capability_echo.workflow_permission_write', surface: 'workflow', severity: 'high', file: added.file, @@ -115,7 +115,7 @@ function detectPullRequestTarget(added: AddedLine): Finding[] { return [ { - kind: 'workflow_pull_request_target', + kind: 'capability_echo.workflow_pull_request_target', surface: 'workflow', severity: 'high', file: added.file, @@ -134,7 +134,7 @@ function detectPullRequestHeadCheckoutOnTarget(added: AddedLine, hasPullRequestT return [ { - kind: 'workflow_pr_head_checkout_on_target', + kind: 'capability_echo.workflow_pr_head_checkout_on_target', surface: 'workflow', severity: 'high', file: added.file, @@ -168,7 +168,7 @@ function detectSelfHostedRunner(added: AddedLine): Finding[] { return [ { - kind: 'workflow_self_hosted_runner', + kind: 'capability_echo.workflow_self_hosted_runner', surface: 'workflow', severity: 'high', file: added.file, @@ -198,7 +198,7 @@ function detectMutableActionRef(added: AddedLine): Finding[] { return [ { - kind: 'workflow_mutable_action_ref', + kind: 'capability_echo.workflow_mutable_action_ref', surface: 'workflow', severity: 'medium', file: added.file, @@ -229,7 +229,7 @@ function detectExternalCurl(added: AddedLine): Finding[] { return [ { - kind: 'workflow_external_curl', + kind: 'capability_echo.workflow_external_curl', surface: 'workflow', severity: 'medium', file: added.file, @@ -248,7 +248,7 @@ function detectSecretsInherit(added: AddedLine): Finding[] { return [ { - kind: 'workflow_secrets_inherit', + kind: 'capability_echo.workflow_secrets_inherit', surface: 'workflow', severity: 'high', file: added.file, @@ -274,7 +274,7 @@ function detectSecretExfil(added: AddedLine, secretEnvVars: Set): Findin return [ { - kind: 'workflow_secret_exfil_pattern', + kind: 'capability_echo.workflow_secret_exfil_pattern', surface: 'workflow', severity: 'high', file: added.file, @@ -301,7 +301,7 @@ function detectDockerHostControl(added: AddedLine): Finding[] { if (/\/var\/run\/docker\.sock(?::\/var\/run\/docker\.sock)?/i.test(content)) { findings.push({ - kind: 'workflow_docker_socket_mount', + kind: 'capability_echo.workflow_docker_socket_mount', surface: 'workflow', severity: 'critical', file: added.file, @@ -314,7 +314,7 @@ function detectDockerHostControl(added: AddedLine): Finding[] { if (/\bdocker\s+run\b.*\s--privileged(?:\s|$)/i.test(content)) { findings.push({ - kind: 'workflow_privileged_container', + kind: 'capability_echo.workflow_privileged_container', surface: 'workflow', severity: 'high', file: added.file, diff --git a/src/report.ts b/src/report.ts index 437c824..2df8742 100644 --- a/src/report.ts +++ b/src/report.ts @@ -35,30 +35,30 @@ const severityRank: Record = { }; const SUMMARY_LABELS: Record = { - external_fetch_added: 'external network fetch calls', - source_secret_exfil_pattern: 'source secret exfiltration patterns', - subprocess_spawn_added: 'subprocess or shell spawn calls', - dynamic_eval_added: 'dynamic code execution', - shell_pipe_to_shell: 'shell pipe-to-shell downloads', - shell_external_download: 'shell external downloads', - dockerfile_remote_add: 'Dockerfile remote ADD instructions', - dockerfile_pipe_to_shell: 'Dockerfile pipe-to-shell builds', - workflow_permission_write: 'GitHub Actions write permissions', - workflow_pull_request_target: 'GitHub Actions pull_request_target triggers', - workflow_pr_head_checkout_on_target: 'GitHub Actions PR-head checkout under pull_request_target', - workflow_self_hosted_runner: 'GitHub Actions self-hosted runners', - workflow_mutable_action_ref: 'GitHub Actions mutable action references', - workflow_secrets_inherit: 'GitHub Actions inherited secrets', - workflow_external_curl: 'workflow external network requests', - workflow_secret_exfil_pattern: 'workflow secret exfiltration patterns', - workflow_docker_socket_mount: 'workflow Docker socket mounts', - workflow_privileged_container: 'workflow privileged containers', - lifecycle_script_added: 'npm lifecycle scripts', - script_pipe_to_shell: 'pipe-to-shell install scripts', - script_network_command: 'network or publish npm scripts', - high_capability_dep_added: 'high-capability dependency additions', - telemetry_dep_added: 'telemetry dependency additions', - unsafe_deserialize_added: 'unsafe deserialization' + 'capability_echo.external_fetch_added': 'external network fetch calls', + 'capability_echo.source_secret_exfil_pattern': 'source secret exfiltration patterns', + 'capability_echo.subprocess_spawn_added': 'subprocess or shell spawn calls', + 'capability_echo.dynamic_eval_added': 'dynamic code execution', + 'capability_echo.shell_pipe_to_shell': 'shell pipe-to-shell downloads', + 'capability_echo.shell_external_download': 'shell external downloads', + 'capability_echo.dockerfile_remote_add': 'Dockerfile remote ADD instructions', + 'capability_echo.dockerfile_pipe_to_shell': 'Dockerfile pipe-to-shell builds', + 'capability_echo.workflow_permission_write': 'GitHub Actions write permissions', + 'capability_echo.workflow_pull_request_target': 'GitHub Actions pull_request_target triggers', + 'capability_echo.workflow_pr_head_checkout_on_target': 'GitHub Actions PR-head checkout under pull_request_target', + 'capability_echo.workflow_self_hosted_runner': 'GitHub Actions self-hosted runners', + 'capability_echo.workflow_mutable_action_ref': 'GitHub Actions mutable action references', + 'capability_echo.workflow_secrets_inherit': 'GitHub Actions inherited secrets', + 'capability_echo.workflow_external_curl': 'workflow external network requests', + 'capability_echo.workflow_secret_exfil_pattern': 'workflow secret exfiltration patterns', + 'capability_echo.workflow_docker_socket_mount': 'workflow Docker socket mounts', + 'capability_echo.workflow_privileged_container': 'workflow privileged containers', + 'capability_echo.lifecycle_script_added': 'npm lifecycle scripts', + 'capability_echo.script_pipe_to_shell': 'pipe-to-shell install scripts', + 'capability_echo.script_network_command': 'network or publish npm scripts', + 'capability_echo.high_capability_dep_added': 'high-capability dependency additions', + 'capability_echo.telemetry_dep_added': 'telemetry dependency additions', + 'capability_echo.unsafe_deserialize_added': 'unsafe deserialization' }; export function createReport(findings: Finding[], context: DiffContext): EchoReport { diff --git a/test/cli-output.test.mjs b/test/cli-output.test.mjs index 13b5e59..b0957d0 100644 --- a/test/cli-output.test.mjs +++ b/test/cli-output.test.mjs @@ -24,10 +24,10 @@ test('CLI emits JSON capability drift report', async () => { assert.ok(report.findingCount >= 5); assert.ok(report.changedFileCount >= 3); assert.ok(report.capabilitySummary.length >= 4); - assert.ok(report.findings.some((finding) => finding.kind === 'external_fetch_added')); - assert.ok(report.findings.some((finding) => finding.kind === 'lifecycle_script_added')); - assert.ok(report.findings.some((finding) => finding.kind === 'script_pipe_to_shell')); - assert.ok(report.findings.some((finding) => finding.kind === 'workflow_permission_write')); + assert.ok(report.findings.some((finding) => finding.kind === 'capability_echo.external_fetch_added')); + assert.ok(report.findings.some((finding) => finding.kind === 'capability_echo.lifecycle_script_added')); + assert.ok(report.findings.some((finding) => finding.kind === 'capability_echo.script_pipe_to_shell')); + assert.ok(report.findings.some((finding) => finding.kind === 'capability_echo.workflow_permission_write')); assert.deepEqual(report.surfaceSummary, { source: 1, package: 3, workflow: 2, container: 0 }); assert.deepEqual(report.severitySummary, { critical: 1, high: 2, medium: 3, low: 0 }); assert.deepEqual(report.topRecommendations, [ diff --git a/test/detectors.test.mjs b/test/detectors.test.mjs index 88a6634..003a364 100644 --- a/test/detectors.test.mjs +++ b/test/detectors.test.mjs @@ -14,7 +14,7 @@ test('js detector flags external fetch', () => { ]); assert.equal(findings.length, 1); - assert.equal(findings[0].kind, 'external_fetch_added'); + assert.equal(findings[0].kind, 'capability_echo.external_fetch_added'); }); test('js detector flags env secret exfiltration over external fetch', () => { @@ -27,8 +27,8 @@ test('js detector flags env secret exfiltration over external fetch', () => { } ]); - assert.ok(findings.some((finding) => finding.kind === 'external_fetch_added')); - const exfilFinding = findings.find((finding) => finding.kind === 'source_secret_exfil_pattern'); + assert.ok(findings.some((finding) => finding.kind === 'capability_echo.external_fetch_added')); + const exfilFinding = findings.find((finding) => finding.kind === 'capability_echo.source_secret_exfil_pattern'); assert.ok(exfilFinding); assert.equal(exfilFinding.surface, 'source'); assert.equal(exfilFinding.severity, 'high'); @@ -44,7 +44,7 @@ test('js detector downgrades test file subprocess findings', () => { ]); assert.equal(findings.length, 1); - assert.equal(findings[0].kind, 'subprocess_spawn_added'); + assert.equal(findings[0].kind, 'capability_echo.subprocess_spawn_added'); assert.equal(findings[0].severity, 'low'); }); @@ -58,7 +58,7 @@ test('workflow detector flags write permissions', () => { ]); assert.equal(findings.length, 1); - assert.equal(findings[0].kind, 'workflow_permission_write'); + assert.equal(findings[0].kind, 'capability_echo.workflow_permission_write'); }); test('workflow detector flags broader token write scopes', () => { @@ -76,7 +76,7 @@ test('workflow detector flags broader token write scopes', () => { ]); assert.equal(findings.length, 2); - assert.ok(findings.every((finding) => finding.kind === 'workflow_permission_write')); + assert.ok(findings.every((finding) => finding.kind === 'capability_echo.workflow_permission_write')); assert.ok(findings.every((finding) => finding.severity === 'high')); }); @@ -90,7 +90,7 @@ test('workflow detector flags secret exfil pattern', () => { ]); assert.equal(findings.length, 2); - assert.ok(findings.some((finding) => finding.kind === 'workflow_secret_exfil_pattern')); + assert.ok(findings.some((finding) => finding.kind === 'capability_echo.workflow_secret_exfil_pattern')); }); test('workflow detector flags secret-backed env vars used in external requests', () => { @@ -107,8 +107,8 @@ test('workflow detector flags secret-backed env vars used in external requests', } ]); - assert.ok(findings.some((finding) => finding.kind === 'workflow_external_curl')); - const exfilFinding = findings.find((finding) => finding.kind === 'workflow_secret_exfil_pattern'); + assert.ok(findings.some((finding) => finding.kind === 'capability_echo.workflow_external_curl')); + const exfilFinding = findings.find((finding) => finding.kind === 'capability_echo.workflow_secret_exfil_pattern'); assert.ok(exfilFinding); assert.equal(exfilFinding.line, 21); assert.equal(exfilFinding.severity, 'high'); @@ -124,7 +124,7 @@ test('workflow detector flags inherited reusable workflow secrets', () => { ]); assert.equal(findings.length, 1); - assert.equal(findings[0].kind, 'workflow_secrets_inherit'); + assert.equal(findings[0].kind, 'capability_echo.workflow_secrets_inherit'); assert.equal(findings[0].surface, 'workflow'); assert.equal(findings[0].severity, 'high'); assert.match(findings[0].recommendation, /explicit/); @@ -140,7 +140,7 @@ test('workflow detector flags Docker socket mounts', () => { ]); assert.equal(findings.length, 1); - assert.equal(findings[0].kind, 'workflow_docker_socket_mount'); + assert.equal(findings[0].kind, 'capability_echo.workflow_docker_socket_mount'); assert.equal(findings[0].severity, 'critical'); }); @@ -154,7 +154,7 @@ test('workflow detector flags privileged containers', () => { ]); assert.equal(findings.length, 1); - assert.equal(findings[0].kind, 'workflow_privileged_container'); + assert.equal(findings[0].kind, 'capability_echo.workflow_privileged_container'); assert.equal(findings[0].severity, 'high'); }); @@ -168,7 +168,7 @@ test('workflow detector flags pull_request_target triggers', () => { ]); assert.equal(findings.length, 1); - assert.equal(findings[0].kind, 'workflow_pull_request_target'); + assert.equal(findings[0].kind, 'capability_echo.workflow_pull_request_target'); assert.equal(findings[0].severity, 'high'); assert.match(findings[0].recommendation, /pull_request/); }); @@ -187,7 +187,7 @@ test('workflow detector flags pull_request_target workflows that check out PR he } ]); - const checkoutFinding = findings.find((finding) => finding.kind === 'workflow_pr_head_checkout_on_target'); + const checkoutFinding = findings.find((finding) => finding.kind === 'capability_echo.workflow_pr_head_checkout_on_target'); assert.ok(checkoutFinding); assert.equal(checkoutFinding.severity, 'high'); assert.equal(checkoutFinding.line, 21); @@ -216,7 +216,7 @@ test('workflow detector flags self-hosted runners', () => { ]); assert.equal(findings.length, 1); - assert.equal(findings[0].kind, 'workflow_self_hosted_runner'); + assert.equal(findings[0].kind, 'capability_echo.workflow_self_hosted_runner'); assert.equal(findings[0].severity, 'high'); assert.match(findings[0].message, /self-hosted/); }); @@ -241,7 +241,7 @@ test('workflow detector flags multiline self-hosted runner labels', () => { ]); assert.equal(findings.length, 1); - assert.equal(findings[0].kind, 'workflow_self_hosted_runner'); + assert.equal(findings[0].kind, 'capability_echo.workflow_self_hosted_runner'); assert.equal(findings[0].line, 13); }); @@ -255,7 +255,7 @@ test('workflow detector flags mutable third-party action refs', () => { ]); assert.equal(findings.length, 1); - assert.equal(findings[0].kind, 'workflow_mutable_action_ref'); + assert.equal(findings[0].kind, 'capability_echo.workflow_mutable_action_ref'); assert.equal(findings[0].surface, 'workflow'); assert.equal(findings[0].severity, 'medium'); assert.match(findings[0].recommendation, /commit SHA/); @@ -282,7 +282,7 @@ test('report summarizes mutable workflow action refs with a human label', () => const report = createReport( [ { - kind: 'source_secret_exfil_pattern', + kind: 'capability_echo.source_secret_exfil_pattern', surface: 'source', severity: 'high', file: 'src/api/sync.ts', @@ -292,7 +292,7 @@ test('report summarizes mutable workflow action refs with a human label', () => recommendation: 'Do not send env secrets to external services unless the endpoint and payload are explicitly required.' }, { - kind: 'workflow_mutable_action_ref', + kind: 'capability_echo.workflow_mutable_action_ref', surface: 'workflow', severity: 'medium', file: '.github/workflows/agent.yml', @@ -302,7 +302,7 @@ test('report summarizes mutable workflow action refs with a human label', () => recommendation: 'Pin third-party actions to a reviewed commit SHA before merge.' }, { - kind: 'workflow_pr_head_checkout_on_target', + kind: 'capability_echo.workflow_pr_head_checkout_on_target', surface: 'workflow', severity: 'high', file: '.github/workflows/agent.yml', @@ -312,7 +312,7 @@ test('report summarizes mutable workflow action refs with a human label', () => recommendation: 'Use pull_request for untrusted PR code, or avoid checking out PR head code under pull_request_target.' }, { - kind: 'workflow_secrets_inherit', + kind: 'capability_echo.workflow_secrets_inherit', surface: 'workflow', severity: 'high', file: '.github/workflows/deploy.yml', diff --git a/test/dockerfile-capability.test.mjs b/test/dockerfile-capability.test.mjs index ab6bf9e..39f73cf 100644 --- a/test/dockerfile-capability.test.mjs +++ b/test/dockerfile-capability.test.mjs @@ -18,7 +18,7 @@ test('dockerfile: remote ADD is high capability drift', () => { line('Dockerfile', 'ADD https://install.example.com/agent /usr/local/bin/agent') ]); - const finding = findings.find((item) => item.kind === 'dockerfile_remote_add'); + const finding = findings.find((item) => item.kind === 'capability_echo.dockerfile_remote_add'); assert.ok(finding); assert.equal(finding.surface, 'container'); assert.equal(finding.severity, 'high'); @@ -29,7 +29,7 @@ test('dockerfile: curl piped to shell is critical capability drift', () => { line('docker/Dockerfile.release', 'RUN curl https://install.example.com/setup.sh | bash') ]); - const finding = findings.find((item) => item.kind === 'dockerfile_pipe_to_shell'); + const finding = findings.find((item) => item.kind === 'capability_echo.dockerfile_pipe_to_shell'); assert.ok(finding); assert.equal(finding.surface, 'container'); assert.equal(finding.severity, 'critical'); diff --git a/test/git-diff.test.mjs b/test/git-diff.test.mjs index 25b5155..7a269ed 100644 --- a/test/git-diff.test.mjs +++ b/test/git-diff.test.mjs @@ -79,9 +79,9 @@ test('CLI diffs capability drift between git refs without agent config changes', const report = JSON.parse(stdout); assert.equal(report.rating, 'critical'); - assert.ok(report.findings.some((finding) => finding.kind === 'external_fetch_added')); - assert.ok(report.findings.some((finding) => finding.kind === 'workflow_permission_write')); - assert.ok(report.findings.some((finding) => finding.kind === 'lifecycle_script_added')); + assert.ok(report.findings.some((finding) => finding.kind === 'capability_echo.external_fetch_added')); + assert.ok(report.findings.some((finding) => finding.kind === 'capability_echo.workflow_permission_write')); + assert.ok(report.findings.some((finding) => finding.kind === 'capability_echo.lifecycle_script_added')); } finally { await rm(repo, { recursive: true, force: true }); } @@ -235,8 +235,8 @@ test('CLI detects package capability drift from compared git refs, not the curre assert.equal(report.changedFileCount, 1); assert.deepEqual(report.scannedSurfaces, ['package']); - assert.ok(report.findings.some((finding) => finding.kind === 'lifecycle_script_added')); - assert.ok(report.findings.some((finding) => finding.kind === 'high_capability_dep_added')); + assert.ok(report.findings.some((finding) => finding.kind === 'capability_echo.lifecycle_script_added')); + assert.ok(report.findings.some((finding) => finding.kind === 'capability_echo.high_capability_dep_added')); assert.ok(report.findings.every((finding) => finding.file === 'tools/agent/package.json')); } finally { await rm(repo, { recursive: true, force: true }); @@ -307,7 +307,7 @@ test('CLI flags PR-head checkout added to an existing pull_request_target workfl ); const report = JSON.parse(stdout); - const finding = report.findings.find((item) => item.kind === 'workflow_pr_head_checkout_on_target'); + const finding = report.findings.find((item) => item.kind === 'capability_echo.workflow_pr_head_checkout_on_target'); assert.ok(finding); assert.equal(finding.file, '.github/workflows/ci.yml'); assert.equal(finding.line, 12); @@ -378,7 +378,7 @@ test('CLI flags external requests using existing workflow secret env vars', asyn ); const report = JSON.parse(stdout); - const finding = report.findings.find((item) => item.kind === 'workflow_secret_exfil_pattern'); + const finding = report.findings.find((item) => item.kind === 'capability_echo.workflow_secret_exfil_pattern'); assert.ok(finding); assert.equal(finding.file, '.github/workflows/ci.yml'); assert.equal(finding.line, 10); @@ -457,7 +457,7 @@ test('CLI flags external fetches using existing source env secret variables', as ); const report = JSON.parse(stdout); - const finding = report.findings.find((item) => item.kind === 'source_secret_exfil_pattern'); + const finding = report.findings.find((item) => item.kind === 'capability_echo.source_secret_exfil_pattern'); assert.ok(finding); assert.equal(finding.file, 'src/client.ts'); assert.equal(finding.line, 4); @@ -548,7 +548,7 @@ test('CLI flags Python external requests using existing source env secret variab ); const report = JSON.parse(stdout); - const finding = report.findings.find((item) => item.kind === 'source_secret_exfil_pattern'); + const finding = report.findings.find((item) => item.kind === 'capability_echo.source_secret_exfil_pattern'); assert.ok(finding); assert.equal(finding.file, 'src/agent.py'); assert.equal(finding.line, 6); diff --git a/test/package-deps.test.mjs b/test/package-deps.test.mjs index 71d14e1..30cddc2 100644 --- a/test/package-deps.test.mjs +++ b/test/package-deps.test.mjs @@ -23,7 +23,7 @@ test('flags newly added high-capability dep (puppeteer)', async () => { ); try { const findings = await detectPackageDeps({ mode: 'directories', oldRoot: fixture.oldRoot, newRoot: fixture.newRoot }); - const f = findings.find((finding) => finding.kind === 'high_capability_dep_added'); + const f = findings.find((finding) => finding.kind === 'capability_echo.high_capability_dep_added'); assert.ok(f); assert.equal(f.subject, 'puppeteer'); assert.equal(f.severity, 'high'); @@ -39,7 +39,7 @@ test('does not flag pre-existing deps', async () => { ); try { const findings = await detectPackageDeps({ mode: 'directories', oldRoot: fixture.oldRoot, newRoot: fixture.newRoot }); - assert.equal(findings.find((f) => f.kind === 'high_capability_dep_added'), undefined); + assert.equal(findings.find((f) => f.kind === 'capability_echo.high_capability_dep_added'), undefined); } finally { await rm(fixture.root, { recursive: true, force: true }); } @@ -52,7 +52,7 @@ test('flags telemetry dep at medium severity', async () => { ); try { const findings = await detectPackageDeps({ mode: 'directories', oldRoot: fixture.oldRoot, newRoot: fixture.newRoot }); - const f = findings.find((finding) => finding.kind === 'telemetry_dep_added'); + const f = findings.find((finding) => finding.kind === 'capability_echo.telemetry_dep_added'); assert.ok(f); assert.equal(f.severity, 'medium'); } finally { diff --git a/test/py-capability.test.mjs b/test/py-capability.test.mjs index 404195b..f29bedd 100644 --- a/test/py-capability.test.mjs +++ b/test/py-capability.test.mjs @@ -10,7 +10,7 @@ test('py: requests.get with literal URL flags external fetch', () => { const findings = detectPyCapability([ line('agent.py', 'resp = requests.get("https://api.example.com/v1/things")') ]); - assert.ok(findings.find((f) => f.kind === 'external_fetch_added')); + assert.ok(findings.find((f) => f.kind === 'capability_echo.external_fetch_added')); }); test('py: external request with env secret flags source secret exfiltration', () => { @@ -21,8 +21,8 @@ test('py: external request with env secret flags source secret exfiltration', () ) ]); - assert.ok(findings.find((f) => f.kind === 'external_fetch_added')); - const f = findings.find((finding) => finding.kind === 'source_secret_exfil_pattern'); + assert.ok(findings.find((f) => f.kind === 'capability_echo.external_fetch_added')); + const f = findings.find((finding) => finding.kind === 'capability_echo.source_secret_exfil_pattern'); assert.ok(f); assert.equal(f.severity, 'high'); assert.equal(f.surface, 'source'); @@ -32,21 +32,21 @@ test('py: requests.get without literal URL does not over-fire', () => { const findings = detectPyCapability([ line('agent.py', 'resp = requests.get(url, headers=h)') ]); - assert.equal(findings.find((f) => f.kind === 'external_fetch_added'), undefined); + assert.equal(findings.find((f) => f.kind === 'capability_echo.external_fetch_added'), undefined); }); test('py: urllib.request.urlopen counts as external fetch', () => { const findings = detectPyCapability([ line('agent.py', 'from urllib.request import urlopen; r = urlopen("https://x.example/y")') ]); - assert.ok(findings.find((f) => f.kind === 'external_fetch_added')); + assert.ok(findings.find((f) => f.kind === 'capability_echo.external_fetch_added')); }); test('py: subprocess.Popen flags as high', () => { const findings = detectPyCapability([ line('agent.py', 'subprocess.Popen(["ls", "-la"], shell=False)') ]); - const f = findings.find((finding) => finding.kind === 'subprocess_spawn_added'); + const f = findings.find((finding) => finding.kind === 'capability_echo.subprocess_spawn_added'); assert.ok(f); assert.equal(f.severity, 'high'); }); @@ -55,14 +55,14 @@ test('py: os.system flags subprocess spawn', () => { const findings = detectPyCapability([ line('agent.py', 'os.system("rm -rf /tmp/cache")') ]); - assert.ok(findings.find((f) => f.kind === 'subprocess_spawn_added')); + assert.ok(findings.find((f) => f.kind === 'capability_echo.subprocess_spawn_added')); }); test('py: eval() flags as critical dynamic exec', () => { const findings = detectPyCapability([ line('agent.py', 'result = eval(user_input)') ]); - const f = findings.find((finding) => finding.kind === 'dynamic_eval_added'); + const f = findings.find((finding) => finding.kind === 'capability_echo.dynamic_eval_added'); assert.ok(f); assert.equal(f.severity, 'critical'); }); @@ -71,14 +71,14 @@ test('py: importlib.import_module flags as dynamic exec', () => { const findings = detectPyCapability([ line('agent.py', 'mod = importlib.import_module(name)') ]); - assert.ok(findings.find((f) => f.kind === 'dynamic_eval_added')); + assert.ok(findings.find((f) => f.kind === 'capability_echo.dynamic_eval_added')); }); test('py: pickle.loads flags as unsafe deserialize', () => { const findings = detectPyCapability([ line('agent.py', 'obj = pickle.loads(payload)') ]); - const f = findings.find((finding) => finding.kind === 'unsafe_deserialize_added'); + const f = findings.find((finding) => finding.kind === 'capability_echo.unsafe_deserialize_added'); assert.ok(f); assert.equal(f.severity, 'critical'); }); @@ -87,12 +87,12 @@ test('py: yaml.load without SafeLoader flags; yaml.safe_load does not', () => { const unsafe = detectPyCapability([ line('agent.py', 'config = yaml.load(open("config.yml"))') ]); - assert.ok(unsafe.find((f) => f.kind === 'unsafe_deserialize_added')); + assert.ok(unsafe.find((f) => f.kind === 'capability_echo.unsafe_deserialize_added')); const safe = detectPyCapability([ line('agent.py', 'config = yaml.safe_load(open("config.yml"))') ]); - assert.equal(safe.find((f) => f.kind === 'unsafe_deserialize_added'), undefined); + assert.equal(safe.find((f) => f.kind === 'capability_echo.unsafe_deserialize_added'), undefined); }); test('py: comment lines are ignored', () => { @@ -106,7 +106,7 @@ test('py: test file downgrades severity', () => { const findings = detectPyCapability([ line('tests/test_agent.py', 'subprocess.Popen(["echo", "test"])') ]); - const f = findings.find((finding) => finding.kind === 'subprocess_spawn_added'); + const f = findings.find((finding) => finding.kind === 'capability_echo.subprocess_spawn_added'); assert.ok(f); assert.equal(f.severity, 'low'); }); diff --git a/test/shell-capability.test.mjs b/test/shell-capability.test.mjs index 74e0cf2..23c2f1b 100644 --- a/test/shell-capability.test.mjs +++ b/test/shell-capability.test.mjs @@ -12,7 +12,7 @@ test('shell: curl piped to bash is critical capability drift', () => { line('scripts/bootstrap.sh', 'curl https://install.example.com/agent.sh | bash') ]); - const finding = findings.find((item) => item.kind === 'shell_pipe_to_shell'); + const finding = findings.find((item) => item.kind === 'capability_echo.shell_pipe_to_shell'); assert.ok(finding); assert.equal(finding.severity, 'critical'); assert.equal(finding.surface, 'source'); @@ -28,7 +28,7 @@ test('shell: literal external download is medium capability drift', () => { line('scripts/fetch-model.sh', 'wget https://models.example.com/latest.bin -O model.bin') ]); - const finding = findings.find((item) => item.kind === 'shell_external_download'); + const finding = findings.find((item) => item.kind === 'capability_echo.shell_external_download'); assert.ok(finding); assert.equal(finding.severity, 'medium'); }); diff --git a/test/workflow.test.mjs b/test/workflow.test.mjs index 4661f8c..6ab7b75 100644 --- a/test/workflow.test.mjs +++ b/test/workflow.test.mjs @@ -147,7 +147,7 @@ test('JavaScript action entrypoint emits outputs, summary, and GitHub annotation assert.equal(jsonReport.rating, 'critical'); assert.equal(jsonReport.findingCount, 4); assert.deepEqual(jsonReport.surfaceSummary, { source: 1, package: 3, workflow: 0, container: 0 }); - assert.ok(jsonReport.findings.some((finding) => finding.kind === 'external_fetch_added')); + assert.ok(jsonReport.findings.some((finding) => finding.kind === 'capability_echo.external_fetch_added')); assert.match(summary, /# CapabilityEcho capability drift: CRITICAL/); assert.match(summary, /## Top recommendations/); assert.match(stdout, /::warning file=src\/client\.ts,line=2/);