codegate-ai 0.8.0 → 0.9.1
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 +48 -15
- package/dist/cli.js +41 -7
- package/dist/commands/scan-command.d.ts +6 -1
- package/dist/commands/scan-command.js +1 -1
- package/dist/commands/scan-content-command.js +5 -1
- package/dist/config/inline-ignore.d.ts +11 -0
- package/dist/config/inline-ignore.js +57 -0
- package/dist/config/suppression-policy.d.ts +8 -0
- package/dist/config/suppression-policy.js +66 -1
- package/dist/config.d.ts +19 -1
- package/dist/config.js +160 -1
- package/dist/layer2-static/action/parser.d.ts +3 -0
- package/dist/layer2-static/action/parser.js +165 -0
- package/dist/layer2-static/action/types.d.ts +39 -0
- package/dist/layer2-static/action/types.js +1 -0
- package/dist/layer2-static/advisories/cache.d.ts +6 -0
- package/dist/layer2-static/advisories/cache.js +44 -0
- package/dist/layer2-static/advisories/cache.ts +62 -0
- package/dist/layer2-static/advisories/gha-advisory-client.d.ts +7 -0
- package/dist/layer2-static/advisories/gha-advisory-client.js +4 -0
- package/dist/layer2-static/advisories/gha-advisory-client.ts +13 -0
- package/dist/layer2-static/advisories/gha-known-vulnerable-actions.json +4 -0
- package/dist/layer2-static/audits/registry.d.ts +3 -0
- package/dist/layer2-static/audits/registry.js +29 -0
- package/dist/layer2-static/audits/types.d.ts +12 -0
- package/dist/layer2-static/audits/types.js +1 -0
- package/dist/layer2-static/dependabot/parser.d.ts +3 -0
- package/dist/layer2-static/dependabot/parser.js +189 -0
- package/dist/layer2-static/dependabot/types.d.ts +50 -0
- package/dist/layer2-static/dependabot/types.js +1 -0
- package/dist/layer2-static/detectors/dependabot-cooldown.d.ts +7 -0
- package/dist/layer2-static/detectors/dependabot-cooldown.js +54 -0
- package/dist/layer2-static/detectors/dependabot-execution.d.ts +7 -0
- package/dist/layer2-static/detectors/dependabot-execution.js +68 -0
- package/dist/layer2-static/detectors/workflow-anonymous-definition.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-anonymous-definition.js +48 -0
- package/dist/layer2-static/detectors/workflow-archived-uses.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-archived-uses.js +147 -0
- package/dist/layer2-static/detectors/workflow-artipacked.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-artipacked.js +62 -0
- package/dist/layer2-static/detectors/workflow-bot-conditions.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-bot-conditions.js +108 -0
- package/dist/layer2-static/detectors/workflow-cache-poisoning.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-cache-poisoning.js +67 -0
- package/dist/layer2-static/detectors/workflow-concurrency-limits.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-concurrency-limits.js +76 -0
- package/dist/layer2-static/detectors/workflow-dangerous-triggers.d.ts +6 -0
- package/dist/layer2-static/detectors/workflow-dangerous-triggers.js +38 -0
- package/dist/layer2-static/detectors/workflow-excessive-permissions.d.ts +6 -0
- package/dist/layer2-static/detectors/workflow-excessive-permissions.js +65 -0
- package/dist/layer2-static/detectors/workflow-forbidden-uses.d.ts +12 -0
- package/dist/layer2-static/detectors/workflow-forbidden-uses.js +148 -0
- package/dist/layer2-static/detectors/workflow-github-env.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-github-env.js +49 -0
- package/dist/layer2-static/detectors/workflow-hardcoded-container-credentials.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-hardcoded-container-credentials.js +90 -0
- package/dist/layer2-static/detectors/workflow-impostor-commit.d.ts +9 -0
- package/dist/layer2-static/detectors/workflow-impostor-commit.js +127 -0
- package/dist/layer2-static/detectors/workflow-insecure-commands.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-insecure-commands.js +72 -0
- package/dist/layer2-static/detectors/workflow-known-vuln-action.d.ts +8 -0
- package/dist/layer2-static/detectors/workflow-known-vuln-action.js +70 -0
- package/dist/layer2-static/detectors/workflow-misfeature.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-misfeature.js +101 -0
- package/dist/layer2-static/detectors/workflow-obfuscation.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-obfuscation.js +79 -0
- package/dist/layer2-static/detectors/workflow-overprovisioned-secrets.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-overprovisioned-secrets.js +81 -0
- package/dist/layer2-static/detectors/workflow-ref-confusion.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-ref-confusion.js +69 -0
- package/dist/layer2-static/detectors/workflow-ref-version-mismatch.d.ts +9 -0
- package/dist/layer2-static/detectors/workflow-ref-version-mismatch.js +179 -0
- package/dist/layer2-static/detectors/workflow-secrets-inherit.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-secrets-inherit.js +59 -0
- package/dist/layer2-static/detectors/workflow-secrets-outside-env.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-secrets-outside-env.js +99 -0
- package/dist/layer2-static/detectors/workflow-self-hosted-runner.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-self-hosted-runner.js +67 -0
- package/dist/layer2-static/detectors/workflow-stale-action-refs.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-stale-action-refs.js +178 -0
- package/dist/layer2-static/detectors/workflow-superfluous-actions.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-superfluous-actions.js +70 -0
- package/dist/layer2-static/detectors/workflow-template-injection.d.ts +6 -0
- package/dist/layer2-static/detectors/workflow-template-injection.js +94 -0
- package/dist/layer2-static/detectors/workflow-undocumented-permissions.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-undocumented-permissions.js +101 -0
- package/dist/layer2-static/detectors/workflow-unpinned-images.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-unpinned-images.js +108 -0
- package/dist/layer2-static/detectors/workflow-unpinned-uses.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-unpinned-uses.js +60 -0
- package/dist/layer2-static/detectors/workflow-unredacted-secrets.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-unredacted-secrets.js +92 -0
- package/dist/layer2-static/detectors/workflow-unsound-condition.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-unsound-condition.js +90 -0
- package/dist/layer2-static/detectors/workflow-unsound-contains.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-unsound-contains.js +88 -0
- package/dist/layer2-static/detectors/workflow-use-trusted-publishing.d.ts +7 -0
- package/dist/layer2-static/detectors/workflow-use-trusted-publishing.js +66 -0
- package/dist/layer2-static/engine.d.ts +9 -1
- package/dist/layer2-static/engine.js +490 -50
- package/dist/layer2-static/github/cache.d.ts +8 -0
- package/dist/layer2-static/github/cache.js +41 -0
- package/dist/layer2-static/github/client.d.ts +17 -0
- package/dist/layer2-static/github/client.js +43 -0
- package/dist/layer2-static/rules/claude-code.json +16 -0
- package/dist/layer2-static/rules/codex.json +16 -0
- package/dist/layer2-static/rules/common.json +15 -0
- package/dist/layer2-static/rules/copilot.json +15 -0
- package/dist/layer2-static/rules/cursor.json +15 -0
- package/dist/layer2-static/rules/opencode.json +15 -0
- package/dist/layer2-static/workflow/injection-sinks.json +5 -0
- package/dist/layer2-static/workflow/parser.d.ts +3 -0
- package/dist/layer2-static/workflow/parser.js +88 -0
- package/dist/layer2-static/workflow/types.d.ts +15 -0
- package/dist/layer2-static/workflow/types.js +1 -0
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +3 -3
- package/dist/reporter/sarif.js +23 -5
- package/dist/reporter/terminal.js +12 -0
- package/dist/scan.d.ts +7 -1
- package/dist/scan.js +154 -40
- package/dist/types/finding.d.ts +1 -1
- package/dist/types/finding.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -120,21 +120,26 @@ See the [Configuration](#configuration) section for full settings and examples.
|
|
|
120
120
|
|
|
121
121
|
## `scan` Command Flags
|
|
122
122
|
|
|
123
|
-
| Flag
|
|
124
|
-
|
|
|
125
|
-
| `--deep`
|
|
126
|
-
| `--remediate`
|
|
127
|
-
| `--fix-safe`
|
|
128
|
-
| `--dry-run`
|
|
129
|
-
| `--patch`
|
|
130
|
-
| `--no-tui`
|
|
131
|
-
| `--format <type>`
|
|
132
|
-
| `--output <path>`
|
|
133
|
-
| `--verbose`
|
|
134
|
-
| `--config <path>`
|
|
135
|
-
| `--force`
|
|
136
|
-
| `--include-user-scope`
|
|
137
|
-
| `--
|
|
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. |
|
|
138
143
|
|
|
139
144
|
Examples:
|
|
140
145
|
|
|
@@ -148,9 +153,37 @@ codegate scan . --deep --force
|
|
|
148
153
|
codegate scan . --remediate
|
|
149
154
|
codegate scan . --fix-safe
|
|
150
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
|
|
151
158
|
codegate scan . --reset-state
|
|
152
159
|
```
|
|
153
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
|
+
|
|
154
187
|
## `scan-content` Command
|
|
155
188
|
|
|
156
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.
|
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";
|
|
@@ -127,6 +127,8 @@ const defaultCliDeps = {
|
|
|
127
127
|
}),
|
|
128
128
|
prepareScanDiscovery: (scanTarget, config, options) => createScanDiscoveryContext(scanTarget, undefined, {
|
|
129
129
|
includeUserScope: config?.scan_user_scope === true,
|
|
130
|
+
collectModes: config?.scan_collection_modes,
|
|
131
|
+
collectKinds: config?.scan_collection_kinds,
|
|
130
132
|
parseSelected: true,
|
|
131
133
|
explicitCandidates: options?.explicitCandidates,
|
|
132
134
|
}),
|
|
@@ -158,6 +160,8 @@ const defaultCliDeps = {
|
|
|
158
160
|
? discoverDeepScanResourcesFromContext(discoveryContext)
|
|
159
161
|
: discoverDeepScanResources(scanTarget, undefined, {
|
|
160
162
|
includeUserScope: config?.scan_user_scope === true,
|
|
163
|
+
collectModes: config?.scan_collection_modes,
|
|
164
|
+
collectKinds: config?.scan_collection_kinds,
|
|
161
165
|
}),
|
|
162
166
|
discoverLocalTextTargets: (_scanTarget, _config, discoveryContext) => discoveryContext ? discoverLocalTextAnalysisTargetsFromContext(discoveryContext) : [],
|
|
163
167
|
// Deep resource execution never makes outbound network calls.
|
|
@@ -198,6 +202,20 @@ function addScanCommand(program, version, deps) {
|
|
|
198
202
|
.option("--config <path>", "use a specific global config file")
|
|
199
203
|
.option("--force", "skip interactive confirmations")
|
|
200
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")
|
|
201
219
|
.option("--skill <name>", "select one skill directory when scanning a skills index repo URL")
|
|
202
220
|
.option("--reset-state", "clear persisted scan-state history and exit")
|
|
203
221
|
.addHelpText("after", renderExampleHelp([
|
|
@@ -205,6 +223,7 @@ function addScanCommand(program, version, deps) {
|
|
|
205
223
|
"codegate scan ./skills/security-review/SKILL.md",
|
|
206
224
|
"codegate scan https://github.com/owner/repo",
|
|
207
225
|
"codegate scan https://github.com/owner/repo --skill security-review",
|
|
226
|
+
"codegate scan . --workflow-audits --collect project --persona auditor --runtime-mode online",
|
|
208
227
|
"codegate scan https://github.com/owner/repo/blob/main/skills/security-review/SKILL.md",
|
|
209
228
|
"codegate scan https://example.com/security-review/SKILL.md --format json",
|
|
210
229
|
]))
|
|
@@ -220,6 +239,7 @@ function addScanCommand(program, version, deps) {
|
|
|
220
239
|
};
|
|
221
240
|
let resolvedTarget;
|
|
222
241
|
try {
|
|
242
|
+
const scanOptions = options;
|
|
223
243
|
const resolveTarget = deps.resolveScanTarget ??
|
|
224
244
|
((input) => resolveScanTarget(input));
|
|
225
245
|
resolvedTarget = await resolveTarget({
|
|
@@ -237,12 +257,26 @@ function addScanCommand(program, version, deps) {
|
|
|
237
257
|
scanTarget,
|
|
238
258
|
cli: cliConfig,
|
|
239
259
|
});
|
|
240
|
-
const config =
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
:
|
|
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
|
+
};
|
|
246
280
|
if (options.resetState) {
|
|
247
281
|
const reset = deps.resetScanState ?? ((path) => resetScanState(path));
|
|
248
282
|
await reset(config.scan_state_path);
|
|
@@ -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";
|
|
@@ -20,7 +20,7 @@ export async function executeScanContentCommand(input, deps) {
|
|
|
20
20
|
throw new Error(parsed.error);
|
|
21
21
|
}
|
|
22
22
|
const kbVersion = loadKnowledgeBase().schemaVersion;
|
|
23
|
-
const report = applyConfigPolicy(runStaticPipeline({
|
|
23
|
+
const report = applyConfigPolicy(await runStaticPipeline({
|
|
24
24
|
version: input.version,
|
|
25
25
|
kbVersion,
|
|
26
26
|
scanTarget: `scan-content:${input.type}`,
|
|
@@ -48,6 +48,10 @@ export async function executeScanContentCommand(input, deps) {
|
|
|
48
48
|
rulePackPaths: input.config.rule_pack_paths,
|
|
49
49
|
allowedRules: input.config.allowed_rules,
|
|
50
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,
|
|
51
55
|
},
|
|
52
56
|
}), input.config);
|
|
53
57
|
deps.stdout(renderByFormat(input.config.output_format, report));
|
|
@@ -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
|
+
}
|
|
@@ -2,13 +2,21 @@ import type { Finding } from "../types/finding.js";
|
|
|
2
2
|
export interface SuppressionRule {
|
|
3
3
|
rule_id?: string;
|
|
4
4
|
file_path?: string;
|
|
5
|
+
location?: string;
|
|
5
6
|
severity?: Finding["severity"];
|
|
6
7
|
category?: Finding["category"];
|
|
7
8
|
cwe?: string;
|
|
8
9
|
fingerprint?: string;
|
|
9
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>;
|
|
10
17
|
export interface SuppressionPolicy {
|
|
11
18
|
suppress_findings?: readonly string[];
|
|
12
19
|
suppression_rules?: readonly SuppressionRule[];
|
|
20
|
+
rule_policies?: RulePolicyMap;
|
|
13
21
|
}
|
|
14
22
|
export declare function applySuppressionPolicy<T extends Finding>(findings: T[], policy: SuppressionPolicy): T[];
|
|
@@ -38,6 +38,33 @@ function globToRegExp(glob) {
|
|
|
38
38
|
function matchesGlob(value, glob) {
|
|
39
39
|
return globToRegExp(glob).test(value.replaceAll("\\", "/"));
|
|
40
40
|
}
|
|
41
|
+
function parseSuppressionLocation(value) {
|
|
42
|
+
const trimmed = normalizeString(value);
|
|
43
|
+
if (!trimmed) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const pieces = trimmed.split(":");
|
|
47
|
+
if (pieces.length === 0 || pieces.length > 3) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const [filePath, lineRaw, columnRaw] = pieces;
|
|
51
|
+
if (!filePath) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const line = lineRaw !== undefined ? Number.parseInt(lineRaw, 10) : undefined;
|
|
55
|
+
const column = columnRaw !== undefined ? Number.parseInt(columnRaw, 10) : undefined;
|
|
56
|
+
if (lineRaw !== undefined && (!Number.isFinite(line) || (line ?? 0) < 1)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (columnRaw !== undefined && (!Number.isFinite(column) || (column ?? 0) < 1)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
filePath: filePath.replaceAll("\\", "/"),
|
|
64
|
+
line,
|
|
65
|
+
column,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
41
68
|
function matchesSuppressionRule(finding, rule) {
|
|
42
69
|
const ruleId = normalizeString(rule.rule_id);
|
|
43
70
|
if (ruleId && finding.rule_id !== ruleId) {
|
|
@@ -47,6 +74,21 @@ function matchesSuppressionRule(finding, rule) {
|
|
|
47
74
|
if (filePath && !matchesGlob(finding.file_path, filePath)) {
|
|
48
75
|
return false;
|
|
49
76
|
}
|
|
77
|
+
const location = parseSuppressionLocation(rule.location ?? "");
|
|
78
|
+
if (location) {
|
|
79
|
+
const normalizedFindingPath = finding.file_path.replaceAll("\\", "/");
|
|
80
|
+
if (normalizedFindingPath !== location.filePath) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (typeof location.line === "number" &&
|
|
84
|
+
(typeof finding.location.line !== "number" || finding.location.line !== location.line)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if (typeof location.column === "number" &&
|
|
88
|
+
(typeof finding.location.column !== "number" || finding.location.column !== location.column)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
50
92
|
const severity = rule.severity;
|
|
51
93
|
if (severity && finding.severity !== severity) {
|
|
52
94
|
return false;
|
|
@@ -65,17 +107,40 @@ function matchesSuppressionRule(finding, rule) {
|
|
|
65
107
|
}
|
|
66
108
|
return true;
|
|
67
109
|
}
|
|
110
|
+
function matchesRulePolicyIgnore(finding, location) {
|
|
111
|
+
const parsed = parseSuppressionLocation(location);
|
|
112
|
+
if (!parsed) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const normalizedFindingPath = finding.file_path.replaceAll("\\", "/");
|
|
116
|
+
if (normalizedFindingPath !== parsed.filePath) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (typeof parsed.line === "number" &&
|
|
120
|
+
(typeof finding.location.line !== "number" || finding.location.line !== parsed.line)) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
if (typeof parsed.column === "number" &&
|
|
124
|
+
(typeof finding.location.column !== "number" || finding.location.column !== parsed.column)) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
68
129
|
export function applySuppressionPolicy(findings, policy) {
|
|
69
130
|
const legacySuppressions = new Set((policy.suppress_findings ?? [])
|
|
70
131
|
.map((findingId) => normalizeString(findingId))
|
|
71
132
|
.filter((findingId) => findingId !== undefined));
|
|
72
133
|
const rules = policy.suppression_rules ?? [];
|
|
134
|
+
const rulePolicies = policy.rule_policies ?? {};
|
|
73
135
|
return findings.map((finding) => {
|
|
74
136
|
const ruleMatch = rules.some((rule) => matchesSuppressionRule(finding, rule));
|
|
75
137
|
const legacyMatch = legacySuppressions.has(finding.finding_id);
|
|
138
|
+
const rulePolicy = rulePolicies[finding.rule_id];
|
|
139
|
+
const ruleDisabled = rulePolicy?.disable === true;
|
|
140
|
+
const ruleIgnoreMatch = rulePolicy?.ignore?.some((location) => matchesRulePolicyIgnore(finding, location)) ?? false;
|
|
76
141
|
return {
|
|
77
142
|
...finding,
|
|
78
|
-
suppressed: finding.suppressed || legacyMatch || ruleMatch,
|
|
143
|
+
suppressed: finding.suppressed || legacyMatch || ruleMatch || ruleDisabled || ruleIgnoreMatch,
|
|
79
144
|
};
|
|
80
145
|
});
|
|
81
146
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
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
|
+
import { type RulePolicyMap, type SuppressionRule } from "./config/suppression-policy.js";
|
|
4
4
|
export declare const OUTPUT_FORMATS: readonly ["terminal", "json", "sarif", "markdown", "html"];
|
|
5
5
|
export type OutputFormat = (typeof OUTPUT_FORMATS)[number];
|
|
6
|
+
export declare const SCAN_COLLECTION_MODES: readonly ["default", "project", "user", "explicit", "all"];
|
|
7
|
+
export type ScanCollectionMode = (typeof SCAN_COLLECTION_MODES)[number];
|
|
8
|
+
export declare const SCAN_COLLECTION_KINDS: readonly ["workflows", "actions", "dependabot"];
|
|
9
|
+
export type ScanCollectionKind = (typeof SCAN_COLLECTION_KINDS)[number];
|
|
10
|
+
export declare const PERSONAS: readonly ["regular", "pedantic", "auditor"];
|
|
11
|
+
export type AuditPersona = (typeof PERSONAS)[number];
|
|
12
|
+
export declare const RUNTIME_MODES: readonly ["offline", "online", "online-no-audits"];
|
|
13
|
+
export type RuntimeMode = (typeof RUNTIME_MODES)[number];
|
|
6
14
|
export declare const SEVERITY_THRESHOLDS: readonly ["critical", "high", "medium", "low", "info"];
|
|
7
15
|
export type SeverityThreshold = (typeof SEVERITY_THRESHOLDS)[number];
|
|
8
16
|
export interface TuiConfig {
|
|
@@ -15,6 +23,9 @@ export interface ToolDiscoveryConfig {
|
|
|
15
23
|
agent_paths: Record<string, string>;
|
|
16
24
|
skip_tools: string[];
|
|
17
25
|
}
|
|
26
|
+
export interface WorkflowAuditConfig {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
}
|
|
18
29
|
export interface CodeGateConfig {
|
|
19
30
|
severity_threshold: SeverityThreshold;
|
|
20
31
|
auto_proceed_below_threshold: boolean;
|
|
@@ -36,6 +47,13 @@ export interface CodeGateConfig {
|
|
|
36
47
|
rule_pack_paths?: string[];
|
|
37
48
|
allowed_rules?: string[];
|
|
38
49
|
skip_rules?: string[];
|
|
50
|
+
strict_collection?: boolean;
|
|
51
|
+
scan_collection_modes?: ScanCollectionMode[];
|
|
52
|
+
scan_collection_kinds?: ScanCollectionKind[];
|
|
53
|
+
rules?: RulePolicyMap;
|
|
54
|
+
persona?: AuditPersona;
|
|
55
|
+
runtime_mode?: RuntimeMode;
|
|
56
|
+
workflow_audits?: WorkflowAuditConfig;
|
|
39
57
|
suppress_findings: string[];
|
|
40
58
|
suppression_rules?: SuppressionRule[];
|
|
41
59
|
}
|
package/dist/config.js
CHANGED
|
@@ -3,8 +3,12 @@ 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
|
+
import { applySuppressionPolicy, } from "./config/suppression-policy.js";
|
|
7
7
|
export const OUTPUT_FORMATS = ["terminal", "json", "sarif", "markdown", "html"];
|
|
8
|
+
export const SCAN_COLLECTION_MODES = ["default", "project", "user", "explicit", "all"];
|
|
9
|
+
export const SCAN_COLLECTION_KINDS = ["workflows", "actions", "dependabot"];
|
|
10
|
+
export const PERSONAS = ["regular", "pedantic", "auditor"];
|
|
11
|
+
export const RUNTIME_MODES = ["offline", "online", "online-no-audits"];
|
|
8
12
|
export const SEVERITY_THRESHOLDS = ["critical", "high", "medium", "low", "info"];
|
|
9
13
|
export const DEFAULT_CONFIG = {
|
|
10
14
|
severity_threshold: "high",
|
|
@@ -38,6 +42,11 @@ export const DEFAULT_CONFIG = {
|
|
|
38
42
|
rule_pack_paths: [],
|
|
39
43
|
allowed_rules: [],
|
|
40
44
|
skip_rules: [],
|
|
45
|
+
strict_collection: false,
|
|
46
|
+
scan_collection_modes: ["default"],
|
|
47
|
+
persona: "regular",
|
|
48
|
+
runtime_mode: "offline",
|
|
49
|
+
workflow_audits: { enabled: false },
|
|
41
50
|
suppress_findings: [],
|
|
42
51
|
suppression_rules: [],
|
|
43
52
|
};
|
|
@@ -60,6 +69,146 @@ function normalizeOptionalPath(value) {
|
|
|
60
69
|
const trimmed = value.trim();
|
|
61
70
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
62
71
|
}
|
|
72
|
+
function normalizeCollectionMode(value) {
|
|
73
|
+
if (!value) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return SCAN_COLLECTION_MODES.find((mode) => mode === value) ?? undefined;
|
|
77
|
+
}
|
|
78
|
+
function normalizeCollectionModes(values) {
|
|
79
|
+
if (!values) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const normalized = [];
|
|
83
|
+
for (const value of values) {
|
|
84
|
+
const mode = normalizeCollectionMode(value);
|
|
85
|
+
if (!mode || normalized.includes(mode)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
normalized.push(mode);
|
|
89
|
+
}
|
|
90
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
91
|
+
}
|
|
92
|
+
function normalizeCollectionKind(value) {
|
|
93
|
+
if (!value) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
return SCAN_COLLECTION_KINDS.find((kind) => kind === value) ?? undefined;
|
|
97
|
+
}
|
|
98
|
+
function normalizeCollectionKinds(values) {
|
|
99
|
+
if (!values) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const normalized = [];
|
|
103
|
+
for (const value of values) {
|
|
104
|
+
const kind = normalizeCollectionKind(value);
|
|
105
|
+
if (!kind || normalized.includes(kind)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
normalized.push(kind);
|
|
109
|
+
}
|
|
110
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
111
|
+
}
|
|
112
|
+
function isPlainObject(value) {
|
|
113
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
114
|
+
}
|
|
115
|
+
function normalizeRulePolicyConfig(value) {
|
|
116
|
+
if (!isPlainObject(value)) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const disable = typeof value.disable === "boolean" ? value.disable : undefined;
|
|
120
|
+
const ignoreRaw = value.ignore;
|
|
121
|
+
const ignore = Array.isArray(ignoreRaw) && ignoreRaw.length > 0
|
|
122
|
+
? ignoreRaw
|
|
123
|
+
.filter((entry) => typeof entry === "string")
|
|
124
|
+
.map((entry) => entry.trim())
|
|
125
|
+
.filter((entry) => entry.length > 0)
|
|
126
|
+
: undefined;
|
|
127
|
+
const configRaw = value.config;
|
|
128
|
+
const config = isPlainObject(configRaw) ? { ...configRaw } : undefined;
|
|
129
|
+
if (disable === undefined && ignore === undefined && config === undefined) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
disable,
|
|
134
|
+
ignore,
|
|
135
|
+
config,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function normalizeRulePolicyMap(rules) {
|
|
139
|
+
if (!rules || typeof rules !== "object") {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
const normalized = {};
|
|
143
|
+
for (const [ruleId, value] of Object.entries(rules)) {
|
|
144
|
+
const key = ruleId.trim();
|
|
145
|
+
if (key.length === 0) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const policy = normalizeRulePolicyConfig(value);
|
|
149
|
+
if (!policy) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
normalized[key] = policy;
|
|
153
|
+
}
|
|
154
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
155
|
+
}
|
|
156
|
+
function mergeRulePolicyConfig(current, incoming) {
|
|
157
|
+
if (!current) {
|
|
158
|
+
if (!incoming) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
disable: incoming.disable,
|
|
163
|
+
ignore: incoming.ignore ? [...incoming.ignore] : undefined,
|
|
164
|
+
config: incoming.config ? { ...incoming.config } : undefined,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (!incoming) {
|
|
168
|
+
return {
|
|
169
|
+
disable: current.disable,
|
|
170
|
+
ignore: current.ignore ? [...current.ignore] : undefined,
|
|
171
|
+
config: current.config ? { ...current.config } : undefined,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
disable: incoming.disable ?? current.disable,
|
|
176
|
+
ignore: unique([
|
|
177
|
+
current.ignore,
|
|
178
|
+
incoming.ignore,
|
|
179
|
+
]),
|
|
180
|
+
config: current.config || incoming.config
|
|
181
|
+
? {
|
|
182
|
+
...(current.config ?? {}),
|
|
183
|
+
...(incoming.config ?? {}),
|
|
184
|
+
}
|
|
185
|
+
: undefined,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function mergeRulePolicyMaps(...maps) {
|
|
189
|
+
const merged = {};
|
|
190
|
+
for (const map of maps) {
|
|
191
|
+
if (!map) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
for (const [ruleId, policy] of Object.entries(map)) {
|
|
195
|
+
merged[ruleId] = mergeRulePolicyConfig(merged[ruleId], policy) ?? merged[ruleId];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
199
|
+
}
|
|
200
|
+
function normalizePersona(value) {
|
|
201
|
+
if (!value) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
return PERSONAS.find((persona) => persona === value) ?? undefined;
|
|
205
|
+
}
|
|
206
|
+
function normalizeRuntimeMode(value) {
|
|
207
|
+
if (!value) {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
return RUNTIME_MODES.find((mode) => mode === value) ?? undefined;
|
|
211
|
+
}
|
|
63
212
|
function unique(values) {
|
|
64
213
|
const merged = values.flatMap((entry) => entry ?? []);
|
|
65
214
|
const seen = new Set();
|
|
@@ -183,6 +332,15 @@ export function resolveEffectiveConfig(options) {
|
|
|
183
332
|
globalConfig.skip_rules,
|
|
184
333
|
projectConfig.skip_rules,
|
|
185
334
|
]),
|
|
335
|
+
strict_collection: pickFirst(projectConfig.strict_collection, globalConfig.strict_collection, DEFAULT_CONFIG.strict_collection) ?? DEFAULT_CONFIG.strict_collection,
|
|
336
|
+
scan_collection_modes: pickFirst(normalizeCollectionModes(projectConfig.scan_collection_modes), normalizeCollectionModes(globalConfig.scan_collection_modes), DEFAULT_CONFIG.scan_collection_modes) ?? DEFAULT_CONFIG.scan_collection_modes,
|
|
337
|
+
scan_collection_kinds: pickFirst(normalizeCollectionKinds(projectConfig.scan_collection_kinds), normalizeCollectionKinds(globalConfig.scan_collection_kinds)) ?? undefined,
|
|
338
|
+
rules: mergeRulePolicyMaps(normalizeRulePolicyMap(globalConfig.rules), normalizeRulePolicyMap(projectConfig.rules)) ?? undefined,
|
|
339
|
+
persona: pickFirst(normalizePersona(projectConfig.persona), normalizePersona(globalConfig.persona), DEFAULT_CONFIG.persona) ?? DEFAULT_CONFIG.persona,
|
|
340
|
+
runtime_mode: pickFirst(normalizeRuntimeMode(projectConfig.runtime_mode), normalizeRuntimeMode(globalConfig.runtime_mode), DEFAULT_CONFIG.runtime_mode) ?? DEFAULT_CONFIG.runtime_mode,
|
|
341
|
+
workflow_audits: {
|
|
342
|
+
enabled: pickFirst(projectConfig.workflow_audits?.enabled, globalConfig.workflow_audits?.enabled, DEFAULT_CONFIG.workflow_audits?.enabled) ?? false,
|
|
343
|
+
},
|
|
186
344
|
suppress_findings: unique([
|
|
187
345
|
DEFAULT_CONFIG.suppress_findings,
|
|
188
346
|
globalConfig.suppress_findings,
|
|
@@ -202,6 +360,7 @@ export function applyConfigPolicy(report, config) {
|
|
|
202
360
|
const findings = applySuppressionPolicy(report.findings, {
|
|
203
361
|
suppress_findings: config.suppress_findings,
|
|
204
362
|
suppression_rules: config.suppression_rules,
|
|
363
|
+
rule_policies: config.rules,
|
|
205
364
|
}).map((finding) => ({
|
|
206
365
|
...finding,
|
|
207
366
|
owasp: config.owasp_mapping ? finding.owasp : [],
|