codegate-ai 0.8.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.
- 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/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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { buildFindingEvidence } from "../evidence.js";
|
|
2
|
+
import { extractWorkflowFacts, isGitHubWorkflowPath } from "../workflow/parser.js";
|
|
3
|
+
const PRIVILEGED_COMMANDS = ["publish", "deploy", "release", "gh release"];
|
|
4
|
+
const UNTRUSTED_CONTAINS_PATTERNS = [
|
|
5
|
+
/contains\s*\(\s*github\.event\.pull_request\.(?:title|body)/iu,
|
|
6
|
+
/contains\s*\(\s*github\.event\.comment\.body/iu,
|
|
7
|
+
/contains\s*\(\s*github\.event\.issue\.title/iu,
|
|
8
|
+
];
|
|
9
|
+
function asRecord(value) {
|
|
10
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function gatherStepConditions(parsed) {
|
|
16
|
+
const root = asRecord(parsed);
|
|
17
|
+
const jobs = asRecord(root?.jobs);
|
|
18
|
+
if (!jobs) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const candidates = [];
|
|
22
|
+
for (const [jobId, jobValue] of Object.entries(jobs)) {
|
|
23
|
+
const job = asRecord(jobValue);
|
|
24
|
+
const steps = Array.isArray(job?.steps) ? job.steps : [];
|
|
25
|
+
steps.forEach((stepValue, stepIndex) => {
|
|
26
|
+
const step = asRecord(stepValue);
|
|
27
|
+
if (!step || typeof step.if !== "string") {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
candidates.push({
|
|
31
|
+
jobId,
|
|
32
|
+
stepIndex,
|
|
33
|
+
condition: step.if,
|
|
34
|
+
run: typeof step.run === "string" ? step.run : undefined,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return candidates;
|
|
39
|
+
}
|
|
40
|
+
function isUnsoundContains(condition) {
|
|
41
|
+
return UNTRUSTED_CONTAINS_PATTERNS.some((pattern) => pattern.test(condition));
|
|
42
|
+
}
|
|
43
|
+
function isPrivilegedStep(run) {
|
|
44
|
+
const normalized = run?.toLowerCase() ?? "";
|
|
45
|
+
return PRIVILEGED_COMMANDS.some((command) => normalized.includes(command));
|
|
46
|
+
}
|
|
47
|
+
export function detectWorkflowUnsoundContains(input) {
|
|
48
|
+
if (!isGitHubWorkflowPath(input.filePath)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const facts = extractWorkflowFacts(input.parsed);
|
|
52
|
+
if (!facts) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const findings = [];
|
|
56
|
+
for (const candidate of gatherStepConditions(input.parsed)) {
|
|
57
|
+
if (!isUnsoundContains(candidate.condition) || !isPrivilegedStep(candidate.run)) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const evidence = buildFindingEvidence({
|
|
61
|
+
textContent: input.textContent,
|
|
62
|
+
searchTerms: [candidate.condition, candidate.run ?? ""],
|
|
63
|
+
fallbackValue: candidate.condition,
|
|
64
|
+
});
|
|
65
|
+
findings.push({
|
|
66
|
+
rule_id: "workflow-unsound-contains",
|
|
67
|
+
finding_id: `WORKFLOW_UNSOUND_CONTAINS-${input.filePath}-${candidate.jobId}-${candidate.stepIndex}`,
|
|
68
|
+
severity: "HIGH",
|
|
69
|
+
category: "CI_TRIGGER",
|
|
70
|
+
layer: "L2",
|
|
71
|
+
file_path: input.filePath,
|
|
72
|
+
location: { field: `jobs.${candidate.jobId}.steps[${candidate.stepIndex}].if` },
|
|
73
|
+
description: "Workflow gates a privileged step with contains() over untrusted event content",
|
|
74
|
+
affected_tools: ["github-actions"],
|
|
75
|
+
cve: null,
|
|
76
|
+
owasp: ["ASI02"],
|
|
77
|
+
cwe: "CWE-20",
|
|
78
|
+
confidence: "HIGH",
|
|
79
|
+
fixable: false,
|
|
80
|
+
remediation_actions: [
|
|
81
|
+
"Avoid trust decisions based on contains() over untrusted titles, bodies, or comments",
|
|
82
|
+
],
|
|
83
|
+
evidence: evidence?.evidence ?? null,
|
|
84
|
+
suppressed: false,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return findings;
|
|
88
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Finding } from "../../types/finding.js";
|
|
2
|
+
export interface WorkflowUseTrustedPublishingInput {
|
|
3
|
+
filePath: string;
|
|
4
|
+
parsed: unknown;
|
|
5
|
+
textContent: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function detectWorkflowUseTrustedPublishing(input: WorkflowUseTrustedPublishingInput): Finding[];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { buildFindingEvidence } from "../evidence.js";
|
|
2
|
+
import { extractWorkflowFacts, isGitHubWorkflowPath } from "../workflow/parser.js";
|
|
3
|
+
const PUBLISH_COMMANDS = ["npm publish", "pnpm publish", "yarn npm publish", "twine upload"];
|
|
4
|
+
const TOKEN_NAMES = ["NODE_AUTH_TOKEN", "NPM_TOKEN", "TWINE_USERNAME", "TWINE_PASSWORD"];
|
|
5
|
+
function containsPublishCommand(value) {
|
|
6
|
+
if (!value) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return PUBLISH_COMMANDS.some((command) => value.includes(command));
|
|
10
|
+
}
|
|
11
|
+
function findTokenName(textContent) {
|
|
12
|
+
for (const tokenName of TOKEN_NAMES) {
|
|
13
|
+
if (textContent.includes(tokenName)) {
|
|
14
|
+
return tokenName;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
export function detectWorkflowUseTrustedPublishing(input) {
|
|
20
|
+
if (!isGitHubWorkflowPath(input.filePath)) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const facts = extractWorkflowFacts(input.parsed);
|
|
24
|
+
if (!facts) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const tokenName = findTokenName(input.textContent);
|
|
28
|
+
if (!tokenName) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const findings = [];
|
|
32
|
+
facts.jobs.forEach((job, jobIndex) => {
|
|
33
|
+
job.steps.forEach((step, stepIndex) => {
|
|
34
|
+
if (!containsPublishCommand(step.run)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const evidence = buildFindingEvidence({
|
|
38
|
+
textContent: input.textContent,
|
|
39
|
+
searchTerms: [step.run ?? "", tokenName],
|
|
40
|
+
fallbackValue: step.run ?? tokenName,
|
|
41
|
+
});
|
|
42
|
+
findings.push({
|
|
43
|
+
rule_id: "workflow-use-trusted-publishing",
|
|
44
|
+
finding_id: `WORKFLOW_USE_TRUSTED_PUBLISHING-${input.filePath}-${jobIndex}-${stepIndex}`,
|
|
45
|
+
severity: "HIGH",
|
|
46
|
+
category: "CI_PERMISSIONS",
|
|
47
|
+
layer: "L2",
|
|
48
|
+
file_path: input.filePath,
|
|
49
|
+
location: { field: `jobs.${job.id}.steps[${stepIndex}].run` },
|
|
50
|
+
description: "Package publication uses long-lived registry credentials instead of trusted publishing",
|
|
51
|
+
affected_tools: ["github-actions"],
|
|
52
|
+
cve: null,
|
|
53
|
+
owasp: ["ASI02"],
|
|
54
|
+
cwe: "CWE-798",
|
|
55
|
+
confidence: "HIGH",
|
|
56
|
+
fixable: false,
|
|
57
|
+
remediation_actions: [
|
|
58
|
+
"Switch package publishing to trusted publishing with OIDC and remove registry secrets",
|
|
59
|
+
],
|
|
60
|
+
evidence: evidence?.evidence ?? null,
|
|
61
|
+
suppressed: false,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
return findings;
|
|
66
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type GitHookEntry } from "./detectors/git-hooks.js";
|
|
2
2
|
import { type SymlinkEscapeEntry } from "./detectors/symlink.js";
|
|
3
|
+
import type { AuditPersona, RuntimeMode } from "../config.js";
|
|
3
4
|
import { type Finding } from "../types/finding.js";
|
|
4
5
|
import type { DiscoveryFormat } from "../types/discovery.js";
|
|
5
6
|
export interface StaticFileInput {
|
|
@@ -20,6 +21,13 @@ export interface StaticEngineConfig {
|
|
|
20
21
|
rulePackPaths?: string[];
|
|
21
22
|
allowedRules?: string[];
|
|
22
23
|
skipRules?: string[];
|
|
24
|
+
persona?: AuditPersona;
|
|
25
|
+
runtimeMode?: RuntimeMode;
|
|
26
|
+
workflowAuditsEnabled?: boolean;
|
|
27
|
+
rulePolicies?: Record<string, {
|
|
28
|
+
disable?: boolean;
|
|
29
|
+
config?: Record<string, unknown>;
|
|
30
|
+
}>;
|
|
23
31
|
}
|
|
24
32
|
export interface StaticEngineInput {
|
|
25
33
|
projectRoot: string;
|
|
@@ -28,4 +36,4 @@ export interface StaticEngineInput {
|
|
|
28
36
|
hooks: GitHookEntry[];
|
|
29
37
|
config: StaticEngineConfig;
|
|
30
38
|
}
|
|
31
|
-
export declare function runStaticEngine(input: StaticEngineInput): Finding[]
|
|
39
|
+
export declare function runStaticEngine(input: StaticEngineInput): Promise<Finding[]>;
|