codegate-ai 0.7.0 → 0.8.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.
- package/README.md +61 -25
- package/dist/cli.js +44 -0
- package/dist/commands/scan-content-command.d.ts +16 -0
- package/dist/commands/scan-content-command.js +61 -0
- package/dist/config/suppression-policy.d.ts +14 -0
- package/dist/config/suppression-policy.js +81 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +29 -3
- package/dist/layer2-static/advisories/agent-components.json +62 -0
- package/dist/layer2-static/detectors/advisory-intelligence.d.ts +7 -0
- package/dist/layer2-static/detectors/advisory-intelligence.js +170 -0
- package/dist/layer2-static/detectors/command-exec.js +6 -0
- package/dist/layer2-static/detectors/rule-file.js +5 -0
- package/dist/layer2-static/engine.d.ts +4 -1
- package/dist/layer2-static/engine.js +97 -0
- package/dist/layer2-static/rule-engine.d.ts +1 -1
- package/dist/layer2-static/rule-engine.js +1 -13
- package/dist/layer2-static/rule-pack-loader.d.ts +10 -0
- package/dist/layer2-static/rule-pack-loader.js +187 -0
- package/dist/layer3-dynamic/toxic-flow.js +6 -0
- package/dist/pipeline.js +9 -8
- package/dist/report/finding-fingerprint.d.ts +5 -0
- package/dist/report/finding-fingerprint.js +47 -0
- package/dist/reporter/markdown.js +25 -3
- package/dist/reporter/sarif.js +2 -0
- package/dist/reporter/terminal.js +25 -0
- package/dist/scan-target/fetch-plan.d.ts +8 -0
- package/dist/scan-target/fetch-plan.js +30 -0
- package/dist/scan-target/staging.js +60 -5
- package/dist/scan.js +3 -0
- package/dist/types/finding.d.ts +9 -0
- 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. |
|
|
@@ -150,6 +151,28 @@ codegate scan . --remediate --dry-run --patch
|
|
|
150
151
|
codegate scan . --reset-state
|
|
151
152
|
```
|
|
152
153
|
|
|
154
|
+
## `scan-content` Command
|
|
155
|
+
|
|
156
|
+
`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.
|
|
157
|
+
|
|
158
|
+
Use `--type` to declare the content format:
|
|
159
|
+
|
|
160
|
+
| Type | Purpose |
|
|
161
|
+
| ---------- | -------------------------------------------------------------------- |
|
|
162
|
+
| `json` | Parse JSON input and run the static scanner on the parsed structure. |
|
|
163
|
+
| `yaml` | Parse YAML input and run the static scanner on the parsed structure. |
|
|
164
|
+
| `toml` | Parse TOML input and run the static scanner on the parsed structure. |
|
|
165
|
+
| `markdown` | Analyze Markdown instruction text as a rule surface. |
|
|
166
|
+
| `text` | Analyze plain text as a rule surface. |
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
codegate scan-content '{"mcpServers":{"bad":{"command":"bash"}}}' --type json
|
|
172
|
+
codegate scan-content '# Suspicious instructions' --type markdown
|
|
173
|
+
codegate scan-content 'echo hello' --type text
|
|
174
|
+
```
|
|
175
|
+
|
|
153
176
|
## `run` Command
|
|
154
177
|
|
|
155
178
|
`codegate run <tool>` runs scan-first wrapper mode.
|
|
@@ -199,6 +222,7 @@ Behavior:
|
|
|
199
222
|
- Dangerous findings block execution (fail-closed).
|
|
200
223
|
- Warning-level findings can still require confirmation unless `--cg-force` is provided.
|
|
201
224
|
- Non-install subcommands (for example `skills find` or `clawhub search`) are passed through without preflight scanning.
|
|
225
|
+
- 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
226
|
|
|
203
227
|
Wrapper flags (consumed by CodeGate, not forwarded):
|
|
204
228
|
|
|
@@ -299,33 +323,38 @@ codegate init
|
|
|
299
323
|
- List values are merged and de-duplicated across levels.
|
|
300
324
|
- `trusted_directories` is global-only; project config cannot set it.
|
|
301
325
|
- `blocked_commands` is merged with defaults; defaults are always retained.
|
|
326
|
+
- `rule_pack_paths`, `allowed_rules`, `skip_rules`, `suppress_findings`, and `suppression_rules` merge across global and project config.
|
|
302
327
|
|
|
303
328
|
### Full Configuration Reference
|
|
304
329
|
|
|
305
|
-
| Key | Type | Allowed Values
|
|
306
|
-
| -------------------------------- | ---------------- |
|
|
307
|
-
| `severity_threshold` | string | `critical`, `high`, `medium`, `low`, `info`
|
|
308
|
-
| `auto_proceed_below_threshold` | boolean | `true`, `false`
|
|
309
|
-
| `output_format` | string | `terminal`, `json`, `sarif`, `markdown`, `html`
|
|
310
|
-
| `scan_state_path` | string | file path
|
|
311
|
-
| `scan_user_scope` | boolean | `true`, `false`
|
|
312
|
-
| `tui.enabled` | boolean | `true`, `false`
|
|
313
|
-
| `tui.colour_scheme` | string | free string (currently `default`)
|
|
314
|
-
| `tui.compact_mode` | boolean | `true`, `false`
|
|
315
|
-
| `tool_discovery.preferred_agent` | string | practical values: `claude`, `claude-code`, `codex`, `codex-cli`, `opencode`
|
|
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
|
|
320
|
-
| `known_safe_mcp_servers` | array of strings | package/server identifiers
|
|
321
|
-
| `known_safe_formatters` | array of strings | formatter names
|
|
322
|
-
| `known_safe_lsp_servers` | array of strings | lsp server names
|
|
323
|
-
| `known_safe_hooks` | array of strings | relative hook paths such as `.git/hooks/pre-commit`
|
|
324
|
-
| `unicode_analysis` | boolean | `true`, `false`
|
|
325
|
-
| `check_ide_settings` | boolean | `true`, `false`
|
|
326
|
-
| `owasp_mapping` | boolean | `true`, `false`
|
|
327
|
-
| `trusted_api_domains` | array of strings | domain names
|
|
328
|
-
| `suppress_findings` | array of strings | finding IDs/fingerprints
|
|
330
|
+
| Key | Type | Allowed Values | Default |
|
|
331
|
+
| -------------------------------- | ---------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------- |
|
|
332
|
+
| `severity_threshold` | string | `critical`, `high`, `medium`, `low`, `info` | `high` |
|
|
333
|
+
| `auto_proceed_below_threshold` | boolean | `true`, `false` | `true` |
|
|
334
|
+
| `output_format` | string | `terminal`, `json`, `sarif`, `markdown`, `html` | `terminal` |
|
|
335
|
+
| `scan_state_path` | string | file path | `~/.codegate/scan-state.json` |
|
|
336
|
+
| `scan_user_scope` | boolean | `true`, `false` | `true` |
|
|
337
|
+
| `tui.enabled` | boolean | `true`, `false` | `true` |
|
|
338
|
+
| `tui.colour_scheme` | string | free string (currently `default`) | `default` |
|
|
339
|
+
| `tui.compact_mode` | boolean | `true`, `false` | `false` |
|
|
340
|
+
| `tool_discovery.preferred_agent` | string | practical values: `claude`, `claude-code`, `codex`, `codex-cli`, `opencode` | `claude` |
|
|
341
|
+
| `tool_discovery.agent_paths` | object | map of agent key -> binary path | `{}` |
|
|
342
|
+
| `tool_discovery.skip_tools` | array of strings | tool keys to skip in discovery/selection | `[]` |
|
|
343
|
+
| `trusted_directories` | array of strings | directory paths | `[]` |
|
|
344
|
+
| `blocked_commands` | array of strings | command names | `["bash","sh","curl","wget","nc","python","node"]` |
|
|
345
|
+
| `known_safe_mcp_servers` | array of strings | package/server identifiers | prefilled |
|
|
346
|
+
| `known_safe_formatters` | array of strings | formatter names | prefilled |
|
|
347
|
+
| `known_safe_lsp_servers` | array of strings | lsp server names | prefilled |
|
|
348
|
+
| `known_safe_hooks` | array of strings | relative hook paths such as `.git/hooks/pre-commit` | `[]` |
|
|
349
|
+
| `unicode_analysis` | boolean | `true`, `false` | `true` |
|
|
350
|
+
| `check_ide_settings` | boolean | `true`, `false` | `true` |
|
|
351
|
+
| `owasp_mapping` | boolean | `true`, `false` | `true` |
|
|
352
|
+
| `trusted_api_domains` | array of strings | domain names | `[]` |
|
|
353
|
+
| `suppress_findings` | array of strings | finding IDs/fingerprints | `[]` |
|
|
354
|
+
| `suppression_rules` | array of objects | rule match objects with `rule_id`, `file_path`, `severity`, `category`, `cwe`, `fingerprint` | `[]` |
|
|
355
|
+
| `rule_pack_paths` | array of strings | extra rule pack files or directories | `[]` |
|
|
356
|
+
| `allowed_rules` | array of strings | rule IDs to keep after loading | `[]` |
|
|
357
|
+
| `skip_rules` | array of strings | rule IDs to drop after loading | `[]` |
|
|
329
358
|
|
|
330
359
|
### Default Config Example
|
|
331
360
|
|
|
@@ -359,7 +388,11 @@ codegate init
|
|
|
359
388
|
"check_ide_settings": true,
|
|
360
389
|
"owasp_mapping": true,
|
|
361
390
|
"trusted_api_domains": [],
|
|
362
|
-
"suppress_findings": []
|
|
391
|
+
"suppress_findings": [],
|
|
392
|
+
"suppression_rules": [],
|
|
393
|
+
"rule_pack_paths": [],
|
|
394
|
+
"allowed_rules": [],
|
|
395
|
+
"skip_rules": []
|
|
363
396
|
}
|
|
364
397
|
```
|
|
365
398
|
|
|
@@ -371,6 +404,9 @@ Configuration notes:
|
|
|
371
404
|
- `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
405
|
- `check_ide_settings=false` disables `IDE_SETTINGS` findings.
|
|
373
406
|
- `owasp_mapping=false` keeps detection behavior unchanged and emits empty `owasp` arrays in reports.
|
|
407
|
+
- `suppression_rules` applies all listed criteria with AND semantics. If a criterion is omitted, it is ignored.
|
|
408
|
+
- `rule_pack_paths` can point to extra JSON rule-pack files or directories of JSON rule packs.
|
|
409
|
+
- `allowed_rules` and `skip_rules` control which loaded rule IDs remain active after rule-pack loading.
|
|
374
410
|
|
|
375
411
|
## Output Formats
|
|
376
412
|
|
package/dist/cli.js
CHANGED
|
@@ -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";
|
|
@@ -314,6 +315,47 @@ function addScanCommand(program, version, deps) {
|
|
|
314
315
|
}
|
|
315
316
|
});
|
|
316
317
|
}
|
|
318
|
+
function addScanContentCommand(program, version, deps) {
|
|
319
|
+
program
|
|
320
|
+
.command("scan-content <content...>")
|
|
321
|
+
.description("Scan inline content for AI tool config risks")
|
|
322
|
+
.addOption(new Option("--type <type>", "content type")
|
|
323
|
+
.choices([...SCAN_CONTENT_TYPES])
|
|
324
|
+
.makeOptionMandatory())
|
|
325
|
+
.addHelpText("after", renderExampleHelp([
|
|
326
|
+
'codegate scan-content \'{"mcpServers":{"bad":{"command":"bash"}}}\' --type json',
|
|
327
|
+
"codegate scan-content '# Suspicious instructions' --type markdown",
|
|
328
|
+
"codegate scan-content 'echo hello' --type text",
|
|
329
|
+
]))
|
|
330
|
+
.action(async (contentParts, options) => {
|
|
331
|
+
try {
|
|
332
|
+
const content = (contentParts ?? []).join(" ");
|
|
333
|
+
const type = options.type;
|
|
334
|
+
if (!type) {
|
|
335
|
+
throw new Error("Missing required option: --type");
|
|
336
|
+
}
|
|
337
|
+
const config = deps.resolveConfig({
|
|
338
|
+
scanTarget: deps.cwd(),
|
|
339
|
+
});
|
|
340
|
+
await executeScanContentCommand({
|
|
341
|
+
version,
|
|
342
|
+
cwd: deps.cwd(),
|
|
343
|
+
content,
|
|
344
|
+
type,
|
|
345
|
+
config,
|
|
346
|
+
}, {
|
|
347
|
+
stdout: deps.stdout,
|
|
348
|
+
stderr: deps.stderr,
|
|
349
|
+
setExitCode: deps.setExitCode,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
354
|
+
deps.stderr(`Scan content failed: ${message}`);
|
|
355
|
+
deps.setExitCode(3);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
317
359
|
function addRunCommand(program, version, deps) {
|
|
318
360
|
program
|
|
319
361
|
.command("run <tool>")
|
|
@@ -568,11 +610,13 @@ export function createCli(version = packageJson.version ?? "0.0.0-dev", deps = d
|
|
|
568
610
|
"codegate scan .",
|
|
569
611
|
"codegate scan https://github.com/owner/repo",
|
|
570
612
|
"codegate scan https://github.com/owner/repo/blob/main/skills/security-review/SKILL.md",
|
|
613
|
+
'codegate scan-content \'{"mcpServers":{"bad":{"command":"bash"}}}\' --type json',
|
|
571
614
|
"codegate skills add owner/repo --skill security-review",
|
|
572
615
|
"codegate clawhub install security-auditor",
|
|
573
616
|
"codegate run claude",
|
|
574
617
|
]));
|
|
575
618
|
addScanCommand(program, version, deps);
|
|
619
|
+
addScanContentCommand(program, version, deps);
|
|
576
620
|
addSkillsCommand(program, version, deps);
|
|
577
621
|
addClawhubCommand(program, version, deps);
|
|
578
622
|
addRunCommand(program, version, deps);
|
|
@@ -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,61 @@
|
|
|
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(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
|
+
},
|
|
52
|
+
}), input.config);
|
|
53
|
+
deps.stdout(renderByFormat(input.config.output_format, report));
|
|
54
|
+
deps.setExitCode(report.summary.exit_code);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
+
deps.stderr(`Scan content failed: ${message}`);
|
|
59
|
+
deps.setExitCode(3);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Finding } from "../types/finding.js";
|
|
2
|
+
export interface SuppressionRule {
|
|
3
|
+
rule_id?: string;
|
|
4
|
+
file_path?: string;
|
|
5
|
+
severity?: Finding["severity"];
|
|
6
|
+
category?: Finding["category"];
|
|
7
|
+
cwe?: string;
|
|
8
|
+
fingerprint?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SuppressionPolicy {
|
|
11
|
+
suppress_findings?: readonly string[];
|
|
12
|
+
suppression_rules?: readonly SuppressionRule[];
|
|
13
|
+
}
|
|
14
|
+
export declare function applySuppressionPolicy<T extends Finding>(findings: T[], policy: SuppressionPolicy): T[];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
function normalizeString(value) {
|
|
2
|
+
if (typeof value !== "string") {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
7
|
+
}
|
|
8
|
+
function escapeRegExp(value) {
|
|
9
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10
|
+
}
|
|
11
|
+
function globToRegExp(glob) {
|
|
12
|
+
const pattern = normalizeString(glob)?.replaceAll("\\", "/");
|
|
13
|
+
if (!pattern) {
|
|
14
|
+
return /^$/;
|
|
15
|
+
}
|
|
16
|
+
let regex = "^";
|
|
17
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
18
|
+
const char = pattern[index];
|
|
19
|
+
if (char === "*") {
|
|
20
|
+
if (pattern[index + 1] === "*") {
|
|
21
|
+
regex += ".*";
|
|
22
|
+
index++;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
regex += "[^/]*";
|
|
26
|
+
}
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (char === "?") {
|
|
30
|
+
regex += "[^/]";
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
regex += escapeRegExp(char);
|
|
34
|
+
}
|
|
35
|
+
regex += "$";
|
|
36
|
+
return new RegExp(regex);
|
|
37
|
+
}
|
|
38
|
+
function matchesGlob(value, glob) {
|
|
39
|
+
return globToRegExp(glob).test(value.replaceAll("\\", "/"));
|
|
40
|
+
}
|
|
41
|
+
function matchesSuppressionRule(finding, rule) {
|
|
42
|
+
const ruleId = normalizeString(rule.rule_id);
|
|
43
|
+
if (ruleId && finding.rule_id !== ruleId) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const filePath = normalizeString(rule.file_path);
|
|
47
|
+
if (filePath && !matchesGlob(finding.file_path, filePath)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const severity = rule.severity;
|
|
51
|
+
if (severity && finding.severity !== severity) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const category = normalizeString(rule.category);
|
|
55
|
+
if (category && finding.category !== category) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const cwe = normalizeString(rule.cwe);
|
|
59
|
+
if (cwe && finding.cwe !== cwe) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const fingerprint = normalizeString(rule.fingerprint);
|
|
63
|
+
if (fingerprint && finding.fingerprint !== fingerprint) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
export function applySuppressionPolicy(findings, policy) {
|
|
69
|
+
const legacySuppressions = new Set((policy.suppress_findings ?? [])
|
|
70
|
+
.map((findingId) => normalizeString(findingId))
|
|
71
|
+
.filter((findingId) => findingId !== undefined));
|
|
72
|
+
const rules = policy.suppression_rules ?? [];
|
|
73
|
+
return findings.map((finding) => {
|
|
74
|
+
const ruleMatch = rules.some((rule) => matchesSuppressionRule(finding, rule));
|
|
75
|
+
const legacyMatch = legacySuppressions.has(finding.finding_id);
|
|
76
|
+
return {
|
|
77
|
+
...finding,
|
|
78
|
+
suppressed: finding.suppressed || legacyMatch || ruleMatch,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Finding } from "./types/finding.js";
|
|
2
2
|
import type { CodeGateReport } from "./types/report.js";
|
|
3
|
+
import { type SuppressionRule } from "./config/suppression-policy.js";
|
|
3
4
|
export declare const OUTPUT_FORMATS: readonly ["terminal", "json", "sarif", "markdown", "html"];
|
|
4
5
|
export type OutputFormat = (typeof OUTPUT_FORMATS)[number];
|
|
5
6
|
export declare const SEVERITY_THRESHOLDS: readonly ["critical", "high", "medium", "low", "info"];
|
|
@@ -32,7 +33,11 @@ export interface CodeGateConfig {
|
|
|
32
33
|
check_ide_settings: boolean;
|
|
33
34
|
owasp_mapping: boolean;
|
|
34
35
|
trusted_api_domains: string[];
|
|
36
|
+
rule_pack_paths?: string[];
|
|
37
|
+
allowed_rules?: string[];
|
|
38
|
+
skip_rules?: string[];
|
|
35
39
|
suppress_findings: string[];
|
|
40
|
+
suppression_rules?: SuppressionRule[];
|
|
36
41
|
}
|
|
37
42
|
export interface CliConfigOverrides {
|
|
38
43
|
format?: OutputFormat;
|
package/dist/config.js
CHANGED
|
@@ -3,6 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
5
5
|
import { applyReportSummary, computeExitCode as computeReportExitCode } from "./report-summary.js";
|
|
6
|
+
import { applySuppressionPolicy } from "./config/suppression-policy.js";
|
|
6
7
|
export const OUTPUT_FORMATS = ["terminal", "json", "sarif", "markdown", "html"];
|
|
7
8
|
export const SEVERITY_THRESHOLDS = ["critical", "high", "medium", "low", "info"];
|
|
8
9
|
export const DEFAULT_CONFIG = {
|
|
@@ -34,7 +35,11 @@ export const DEFAULT_CONFIG = {
|
|
|
34
35
|
check_ide_settings: true,
|
|
35
36
|
owasp_mapping: true,
|
|
36
37
|
trusted_api_domains: [],
|
|
38
|
+
rule_pack_paths: [],
|
|
39
|
+
allowed_rules: [],
|
|
40
|
+
skip_rules: [],
|
|
37
41
|
suppress_findings: [],
|
|
42
|
+
suppression_rules: [],
|
|
38
43
|
};
|
|
39
44
|
function normalizeOutputFormat(value) {
|
|
40
45
|
if (!value) {
|
|
@@ -163,22 +168,43 @@ export function resolveEffectiveConfig(options) {
|
|
|
163
168
|
globalConfig.trusted_api_domains,
|
|
164
169
|
projectConfig.trusted_api_domains,
|
|
165
170
|
]),
|
|
171
|
+
rule_pack_paths: unique([
|
|
172
|
+
DEFAULT_CONFIG.rule_pack_paths,
|
|
173
|
+
globalConfig.rule_pack_paths,
|
|
174
|
+
projectConfig.rule_pack_paths,
|
|
175
|
+
]),
|
|
176
|
+
allowed_rules: unique([
|
|
177
|
+
DEFAULT_CONFIG.allowed_rules,
|
|
178
|
+
globalConfig.allowed_rules,
|
|
179
|
+
projectConfig.allowed_rules,
|
|
180
|
+
]),
|
|
181
|
+
skip_rules: unique([
|
|
182
|
+
DEFAULT_CONFIG.skip_rules,
|
|
183
|
+
globalConfig.skip_rules,
|
|
184
|
+
projectConfig.skip_rules,
|
|
185
|
+
]),
|
|
166
186
|
suppress_findings: unique([
|
|
167
187
|
DEFAULT_CONFIG.suppress_findings,
|
|
168
188
|
globalConfig.suppress_findings,
|
|
169
189
|
projectConfig.suppress_findings,
|
|
170
190
|
]),
|
|
191
|
+
suppression_rules: [
|
|
192
|
+
...(DEFAULT_CONFIG.suppression_rules ?? []),
|
|
193
|
+
...(globalConfig.suppression_rules ?? []),
|
|
194
|
+
...(projectConfig.suppression_rules ?? []),
|
|
195
|
+
],
|
|
171
196
|
};
|
|
172
197
|
}
|
|
173
198
|
export function computeExitCode(findings, threshold) {
|
|
174
199
|
return computeReportExitCode(findings, threshold);
|
|
175
200
|
}
|
|
176
201
|
export function applyConfigPolicy(report, config) {
|
|
177
|
-
const
|
|
178
|
-
|
|
202
|
+
const findings = applySuppressionPolicy(report.findings, {
|
|
203
|
+
suppress_findings: config.suppress_findings,
|
|
204
|
+
suppression_rules: config.suppression_rules,
|
|
205
|
+
}).map((finding) => ({
|
|
179
206
|
...finding,
|
|
180
207
|
owasp: config.owasp_mapping ? finding.owasp : [],
|
|
181
|
-
suppressed: finding.suppressed || suppressionSet.has(finding.finding_id),
|
|
182
208
|
}));
|
|
183
209
|
return applyReportSummary({
|
|
184
210
|
...report,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "filesystem",
|
|
4
|
+
"rule_id": "advisory-agent-component-filesystem",
|
|
5
|
+
"severity": "MEDIUM",
|
|
6
|
+
"category": "CONFIG_PRESENT",
|
|
7
|
+
"description": "Filesystem agent components can expose broad local file access and should be reviewed before approval.",
|
|
8
|
+
"signatures": ["@anthropic/mcp-server-filesystem", "mcp-server-filesystem"],
|
|
9
|
+
"file_patterns": ["**/.mcp.json", "**/mcp.json", "**/settings.json"],
|
|
10
|
+
"remediation_actions": ["remove_field", "review_component"],
|
|
11
|
+
"metadata": {
|
|
12
|
+
"sources": ["mcpServers.*.command", "mcpServers.*.args"],
|
|
13
|
+
"sinks": ["local_filesystem"],
|
|
14
|
+
"referenced_secrets": [],
|
|
15
|
+
"risk_tags": ["sensitive_access"],
|
|
16
|
+
"origin": "agent-components.json"
|
|
17
|
+
},
|
|
18
|
+
"owasp": ["ASI03", "ASI07"],
|
|
19
|
+
"cwe": "CWE-200",
|
|
20
|
+
"confidence": "HIGH"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "github",
|
|
24
|
+
"rule_id": "advisory-agent-component-github",
|
|
25
|
+
"severity": "MEDIUM",
|
|
26
|
+
"category": "CONFIG_PRESENT",
|
|
27
|
+
"description": "GitHub agent components can ingest untrusted repository content and deserve manual review.",
|
|
28
|
+
"signatures": ["@modelcontextprotocol/server-github", "server-github"],
|
|
29
|
+
"file_patterns": ["**/.mcp.json", "**/mcp.json", "**/settings.json"],
|
|
30
|
+
"remediation_actions": ["remove_field", "review_component"],
|
|
31
|
+
"metadata": {
|
|
32
|
+
"sources": ["mcpServers.*.command", "mcpServers.*.args"],
|
|
33
|
+
"sinks": ["repository_content"],
|
|
34
|
+
"referenced_secrets": [],
|
|
35
|
+
"risk_tags": ["untrusted_input"],
|
|
36
|
+
"origin": "agent-components.json"
|
|
37
|
+
},
|
|
38
|
+
"owasp": ["ASI01", "ASI07"],
|
|
39
|
+
"cwe": "CWE-74",
|
|
40
|
+
"confidence": "HIGH"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "slack",
|
|
44
|
+
"rule_id": "advisory-agent-component-slack",
|
|
45
|
+
"severity": "HIGH",
|
|
46
|
+
"category": "CONFIG_PRESENT",
|
|
47
|
+
"description": "Slack agent components can become exfiltration sinks and should be approved deliberately.",
|
|
48
|
+
"signatures": ["@modelcontextprotocol/server-slack", "server-slack"],
|
|
49
|
+
"file_patterns": ["**/.mcp.json", "**/mcp.json", "**/settings.json"],
|
|
50
|
+
"remediation_actions": ["remove_field", "review_component"],
|
|
51
|
+
"metadata": {
|
|
52
|
+
"sources": ["mcpServers.*.command", "mcpServers.*.args"],
|
|
53
|
+
"sinks": ["message_exfiltration"],
|
|
54
|
+
"referenced_secrets": [],
|
|
55
|
+
"risk_tags": ["exfiltration_sink"],
|
|
56
|
+
"origin": "agent-components.json"
|
|
57
|
+
},
|
|
58
|
+
"owasp": ["ASI07", "ASI09"],
|
|
59
|
+
"cwe": "CWE-200",
|
|
60
|
+
"confidence": "HIGH"
|
|
61
|
+
}
|
|
62
|
+
]
|