codegate-ai 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +109 -40
  2. package/dist/cli.js +85 -7
  3. package/dist/commands/scan-command.d.ts +6 -1
  4. package/dist/commands/scan-command.js +1 -1
  5. package/dist/commands/scan-content-command.d.ts +16 -0
  6. package/dist/commands/scan-content-command.js +65 -0
  7. package/dist/config/inline-ignore.d.ts +11 -0
  8. package/dist/config/inline-ignore.js +57 -0
  9. package/dist/config/suppression-policy.d.ts +22 -0
  10. package/dist/config/suppression-policy.js +146 -0
  11. package/dist/config.d.ts +23 -0
  12. package/dist/config.js +188 -3
  13. package/dist/layer2-static/action/parser.d.ts +3 -0
  14. package/dist/layer2-static/action/parser.js +165 -0
  15. package/dist/layer2-static/action/types.d.ts +39 -0
  16. package/dist/layer2-static/action/types.js +1 -0
  17. package/dist/layer2-static/advisories/agent-components.json +62 -0
  18. package/dist/layer2-static/advisories/cache.d.ts +6 -0
  19. package/dist/layer2-static/advisories/cache.js +44 -0
  20. package/dist/layer2-static/advisories/cache.ts +62 -0
  21. package/dist/layer2-static/advisories/gha-advisory-client.d.ts +7 -0
  22. package/dist/layer2-static/advisories/gha-advisory-client.js +4 -0
  23. package/dist/layer2-static/advisories/gha-advisory-client.ts +13 -0
  24. package/dist/layer2-static/advisories/gha-known-vulnerable-actions.json +4 -0
  25. package/dist/layer2-static/audits/registry.d.ts +3 -0
  26. package/dist/layer2-static/audits/registry.js +29 -0
  27. package/dist/layer2-static/audits/types.d.ts +12 -0
  28. package/dist/layer2-static/audits/types.js +1 -0
  29. package/dist/layer2-static/dependabot/parser.d.ts +3 -0
  30. package/dist/layer2-static/dependabot/parser.js +189 -0
  31. package/dist/layer2-static/dependabot/types.d.ts +50 -0
  32. package/dist/layer2-static/dependabot/types.js +1 -0
  33. package/dist/layer2-static/detectors/advisory-intelligence.d.ts +7 -0
  34. package/dist/layer2-static/detectors/advisory-intelligence.js +170 -0
  35. package/dist/layer2-static/detectors/command-exec.js +6 -0
  36. package/dist/layer2-static/detectors/dependabot-cooldown.d.ts +7 -0
  37. package/dist/layer2-static/detectors/dependabot-cooldown.js +54 -0
  38. package/dist/layer2-static/detectors/dependabot-execution.d.ts +7 -0
  39. package/dist/layer2-static/detectors/dependabot-execution.js +68 -0
  40. package/dist/layer2-static/detectors/rule-file.js +5 -0
  41. package/dist/layer2-static/detectors/workflow-anonymous-definition.d.ts +7 -0
  42. package/dist/layer2-static/detectors/workflow-anonymous-definition.js +48 -0
  43. package/dist/layer2-static/detectors/workflow-archived-uses.d.ts +7 -0
  44. package/dist/layer2-static/detectors/workflow-archived-uses.js +147 -0
  45. package/dist/layer2-static/detectors/workflow-artipacked.d.ts +7 -0
  46. package/dist/layer2-static/detectors/workflow-artipacked.js +62 -0
  47. package/dist/layer2-static/detectors/workflow-bot-conditions.d.ts +7 -0
  48. package/dist/layer2-static/detectors/workflow-bot-conditions.js +108 -0
  49. package/dist/layer2-static/detectors/workflow-cache-poisoning.d.ts +7 -0
  50. package/dist/layer2-static/detectors/workflow-cache-poisoning.js +67 -0
  51. package/dist/layer2-static/detectors/workflow-concurrency-limits.d.ts +7 -0
  52. package/dist/layer2-static/detectors/workflow-concurrency-limits.js +76 -0
  53. package/dist/layer2-static/detectors/workflow-dangerous-triggers.d.ts +6 -0
  54. package/dist/layer2-static/detectors/workflow-dangerous-triggers.js +38 -0
  55. package/dist/layer2-static/detectors/workflow-excessive-permissions.d.ts +6 -0
  56. package/dist/layer2-static/detectors/workflow-excessive-permissions.js +65 -0
  57. package/dist/layer2-static/detectors/workflow-forbidden-uses.d.ts +12 -0
  58. package/dist/layer2-static/detectors/workflow-forbidden-uses.js +148 -0
  59. package/dist/layer2-static/detectors/workflow-github-env.d.ts +7 -0
  60. package/dist/layer2-static/detectors/workflow-github-env.js +49 -0
  61. package/dist/layer2-static/detectors/workflow-hardcoded-container-credentials.d.ts +7 -0
  62. package/dist/layer2-static/detectors/workflow-hardcoded-container-credentials.js +90 -0
  63. package/dist/layer2-static/detectors/workflow-impostor-commit.d.ts +9 -0
  64. package/dist/layer2-static/detectors/workflow-impostor-commit.js +127 -0
  65. package/dist/layer2-static/detectors/workflow-insecure-commands.d.ts +7 -0
  66. package/dist/layer2-static/detectors/workflow-insecure-commands.js +72 -0
  67. package/dist/layer2-static/detectors/workflow-known-vuln-action.d.ts +8 -0
  68. package/dist/layer2-static/detectors/workflow-known-vuln-action.js +70 -0
  69. package/dist/layer2-static/detectors/workflow-misfeature.d.ts +7 -0
  70. package/dist/layer2-static/detectors/workflow-misfeature.js +101 -0
  71. package/dist/layer2-static/detectors/workflow-obfuscation.d.ts +7 -0
  72. package/dist/layer2-static/detectors/workflow-obfuscation.js +79 -0
  73. package/dist/layer2-static/detectors/workflow-overprovisioned-secrets.d.ts +7 -0
  74. package/dist/layer2-static/detectors/workflow-overprovisioned-secrets.js +81 -0
  75. package/dist/layer2-static/detectors/workflow-ref-confusion.d.ts +7 -0
  76. package/dist/layer2-static/detectors/workflow-ref-confusion.js +69 -0
  77. package/dist/layer2-static/detectors/workflow-ref-version-mismatch.d.ts +9 -0
  78. package/dist/layer2-static/detectors/workflow-ref-version-mismatch.js +179 -0
  79. package/dist/layer2-static/detectors/workflow-secrets-inherit.d.ts +7 -0
  80. package/dist/layer2-static/detectors/workflow-secrets-inherit.js +59 -0
  81. package/dist/layer2-static/detectors/workflow-secrets-outside-env.d.ts +7 -0
  82. package/dist/layer2-static/detectors/workflow-secrets-outside-env.js +99 -0
  83. package/dist/layer2-static/detectors/workflow-self-hosted-runner.d.ts +7 -0
  84. package/dist/layer2-static/detectors/workflow-self-hosted-runner.js +67 -0
  85. package/dist/layer2-static/detectors/workflow-stale-action-refs.d.ts +7 -0
  86. package/dist/layer2-static/detectors/workflow-stale-action-refs.js +178 -0
  87. package/dist/layer2-static/detectors/workflow-superfluous-actions.d.ts +7 -0
  88. package/dist/layer2-static/detectors/workflow-superfluous-actions.js +70 -0
  89. package/dist/layer2-static/detectors/workflow-template-injection.d.ts +6 -0
  90. package/dist/layer2-static/detectors/workflow-template-injection.js +94 -0
  91. package/dist/layer2-static/detectors/workflow-undocumented-permissions.d.ts +7 -0
  92. package/dist/layer2-static/detectors/workflow-undocumented-permissions.js +101 -0
  93. package/dist/layer2-static/detectors/workflow-unpinned-images.d.ts +7 -0
  94. package/dist/layer2-static/detectors/workflow-unpinned-images.js +108 -0
  95. package/dist/layer2-static/detectors/workflow-unpinned-uses.d.ts +7 -0
  96. package/dist/layer2-static/detectors/workflow-unpinned-uses.js +60 -0
  97. package/dist/layer2-static/detectors/workflow-unredacted-secrets.d.ts +7 -0
  98. package/dist/layer2-static/detectors/workflow-unredacted-secrets.js +92 -0
  99. package/dist/layer2-static/detectors/workflow-unsound-condition.d.ts +7 -0
  100. package/dist/layer2-static/detectors/workflow-unsound-condition.js +90 -0
  101. package/dist/layer2-static/detectors/workflow-unsound-contains.d.ts +7 -0
  102. package/dist/layer2-static/detectors/workflow-unsound-contains.js +88 -0
  103. package/dist/layer2-static/detectors/workflow-use-trusted-publishing.d.ts +7 -0
  104. package/dist/layer2-static/detectors/workflow-use-trusted-publishing.js +66 -0
  105. package/dist/layer2-static/engine.d.ts +13 -2
  106. package/dist/layer2-static/engine.js +578 -41
  107. package/dist/layer2-static/github/cache.d.ts +8 -0
  108. package/dist/layer2-static/github/cache.js +41 -0
  109. package/dist/layer2-static/github/client.d.ts +17 -0
  110. package/dist/layer2-static/github/client.js +43 -0
  111. package/dist/layer2-static/rule-engine.d.ts +1 -1
  112. package/dist/layer2-static/rule-engine.js +1 -13
  113. package/dist/layer2-static/rule-pack-loader.d.ts +10 -0
  114. package/dist/layer2-static/rule-pack-loader.js +187 -0
  115. package/dist/layer2-static/workflow/injection-sinks.json +5 -0
  116. package/dist/layer2-static/workflow/parser.d.ts +3 -0
  117. package/dist/layer2-static/workflow/parser.js +88 -0
  118. package/dist/layer2-static/workflow/types.d.ts +15 -0
  119. package/dist/layer2-static/workflow/types.js +1 -0
  120. package/dist/layer3-dynamic/toxic-flow.js +6 -0
  121. package/dist/pipeline.d.ts +1 -1
  122. package/dist/pipeline.js +11 -10
  123. package/dist/report/finding-fingerprint.d.ts +5 -0
  124. package/dist/report/finding-fingerprint.js +47 -0
  125. package/dist/reporter/markdown.js +25 -3
  126. package/dist/reporter/sarif.js +25 -5
  127. package/dist/reporter/terminal.js +37 -0
  128. package/dist/scan-target/fetch-plan.d.ts +8 -0
  129. package/dist/scan-target/fetch-plan.js +30 -0
  130. package/dist/scan-target/staging.js +60 -5
  131. package/dist/scan.d.ts +7 -1
  132. package/dist/scan.js +157 -40
  133. package/dist/types/finding.d.ts +10 -1
  134. package/dist/types/finding.js +5 -0
  135. package/package.json +1 -1
package/README.md CHANGED
@@ -108,6 +108,7 @@ See the [Configuration](#configuration) section for full settings and examples.
108
108
  | Command | Purpose |
109
109
  | ------------------------ | ---------------------------------------------------------------------- |
110
110
  | `codegate scan [target]` | Scan a directory, file, or URL target for AI tool config risks. |
111
+ | `codegate scan-content` | Scan inline JSON, YAML, TOML, Markdown, or text content. |
111
112
  | `codegate run <tool>` | Scan current directory, then launch selected AI tool if policy allows. |
112
113
  | `codegate skills [...]` | Wrap `npx skills` and preflight-scan `skills add` targets. |
113
114
  | `codegate clawhub [...]` | Wrap `npx clawhub` and preflight-scan `clawhub install` targets. |
@@ -119,21 +120,26 @@ See the [Configuration](#configuration) section for full settings and examples.
119
120
 
120
121
  ## `scan` Command Flags
121
122
 
122
- | Flag | Purpose |
123
- | ---------------------- | --------------------------------------------------------------------------------------------------------- |
124
- | `--deep` | Enable Layer 3 dynamic analysis. |
125
- | `--remediate` | Enter remediation mode after scan. |
126
- | `--fix-safe` | Auto-fix unambiguous critical findings. |
127
- | `--dry-run` | Show proposed fixes but write nothing. |
128
- | `--patch` | Generate a patch file for review workflows. |
129
- | `--no-tui` | Disable TUI and interactive prompts. |
130
- | `--format <type>` | Output format: `terminal`, `json`, `sarif`, `markdown`, `html`. |
131
- | `--output <path>` | Write report to file instead of stdout. |
132
- | `--verbose` | Show extended output in terminal format. |
133
- | `--config <path>` | Use a specific global config file path. |
134
- | `--force` | Skip interactive confirmations. |
135
- | `--include-user-scope` | Force-enable user/home AI tool config paths for this run (useful if config disables user-scope scanning). |
136
- | `--reset-state` | Clear persisted scan-state history and exit. |
123
+ | Flag | Purpose |
124
+ | ----------------------- | --------------------------------------------------------------------------------------------------------- |
125
+ | `--deep` | Enable Layer 3 dynamic analysis. |
126
+ | `--remediate` | Enter remediation mode after scan. |
127
+ | `--fix-safe` | Auto-fix unambiguous critical findings. |
128
+ | `--dry-run` | Show proposed fixes but write nothing. |
129
+ | `--patch` | Generate a patch file for review workflows. |
130
+ | `--no-tui` | Disable TUI and interactive prompts. |
131
+ | `--format <type>` | Output format: `terminal`, `json`, `sarif`, `markdown`, `html`. |
132
+ | `--output <path>` | Write report to file instead of stdout. |
133
+ | `--verbose` | Show extended output in terminal format. |
134
+ | `--config <path>` | Use a specific global config file path. |
135
+ | `--force` | Skip interactive confirmations. |
136
+ | `--include-user-scope` | Force-enable user/home AI tool config paths for this run (useful if config disables user-scope scanning). |
137
+ | `--collect <mode>` | Collection scope mode (`default`, `project`, `user`, `explicit`, `all`). Repeatable. |
138
+ | `--strict-collection` | Treat parse failures in collected inputs as high-severity findings. |
139
+ | `--persona <type>` | Audit sensitivity (`regular`, `pedantic`, `auditor`). |
140
+ | `--runtime-mode <mode>` | Runtime mode for optional online audits (`offline`, `online`, `online-no-audits`). |
141
+ | `--workflow-audits` | Enable CI/CD audit pack for GitHub workflow, action, and Dependabot inputs. |
142
+ | `--reset-state` | Clear persisted scan-state history and exit. |
137
143
 
138
144
  Examples:
139
145
 
@@ -147,9 +153,59 @@ codegate scan . --deep --force
147
153
  codegate scan . --remediate
148
154
  codegate scan . --fix-safe
149
155
  codegate scan . --remediate --dry-run --patch
156
+ codegate scan . --workflow-audits --collect project --persona auditor --runtime-mode online
157
+ codegate scan . --workflow-audits --strict-collection
150
158
  codegate scan . --reset-state
151
159
  ```
152
160
 
161
+ ## Workflow Audit Pack
162
+
163
+ CodeGate can audit GitHub Actions workflows when `--workflow-audits` is enabled.
164
+
165
+ Current checks include:
166
+
167
+ - Unpinned external action references (`uses: owner/repo@tag` instead of commit SHA)
168
+ - High-risk triggers (`pull_request_target`, `workflow_run`)
169
+ - Overly broad permissions (`write-all` and explicit write grants)
170
+ - Template expression injection patterns in run steps and known sink inputs
171
+ - Known vulnerable action references (online runtime mode)
172
+ - Dependabot cooldown and execution-risk checks
173
+ - Workflow hygiene checks (concurrency gates, obfuscation, unsafe conditional trust)
174
+
175
+ Track the current workflow-audit coverage and backlog in the [workflow audit parity checklist](docs/workflow-audit-parity-checklist.md).
176
+ Real public validation fixtures and source provenance are documented in [workflow audit real-case corpus](docs/workflow-audit-real-cases.md).
177
+
178
+ Examples:
179
+
180
+ ```bash
181
+ codegate scan . --workflow-audits
182
+ codegate scan . --workflow-audits --collect project --persona auditor
183
+ codegate scan . --workflow-audits --runtime-mode online
184
+ codegate scan . --workflow-audits --collect-kind dependabot
185
+ ```
186
+
187
+ ## `scan-content` Command
188
+
189
+ `codegate scan-content <content...>` scans inline content directly from the command line. It is useful when you want to inspect JSON, YAML, TOML, Markdown, or plain text before writing it to disk or installing it into a tool configuration.
190
+
191
+ Use `--type` to declare the content format:
192
+
193
+ | Type | Purpose |
194
+ | ---------- | -------------------------------------------------------------------- |
195
+ | `json` | Parse JSON input and run the static scanner on the parsed structure. |
196
+ | `yaml` | Parse YAML input and run the static scanner on the parsed structure. |
197
+ | `toml` | Parse TOML input and run the static scanner on the parsed structure. |
198
+ | `markdown` | Analyze Markdown instruction text as a rule surface. |
199
+ | `text` | Analyze plain text as a rule surface. |
200
+
201
+ Examples:
202
+
203
+ ```bash
204
+ codegate scan-content '{"mcpServers":{"bad":{"command":"bash"}}}' --type json
205
+ codegate scan-content '# Suspicious instructions' --type markdown
206
+ codegate scan-content 'echo hello' --type text
207
+ ```
208
+
153
209
  ## `run` Command
154
210
 
155
211
  `codegate run <tool>` runs scan-first wrapper mode.
@@ -199,6 +255,7 @@ Behavior:
199
255
  - Dangerous findings block execution (fail-closed).
200
256
  - Warning-level findings can still require confirmation unless `--cg-force` is provided.
201
257
  - Non-install subcommands (for example `skills find` or `clawhub search`) are passed through without preflight scanning.
258
+ - Wrapper scans honor the same config policy controls as `codegate scan`, including `suppress_findings`, `suppression_rules`, `rule_pack_paths`, `allowed_rules`, and `skip_rules`.
202
259
 
203
260
  Wrapper flags (consumed by CodeGate, not forwarded):
204
261
 
@@ -299,33 +356,38 @@ codegate init
299
356
  - List values are merged and de-duplicated across levels.
300
357
  - `trusted_directories` is global-only; project config cannot set it.
301
358
  - `blocked_commands` is merged with defaults; defaults are always retained.
359
+ - `rule_pack_paths`, `allowed_rules`, `skip_rules`, `suppress_findings`, and `suppression_rules` merge across global and project config.
302
360
 
303
361
  ### Full Configuration Reference
304
362
 
305
- | Key | Type | Allowed Values | Default |
306
- | -------------------------------- | ---------------- | --------------------------------------------------------------------------- | -------------------------------------------------- |
307
- | `severity_threshold` | string | `critical`, `high`, `medium`, `low`, `info` | `high` |
308
- | `auto_proceed_below_threshold` | boolean | `true`, `false` | `true` |
309
- | `output_format` | string | `terminal`, `json`, `sarif`, `markdown`, `html` | `terminal` |
310
- | `scan_state_path` | string | file path | `~/.codegate/scan-state.json` |
311
- | `scan_user_scope` | boolean | `true`, `false` | `true` |
312
- | `tui.enabled` | boolean | `true`, `false` | `true` |
313
- | `tui.colour_scheme` | string | free string (currently `default`) | `default` |
314
- | `tui.compact_mode` | boolean | `true`, `false` | `false` |
315
- | `tool_discovery.preferred_agent` | string | practical values: `claude`, `claude-code`, `codex`, `codex-cli`, `opencode` | `claude` |
316
- | `tool_discovery.agent_paths` | object | map of agent key -> binary path | `{}` |
317
- | `tool_discovery.skip_tools` | array of strings | tool keys to skip in discovery/selection | `[]` |
318
- | `trusted_directories` | array of strings | directory paths | `[]` |
319
- | `blocked_commands` | array of strings | command names | `["bash","sh","curl","wget","nc","python","node"]` |
320
- | `known_safe_mcp_servers` | array of strings | package/server identifiers | prefilled |
321
- | `known_safe_formatters` | array of strings | formatter names | prefilled |
322
- | `known_safe_lsp_servers` | array of strings | lsp server names | prefilled |
323
- | `known_safe_hooks` | array of strings | relative hook paths such as `.git/hooks/pre-commit` | `[]` |
324
- | `unicode_analysis` | boolean | `true`, `false` | `true` |
325
- | `check_ide_settings` | boolean | `true`, `false` | `true` |
326
- | `owasp_mapping` | boolean | `true`, `false` | `true` |
327
- | `trusted_api_domains` | array of strings | domain names | `[]` |
328
- | `suppress_findings` | array of strings | finding IDs/fingerprints | `[]` |
363
+ | Key | Type | Allowed Values | Default |
364
+ | -------------------------------- | ---------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------- |
365
+ | `severity_threshold` | string | `critical`, `high`, `medium`, `low`, `info` | `high` |
366
+ | `auto_proceed_below_threshold` | boolean | `true`, `false` | `true` |
367
+ | `output_format` | string | `terminal`, `json`, `sarif`, `markdown`, `html` | `terminal` |
368
+ | `scan_state_path` | string | file path | `~/.codegate/scan-state.json` |
369
+ | `scan_user_scope` | boolean | `true`, `false` | `true` |
370
+ | `tui.enabled` | boolean | `true`, `false` | `true` |
371
+ | `tui.colour_scheme` | string | free string (currently `default`) | `default` |
372
+ | `tui.compact_mode` | boolean | `true`, `false` | `false` |
373
+ | `tool_discovery.preferred_agent` | string | practical values: `claude`, `claude-code`, `codex`, `codex-cli`, `opencode` | `claude` |
374
+ | `tool_discovery.agent_paths` | object | map of agent key -> binary path | `{}` |
375
+ | `tool_discovery.skip_tools` | array of strings | tool keys to skip in discovery/selection | `[]` |
376
+ | `trusted_directories` | array of strings | directory paths | `[]` |
377
+ | `blocked_commands` | array of strings | command names | `["bash","sh","curl","wget","nc","python","node"]` |
378
+ | `known_safe_mcp_servers` | array of strings | package/server identifiers | prefilled |
379
+ | `known_safe_formatters` | array of strings | formatter names | prefilled |
380
+ | `known_safe_lsp_servers` | array of strings | lsp server names | prefilled |
381
+ | `known_safe_hooks` | array of strings | relative hook paths such as `.git/hooks/pre-commit` | `[]` |
382
+ | `unicode_analysis` | boolean | `true`, `false` | `true` |
383
+ | `check_ide_settings` | boolean | `true`, `false` | `true` |
384
+ | `owasp_mapping` | boolean | `true`, `false` | `true` |
385
+ | `trusted_api_domains` | array of strings | domain names | `[]` |
386
+ | `suppress_findings` | array of strings | finding IDs/fingerprints | `[]` |
387
+ | `suppression_rules` | array of objects | rule match objects with `rule_id`, `file_path`, `severity`, `category`, `cwe`, `fingerprint` | `[]` |
388
+ | `rule_pack_paths` | array of strings | extra rule pack files or directories | `[]` |
389
+ | `allowed_rules` | array of strings | rule IDs to keep after loading | `[]` |
390
+ | `skip_rules` | array of strings | rule IDs to drop after loading | `[]` |
329
391
 
330
392
  ### Default Config Example
331
393
 
@@ -359,7 +421,11 @@ codegate init
359
421
  "check_ide_settings": true,
360
422
  "owasp_mapping": true,
361
423
  "trusted_api_domains": [],
362
- "suppress_findings": []
424
+ "suppress_findings": [],
425
+ "suppression_rules": [],
426
+ "rule_pack_paths": [],
427
+ "allowed_rules": [],
428
+ "skip_rules": []
363
429
  }
364
430
  ```
365
431
 
@@ -371,6 +437,9 @@ Configuration notes:
371
437
  - `unicode_analysis=false` disables hidden-unicode findings in Layer 2 rule-file scanning and Layer 3 tool-description scanning. Other rule-file heuristics remain enabled.
372
438
  - `check_ide_settings=false` disables `IDE_SETTINGS` findings.
373
439
  - `owasp_mapping=false` keeps detection behavior unchanged and emits empty `owasp` arrays in reports.
440
+ - `suppression_rules` applies all listed criteria with AND semantics. If a criterion is omitted, it is ignored.
441
+ - `rule_pack_paths` can point to extra JSON rule-pack files or directories of JSON rule packs.
442
+ - `allowed_rules` and `skip_rules` control which loaded rule IDs remain active after rule-pack loading.
374
443
 
375
444
  ## Output Formats
376
445
 
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { dirname, resolve } from "node:path";
6
6
  import { createInterface } from "node:readline/promises";
7
7
  import { pathToFileURL } from "node:url";
8
8
  import { Command, Option } from "commander";
9
- import { DEFAULT_CONFIG, OUTPUT_FORMATS, resolveEffectiveConfig, } from "./config.js";
9
+ import { DEFAULT_CONFIG, OUTPUT_FORMATS, PERSONAS, RUNTIME_MODES, SCAN_COLLECTION_MODES, SCAN_COLLECTION_KINDS, resolveEffectiveConfig, } from "./config.js";
10
10
  import { APP_NAME } from "./index.js";
11
11
  import { runSandboxCommand } from "./layer3-dynamic/sandbox.js";
12
12
  import { loadKnowledgeBase } from "./layer1-discovery/knowledge-base.js";
@@ -18,6 +18,7 @@ import { executeWrapperRun } from "./wrapper.js";
18
18
  import { runRemediation as runRemediationWorkflow, } from "./layer4-remediation/remediation-runner.js";
19
19
  import { undoLatestSession } from "./commands/undo.js";
20
20
  import { executeScanCommand } from "./commands/scan-command.js";
21
+ import { executeScanContentCommand, SCAN_CONTENT_TYPES, } from "./commands/scan-content-command.js";
21
22
  import { executeSkillsWrapper, launchSkillsPassthrough, } from "./commands/skills-wrapper.js";
22
23
  import { executeClawhubWrapper, launchClawhubPassthrough, } from "./commands/clawhub-wrapper.js";
23
24
  import { promptDeepAgentSelection, promptDeepScanConsent, promptMetaAgentCommandConsent, promptRemediationConsent, promptSkillSelection, } from "./cli-prompts.js";
@@ -126,6 +127,8 @@ const defaultCliDeps = {
126
127
  }),
127
128
  prepareScanDiscovery: (scanTarget, config, options) => createScanDiscoveryContext(scanTarget, undefined, {
128
129
  includeUserScope: config?.scan_user_scope === true,
130
+ collectModes: config?.scan_collection_modes,
131
+ collectKinds: config?.scan_collection_kinds,
129
132
  parseSelected: true,
130
133
  explicitCandidates: options?.explicitCandidates,
131
134
  }),
@@ -157,6 +160,8 @@ const defaultCliDeps = {
157
160
  ? discoverDeepScanResourcesFromContext(discoveryContext)
158
161
  : discoverDeepScanResources(scanTarget, undefined, {
159
162
  includeUserScope: config?.scan_user_scope === true,
163
+ collectModes: config?.scan_collection_modes,
164
+ collectKinds: config?.scan_collection_kinds,
160
165
  }),
161
166
  discoverLocalTextTargets: (_scanTarget, _config, discoveryContext) => discoveryContext ? discoverLocalTextAnalysisTargetsFromContext(discoveryContext) : [],
162
167
  // Deep resource execution never makes outbound network calls.
@@ -197,6 +202,20 @@ function addScanCommand(program, version, deps) {
197
202
  .option("--config <path>", "use a specific global config file")
198
203
  .option("--force", "skip interactive confirmations")
199
204
  .option("--include-user-scope", "include user/home AI tool config paths in scan")
205
+ .addOption(new Option("--collect <mode>", "collection mode (repeatable): default, project, user, explicit, all")
206
+ .choices([...SCAN_COLLECTION_MODES])
207
+ .argParser((value, previous = []) => [...previous, value]))
208
+ .addOption(new Option("--collect-kind <kind>", "collection kind (repeatable): workflows, actions, dependabot")
209
+ .choices([...SCAN_COLLECTION_KINDS])
210
+ .argParser((value, previous = []) => [...previous, value]))
211
+ .option("--strict-collection", "treat parse failures in collected inputs as high severity")
212
+ .addOption(new Option("--persona <type>", "audit sensitivity persona")
213
+ .choices([...PERSONAS])
214
+ .argParser((value) => value))
215
+ .addOption(new Option("--runtime-mode <mode>", "runtime network mode for optional online audits")
216
+ .choices([...RUNTIME_MODES])
217
+ .argParser((value) => value))
218
+ .option("--workflow-audits", "enable workflow security audit pack for .github/workflows")
200
219
  .option("--skill <name>", "select one skill directory when scanning a skills index repo URL")
201
220
  .option("--reset-state", "clear persisted scan-state history and exit")
202
221
  .addHelpText("after", renderExampleHelp([
@@ -204,6 +223,7 @@ function addScanCommand(program, version, deps) {
204
223
  "codegate scan ./skills/security-review/SKILL.md",
205
224
  "codegate scan https://github.com/owner/repo",
206
225
  "codegate scan https://github.com/owner/repo --skill security-review",
226
+ "codegate scan . --workflow-audits --collect project --persona auditor --runtime-mode online",
207
227
  "codegate scan https://github.com/owner/repo/blob/main/skills/security-review/SKILL.md",
208
228
  "codegate scan https://example.com/security-review/SKILL.md --format json",
209
229
  ]))
@@ -219,6 +239,7 @@ function addScanCommand(program, version, deps) {
219
239
  };
220
240
  let resolvedTarget;
221
241
  try {
242
+ const scanOptions = options;
222
243
  const resolveTarget = deps.resolveScanTarget ??
223
244
  ((input) => resolveScanTarget(input));
224
245
  resolvedTarget = await resolveTarget({
@@ -236,12 +257,26 @@ function addScanCommand(program, version, deps) {
236
257
  scanTarget,
237
258
  cli: cliConfig,
238
259
  });
239
- const config = options.includeUserScope === true
240
- ? {
241
- ...baseConfig,
242
- scan_user_scope: true,
243
- }
244
- : baseConfig;
260
+ const config = {
261
+ ...baseConfig,
262
+ scan_collection_modes: options.collect && options.collect.length > 0
263
+ ? options.collect
264
+ : baseConfig.scan_collection_modes,
265
+ scan_collection_kinds: (scanOptions.collectKind && scanOptions.collectKind.length > 0
266
+ ? scanOptions.collectKind
267
+ : baseConfig.scan_collection_kinds),
268
+ strict_collection: options.strictCollection === true
269
+ ? true
270
+ : (baseConfig.strict_collection ?? DEFAULT_CONFIG.strict_collection),
271
+ persona: options.persona ?? baseConfig.persona,
272
+ runtime_mode: options.runtimeMode ?? baseConfig.runtime_mode,
273
+ workflow_audits: {
274
+ enabled: options.workflowAudits === true
275
+ ? true
276
+ : (baseConfig.workflow_audits?.enabled ?? false),
277
+ },
278
+ scan_user_scope: options.includeUserScope === true ? true : (baseConfig.scan_user_scope ?? false),
279
+ };
245
280
  if (options.resetState) {
246
281
  const reset = deps.resetScanState ?? ((path) => resetScanState(path));
247
282
  await reset(config.scan_state_path);
@@ -314,6 +349,47 @@ function addScanCommand(program, version, deps) {
314
349
  }
315
350
  });
316
351
  }
352
+ function addScanContentCommand(program, version, deps) {
353
+ program
354
+ .command("scan-content <content...>")
355
+ .description("Scan inline content for AI tool config risks")
356
+ .addOption(new Option("--type <type>", "content type")
357
+ .choices([...SCAN_CONTENT_TYPES])
358
+ .makeOptionMandatory())
359
+ .addHelpText("after", renderExampleHelp([
360
+ 'codegate scan-content \'{"mcpServers":{"bad":{"command":"bash"}}}\' --type json',
361
+ "codegate scan-content '# Suspicious instructions' --type markdown",
362
+ "codegate scan-content 'echo hello' --type text",
363
+ ]))
364
+ .action(async (contentParts, options) => {
365
+ try {
366
+ const content = (contentParts ?? []).join(" ");
367
+ const type = options.type;
368
+ if (!type) {
369
+ throw new Error("Missing required option: --type");
370
+ }
371
+ const config = deps.resolveConfig({
372
+ scanTarget: deps.cwd(),
373
+ });
374
+ await executeScanContentCommand({
375
+ version,
376
+ cwd: deps.cwd(),
377
+ content,
378
+ type,
379
+ config,
380
+ }, {
381
+ stdout: deps.stdout,
382
+ stderr: deps.stderr,
383
+ setExitCode: deps.setExitCode,
384
+ });
385
+ }
386
+ catch (error) {
387
+ const message = error instanceof Error ? error.message : String(error);
388
+ deps.stderr(`Scan content failed: ${message}`);
389
+ deps.setExitCode(3);
390
+ }
391
+ });
392
+ }
317
393
  function addRunCommand(program, version, deps) {
318
394
  program
319
395
  .command("run <tool>")
@@ -568,11 +644,13 @@ export function createCli(version = packageJson.version ?? "0.0.0-dev", deps = d
568
644
  "codegate scan .",
569
645
  "codegate scan https://github.com/owner/repo",
570
646
  "codegate scan https://github.com/owner/repo/blob/main/skills/security-review/SKILL.md",
647
+ 'codegate scan-content \'{"mcpServers":{"bad":{"command":"bash"}}}\' --type json',
571
648
  "codegate skills add owner/repo --skill security-review",
572
649
  "codegate clawhub install security-auditor",
573
650
  "codegate run claude",
574
651
  ]));
575
652
  addScanCommand(program, version, deps);
653
+ addScanContentCommand(program, version, deps);
576
654
  addSkillsCommand(program, version, deps);
577
655
  addClawhubCommand(program, version, deps);
578
656
  addRunCommand(program, version, deps);
@@ -1,4 +1,4 @@
1
- import { type CodeGateConfig, type OutputFormat } from "../config.js";
1
+ import { type AuditPersona, type CodeGateConfig, type OutputFormat, type RuntimeMode, type ScanCollectionMode } from "../config.js";
2
2
  import { type MetaAgentCommand, type MetaAgentTool } from "../layer3-dynamic/command-builder.js";
3
3
  import type { LocalTextAnalysisTarget } from "../layer3-dynamic/local-text-analysis.js";
4
4
  import type { ResourceFetchResult } from "../layer3-dynamic/resource-fetcher.js";
@@ -21,6 +21,11 @@ export interface ScanCommandOptions {
21
21
  resetState?: boolean;
22
22
  includeUserScope?: boolean;
23
23
  skill?: string;
24
+ collect?: ScanCollectionMode[];
25
+ strictCollection?: boolean;
26
+ persona?: AuditPersona;
27
+ runtimeMode?: RuntimeMode;
28
+ workflowAudits?: boolean;
24
29
  }
25
30
  export interface ScanRunnerInput {
26
31
  version: string;
@@ -1,5 +1,5 @@
1
1
  import { resolve } from "node:path";
2
- import { applyConfigPolicy } from "../config.js";
2
+ import { applyConfigPolicy, } from "../config.js";
3
3
  import { buildMetaAgentCommand, } from "../layer3-dynamic/command-builder.js";
4
4
  import { supportsAgentLocalTextAnalysis } from "../layer3-dynamic/local-text-analysis.js";
5
5
  import { buildLocalTextAnalysisPrompt, buildSecurityAnalysisPrompt, } from "../layer3-dynamic/meta-agent.js";
@@ -0,0 +1,16 @@
1
+ import { type CodeGateConfig } from "../config.js";
2
+ export declare const SCAN_CONTENT_TYPES: readonly ["json", "yaml", "toml", "markdown", "text"];
3
+ export type ScanContentType = (typeof SCAN_CONTENT_TYPES)[number];
4
+ export interface ExecuteScanContentCommandInput {
5
+ version: string;
6
+ cwd: string;
7
+ content: string;
8
+ type: ScanContentType;
9
+ config: CodeGateConfig;
10
+ }
11
+ export interface ExecuteScanContentCommandDeps {
12
+ stdout: (message: string) => void;
13
+ stderr: (message: string) => void;
14
+ setExitCode: (code: number) => void;
15
+ }
16
+ export declare function executeScanContentCommand(input: ExecuteScanContentCommandInput, deps: ExecuteScanContentCommandDeps): Promise<void>;
@@ -0,0 +1,65 @@
1
+ import { applyConfigPolicy } from "../config.js";
2
+ import { loadKnowledgeBase } from "../layer1-discovery/knowledge-base.js";
3
+ import { parseConfigContent } from "../layer1-discovery/config-parser.js";
4
+ import { runStaticPipeline } from "../pipeline.js";
5
+ import { renderByFormat } from "./scan-command/helpers.js";
6
+ export const SCAN_CONTENT_TYPES = ["json", "yaml", "toml", "markdown", "text"];
7
+ function toReportPath(type) {
8
+ if (type === "markdown") {
9
+ return "scan-content.md";
10
+ }
11
+ if (type === "text") {
12
+ return "scan-content.txt";
13
+ }
14
+ return `scan-content.${type}`;
15
+ }
16
+ export async function executeScanContentCommand(input, deps) {
17
+ try {
18
+ const parsed = parseConfigContent(input.content, input.type);
19
+ if (!parsed.ok) {
20
+ throw new Error(parsed.error);
21
+ }
22
+ const kbVersion = loadKnowledgeBase().schemaVersion;
23
+ const report = applyConfigPolicy(await runStaticPipeline({
24
+ version: input.version,
25
+ kbVersion,
26
+ scanTarget: `scan-content:${input.type}`,
27
+ toolsDetected: [],
28
+ projectRoot: input.cwd,
29
+ files: [
30
+ {
31
+ filePath: toReportPath(input.type),
32
+ format: input.type,
33
+ parsed: parsed.data,
34
+ textContent: input.content,
35
+ },
36
+ ],
37
+ symlinkEscapes: [],
38
+ hooks: [],
39
+ config: {
40
+ knownSafeMcpServers: input.config.known_safe_mcp_servers,
41
+ knownSafeFormatters: input.config.known_safe_formatters,
42
+ knownSafeLspServers: input.config.known_safe_lsp_servers,
43
+ knownSafeHooks: input.config.known_safe_hooks,
44
+ blockedCommands: input.config.blocked_commands,
45
+ trustedApiDomains: input.config.trusted_api_domains,
46
+ unicodeAnalysis: input.config.unicode_analysis,
47
+ checkIdeSettings: input.config.check_ide_settings,
48
+ rulePackPaths: input.config.rule_pack_paths,
49
+ allowedRules: input.config.allowed_rules,
50
+ skipRules: input.config.skip_rules,
51
+ persona: input.config.persona,
52
+ runtimeMode: input.config.runtime_mode,
53
+ workflowAuditsEnabled: input.config.workflow_audits?.enabled === true,
54
+ rulePolicies: input.config.rules,
55
+ },
56
+ }), input.config);
57
+ deps.stdout(renderByFormat(input.config.output_format, report));
58
+ deps.setExitCode(report.summary.exit_code);
59
+ }
60
+ catch (error) {
61
+ const message = error instanceof Error ? error.message : String(error);
62
+ deps.stderr(`Scan content failed: ${message}`);
63
+ deps.setExitCode(3);
64
+ }
65
+ }
@@ -0,0 +1,11 @@
1
+ import type { Finding } from "../types/finding.js";
2
+ export interface InlineIgnoreDirectiveSet {
3
+ rules: Set<string>;
4
+ ruleLines: Map<string, Set<number>>;
5
+ }
6
+ export type InlineIgnoreMap = Map<string, InlineIgnoreDirectiveSet>;
7
+ export declare function collectInlineIgnoreDirectives(files: Array<{
8
+ filePath: string;
9
+ textContent: string;
10
+ }>): InlineIgnoreMap;
11
+ export declare function applyInlineIgnoreDirectives<T extends Finding>(findings: T[], directives: InlineIgnoreMap): T[];
@@ -0,0 +1,57 @@
1
+ function normalizeRuleId(value) {
2
+ const trimmed = value.trim();
3
+ return trimmed.length > 0 ? trimmed : null;
4
+ }
5
+ function addDirective(target, ruleId, line) {
6
+ target.rules.add(ruleId);
7
+ const lines = target.ruleLines.get(ruleId) ?? new Set();
8
+ lines.add(line);
9
+ target.ruleLines.set(ruleId, lines);
10
+ }
11
+ function parseDirectiveRules(raw) {
12
+ return raw
13
+ .split(",")
14
+ .map((entry) => normalizeRuleId(entry))
15
+ .filter((entry) => entry !== null);
16
+ }
17
+ export function collectInlineIgnoreDirectives(files) {
18
+ const directives = new Map();
19
+ const pattern = /codegate:\s*ignore\[([^\]]+)\]/giu;
20
+ for (const file of files) {
21
+ const lines = file.textContent.split(/\r?\n/u);
22
+ let directiveSet = directives.get(file.filePath);
23
+ for (let index = 0; index < lines.length; index += 1) {
24
+ const line = lines[index] ?? "";
25
+ pattern.lastIndex = 0;
26
+ for (const match of line.matchAll(pattern)) {
27
+ const ruleIds = parseDirectiveRules(match[1] ?? "");
28
+ if (ruleIds.length === 0) {
29
+ continue;
30
+ }
31
+ if (!directiveSet) {
32
+ directiveSet = {
33
+ rules: new Set(),
34
+ ruleLines: new Map(),
35
+ };
36
+ directives.set(file.filePath, directiveSet);
37
+ }
38
+ for (const ruleId of ruleIds) {
39
+ addDirective(directiveSet, ruleId, index + 1);
40
+ }
41
+ }
42
+ }
43
+ }
44
+ return directives;
45
+ }
46
+ export function applyInlineIgnoreDirectives(findings, directives) {
47
+ return findings.map((finding) => {
48
+ const set = directives.get(finding.file_path);
49
+ if (!set || !set.rules.has(finding.rule_id)) {
50
+ return finding;
51
+ }
52
+ return {
53
+ ...finding,
54
+ suppressed: true,
55
+ };
56
+ });
57
+ }
@@ -0,0 +1,22 @@
1
+ import type { Finding } from "../types/finding.js";
2
+ export interface SuppressionRule {
3
+ rule_id?: string;
4
+ file_path?: string;
5
+ location?: string;
6
+ severity?: Finding["severity"];
7
+ category?: Finding["category"];
8
+ cwe?: string;
9
+ fingerprint?: string;
10
+ }
11
+ export interface RulePolicyConfig {
12
+ disable?: boolean;
13
+ ignore?: readonly string[];
14
+ config?: Record<string, unknown>;
15
+ }
16
+ export type RulePolicyMap = Record<string, RulePolicyConfig>;
17
+ export interface SuppressionPolicy {
18
+ suppress_findings?: readonly string[];
19
+ suppression_rules?: readonly SuppressionRule[];
20
+ rule_policies?: RulePolicyMap;
21
+ }
22
+ export declare function applySuppressionPolicy<T extends Finding>(findings: T[], policy: SuppressionPolicy): T[];