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,165 @@
|
|
|
1
|
+
function asRecord(value) {
|
|
2
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
function asString(value) {
|
|
8
|
+
return typeof value === "string" ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
function asBoolean(value) {
|
|
11
|
+
return typeof value === "boolean" ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
function asStringRecord(value) {
|
|
14
|
+
const record = asRecord(value);
|
|
15
|
+
if (!record) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
20
|
+
if (typeof entry === "string") {
|
|
21
|
+
result[key] = entry;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
25
|
+
}
|
|
26
|
+
function asStringArray(value) {
|
|
27
|
+
if (!Array.isArray(value)) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const result = value.filter((entry) => typeof entry === "string");
|
|
31
|
+
return result.length > 0 ? result : undefined;
|
|
32
|
+
}
|
|
33
|
+
function normalizeActionPath(value) {
|
|
34
|
+
return value.replaceAll("\\", "/");
|
|
35
|
+
}
|
|
36
|
+
export function isGitHubActionPath(path) {
|
|
37
|
+
return /(?:^|\/)action\.ya?ml$/iu.test(normalizeActionPath(path));
|
|
38
|
+
}
|
|
39
|
+
function extractStepFacts(step) {
|
|
40
|
+
const stepRecord = asRecord(step);
|
|
41
|
+
if (!stepRecord) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const withEntries = asStringRecord(stepRecord.with);
|
|
45
|
+
const envEntries = asStringRecord(stepRecord.env);
|
|
46
|
+
const stepFacts = {
|
|
47
|
+
id: asString(stepRecord.id),
|
|
48
|
+
name: asString(stepRecord.name),
|
|
49
|
+
uses: asString(stepRecord.uses),
|
|
50
|
+
run: asString(stepRecord.run),
|
|
51
|
+
if: asString(stepRecord.if),
|
|
52
|
+
shell: asString(stepRecord.shell),
|
|
53
|
+
workingDirectory: asString(stepRecord["working-directory"]) ?? asString(stepRecord.workingDirectory),
|
|
54
|
+
with: withEntries,
|
|
55
|
+
env: envEntries,
|
|
56
|
+
};
|
|
57
|
+
if (!stepFacts.uses && !stepFacts.run) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return stepFacts;
|
|
61
|
+
}
|
|
62
|
+
function extractInputs(value) {
|
|
63
|
+
const inputs = asRecord(value);
|
|
64
|
+
if (!inputs) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const result = {};
|
|
68
|
+
for (const [name, inputValue] of Object.entries(inputs)) {
|
|
69
|
+
const inputRecord = asRecord(inputValue);
|
|
70
|
+
if (!inputRecord) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const entry = {
|
|
74
|
+
description: asString(inputRecord.description),
|
|
75
|
+
required: asBoolean(inputRecord.required),
|
|
76
|
+
default: asString(inputRecord.default),
|
|
77
|
+
deprecationMessage: asString(inputRecord.deprecationMessage) ?? asString(inputRecord.deprecation_message),
|
|
78
|
+
};
|
|
79
|
+
if (entry.description !== undefined ||
|
|
80
|
+
entry.required !== undefined ||
|
|
81
|
+
entry.default !== undefined ||
|
|
82
|
+
entry.deprecationMessage !== undefined) {
|
|
83
|
+
result[name] = entry;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
87
|
+
}
|
|
88
|
+
function extractOutputs(value) {
|
|
89
|
+
const outputs = asRecord(value);
|
|
90
|
+
if (!outputs) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const result = {};
|
|
94
|
+
for (const [name, outputValue] of Object.entries(outputs)) {
|
|
95
|
+
const outputRecord = asRecord(outputValue);
|
|
96
|
+
if (!outputRecord) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const entry = {
|
|
100
|
+
description: asString(outputRecord.description),
|
|
101
|
+
value: asString(outputRecord.value),
|
|
102
|
+
};
|
|
103
|
+
if (entry.description !== undefined || entry.value !== undefined) {
|
|
104
|
+
result[name] = entry;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
108
|
+
}
|
|
109
|
+
function extractRuns(value) {
|
|
110
|
+
const runs = asRecord(value);
|
|
111
|
+
if (!runs) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const stepsRaw = Array.isArray(runs.steps) ? runs.steps : [];
|
|
115
|
+
const steps = stepsRaw
|
|
116
|
+
.map((step) => extractStepFacts(step))
|
|
117
|
+
.filter((step) => step !== null);
|
|
118
|
+
const result = {
|
|
119
|
+
using: asString(runs.using),
|
|
120
|
+
main: asString(runs.main),
|
|
121
|
+
pre: asString(runs.pre),
|
|
122
|
+
post: asString(runs.post),
|
|
123
|
+
image: asString(runs.image),
|
|
124
|
+
args: asStringArray(runs.args),
|
|
125
|
+
steps: steps.length > 0 ? steps : undefined,
|
|
126
|
+
};
|
|
127
|
+
if (result.using === undefined &&
|
|
128
|
+
result.main === undefined &&
|
|
129
|
+
result.pre === undefined &&
|
|
130
|
+
result.post === undefined &&
|
|
131
|
+
result.image === undefined &&
|
|
132
|
+
result.args === undefined &&
|
|
133
|
+
result.steps === undefined) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
export function extractActionFacts(parsed) {
|
|
139
|
+
const root = asRecord(parsed);
|
|
140
|
+
if (!root) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const runs = extractRuns(root.runs);
|
|
144
|
+
const inputs = extractInputs(root.inputs);
|
|
145
|
+
const outputs = extractOutputs(root.outputs);
|
|
146
|
+
const branding = asRecord(root.branding) ?? undefined;
|
|
147
|
+
if (runs === undefined &&
|
|
148
|
+
inputs === undefined &&
|
|
149
|
+
outputs === undefined &&
|
|
150
|
+
branding === undefined &&
|
|
151
|
+
asString(root.name) === undefined &&
|
|
152
|
+
asString(root.description) === undefined &&
|
|
153
|
+
asString(root.author) === undefined) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
name: asString(root.name),
|
|
158
|
+
description: asString(root.description),
|
|
159
|
+
author: asString(root.author),
|
|
160
|
+
branding,
|
|
161
|
+
inputs,
|
|
162
|
+
outputs,
|
|
163
|
+
runs,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ActionStepFacts {
|
|
2
|
+
id?: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
uses?: string;
|
|
5
|
+
run?: string;
|
|
6
|
+
if?: string;
|
|
7
|
+
shell?: string;
|
|
8
|
+
workingDirectory?: string;
|
|
9
|
+
with?: Record<string, string>;
|
|
10
|
+
env?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
export interface ActionRunsFacts {
|
|
13
|
+
using?: string;
|
|
14
|
+
main?: string;
|
|
15
|
+
pre?: string;
|
|
16
|
+
post?: string;
|
|
17
|
+
image?: string;
|
|
18
|
+
args?: string[];
|
|
19
|
+
steps?: ActionStepFacts[];
|
|
20
|
+
}
|
|
21
|
+
export interface ActionInputFacts {
|
|
22
|
+
description?: string;
|
|
23
|
+
required?: boolean;
|
|
24
|
+
default?: string;
|
|
25
|
+
deprecationMessage?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ActionOutputFacts {
|
|
28
|
+
description?: string;
|
|
29
|
+
value?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ActionFacts {
|
|
32
|
+
name?: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
author?: string;
|
|
35
|
+
branding?: Record<string, unknown>;
|
|
36
|
+
inputs?: Record<string, ActionInputFacts>;
|
|
37
|
+
outputs?: Record<string, ActionOutputFacts>;
|
|
38
|
+
runs?: ActionRunsFacts;
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface AdvisoryPayload {
|
|
2
|
+
generatedAt: number;
|
|
3
|
+
advisories: Record<string, string[]>;
|
|
4
|
+
}
|
|
5
|
+
export declare function loadCachedAdvisoryPayload(cacheDir: string, maxAgeMs: number, now?: number): AdvisoryPayload | null;
|
|
6
|
+
export declare function saveCachedAdvisoryPayload(cacheDir: string, payload: AdvisoryPayload): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const CACHE_FILE = "gha-advisories.json";
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function isAdvisoryPayload(value) {
|
|
8
|
+
if (!isRecord(value)) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (typeof value.generatedAt !== "number") {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
if (!isRecord(value.advisories)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return Object.values(value.advisories).every((entry) => Array.isArray(entry) && entry.every((item) => typeof item === "string"));
|
|
18
|
+
}
|
|
19
|
+
function cachePath(cacheDir) {
|
|
20
|
+
return join(cacheDir, CACHE_FILE);
|
|
21
|
+
}
|
|
22
|
+
export function loadCachedAdvisoryPayload(cacheDir, maxAgeMs, now = Date.now()) {
|
|
23
|
+
const path = cachePath(cacheDir);
|
|
24
|
+
if (!existsSync(path)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
29
|
+
if (!isAdvisoryPayload(parsed)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (now - parsed.generatedAt > maxAgeMs) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function saveCachedAdvisoryPayload(cacheDir, payload) {
|
|
42
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
43
|
+
writeFileSync(cachePath(cacheDir), JSON.stringify(payload, null, 2), "utf8");
|
|
44
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface AdvisoryPayload {
|
|
5
|
+
generatedAt: number;
|
|
6
|
+
advisories: Record<string, string[]>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const CACHE_FILE = "gha-advisories.json";
|
|
10
|
+
|
|
11
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isAdvisoryPayload(value: unknown): value is AdvisoryPayload {
|
|
16
|
+
if (!isRecord(value)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (typeof value.generatedAt !== "number") {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (!isRecord(value.advisories)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return Object.values(value.advisories).every(
|
|
27
|
+
(entry) => Array.isArray(entry) && entry.every((item) => typeof item === "string"),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function cachePath(cacheDir: string): string {
|
|
32
|
+
return join(cacheDir, CACHE_FILE);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function loadCachedAdvisoryPayload(
|
|
36
|
+
cacheDir: string,
|
|
37
|
+
maxAgeMs: number,
|
|
38
|
+
now = Date.now(),
|
|
39
|
+
): AdvisoryPayload | null {
|
|
40
|
+
const path = cachePath(cacheDir);
|
|
41
|
+
if (!existsSync(path)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
|
|
47
|
+
if (!isAdvisoryPayload(parsed)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
if (now - parsed.generatedAt > maxAgeMs) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function saveCachedAdvisoryPayload(cacheDir: string, payload: AdvisoryPayload): void {
|
|
60
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
61
|
+
writeFileSync(cachePath(cacheDir), JSON.stringify(payload, null, 2), "utf8");
|
|
62
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RuntimeMode } from "../../config.js";
|
|
2
|
+
import { type GithubMetadataClientOptions } from "../github/client.js";
|
|
3
|
+
import type { AdvisoryPayload } from "../github/cache.js";
|
|
4
|
+
export interface LoadKnownVulnerableActionsOptions extends GithubMetadataClientOptions {
|
|
5
|
+
runtimeMode?: RuntimeMode;
|
|
6
|
+
}
|
|
7
|
+
export declare function loadKnownVulnerableActions(options?: LoadKnownVulnerableActionsOptions): AdvisoryPayload;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RuntimeMode } from "../../config.js";
|
|
2
|
+
import { loadBundledGithubAdvisories, type GithubMetadataClientOptions } from "../github/client.js";
|
|
3
|
+
import type { AdvisoryPayload } from "../github/cache.js";
|
|
4
|
+
|
|
5
|
+
export interface LoadKnownVulnerableActionsOptions extends GithubMetadataClientOptions {
|
|
6
|
+
runtimeMode?: RuntimeMode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function loadKnownVulnerableActions(
|
|
10
|
+
options: LoadKnownVulnerableActionsOptions = {},
|
|
11
|
+
): AdvisoryPayload {
|
|
12
|
+
return loadBundledGithubAdvisories(options);
|
|
13
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { AuditSelectionContext, RegisteredAudit } from "./types.js";
|
|
2
|
+
export declare function filterRegisteredAudits<TContext>(audits: Array<RegisteredAudit<TContext>>, context: AuditSelectionContext): Array<RegisteredAudit<TContext>>;
|
|
3
|
+
export type { RegisteredAudit } from "./types.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const PERSONA_ORDER = {
|
|
2
|
+
regular: 0,
|
|
3
|
+
pedantic: 1,
|
|
4
|
+
auditor: 2,
|
|
5
|
+
};
|
|
6
|
+
function resolvePersona(input) {
|
|
7
|
+
return input.persona ?? "regular";
|
|
8
|
+
}
|
|
9
|
+
function isPersonaAllowed(required, current) {
|
|
10
|
+
const requiredPersona = required ?? "regular";
|
|
11
|
+
return PERSONA_ORDER[current] >= PERSONA_ORDER[requiredPersona];
|
|
12
|
+
}
|
|
13
|
+
function isRuntimeAllowed(onlineRequired, context) {
|
|
14
|
+
if (!onlineRequired) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
const mode = context.runtimeMode ?? "offline";
|
|
18
|
+
return mode === "online";
|
|
19
|
+
}
|
|
20
|
+
function isAuditDisabled(auditId, context) {
|
|
21
|
+
const disabledAuditIds = context.disabledAuditIds;
|
|
22
|
+
return (disabledAuditIds ?? []).includes(auditId);
|
|
23
|
+
}
|
|
24
|
+
export function filterRegisteredAudits(audits, context) {
|
|
25
|
+
const persona = resolvePersona(context);
|
|
26
|
+
return audits.filter((audit) => !isAuditDisabled(audit.id, context) &&
|
|
27
|
+
isPersonaAllowed(audit.minPersona, persona) &&
|
|
28
|
+
isRuntimeAllowed(audit.onlineRequired, context));
|
|
29
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AuditPersona, RuntimeMode } from "../../config.js";
|
|
2
|
+
import type { Finding } from "../../types/finding.js";
|
|
3
|
+
export interface AuditSelectionContext {
|
|
4
|
+
persona?: AuditPersona;
|
|
5
|
+
runtimeMode?: RuntimeMode;
|
|
6
|
+
}
|
|
7
|
+
export interface RegisteredAudit<TContext> {
|
|
8
|
+
id: string;
|
|
9
|
+
run: (context: TContext) => Finding[] | Promise<Finding[]>;
|
|
10
|
+
minPersona?: AuditPersona;
|
|
11
|
+
onlineRequired?: boolean;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
function asRecord(value) {
|
|
2
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
function asString(value) {
|
|
8
|
+
return typeof value === "string" ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
function asNumber(value) {
|
|
11
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
function asBoolean(value) {
|
|
14
|
+
return typeof value === "boolean" ? value : undefined;
|
|
15
|
+
}
|
|
16
|
+
function asStringArray(value) {
|
|
17
|
+
if (!Array.isArray(value)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const result = value.filter((entry) => typeof entry === "string");
|
|
21
|
+
return result.length > 0 ? result : undefined;
|
|
22
|
+
}
|
|
23
|
+
function normalizeDependabotPath(value) {
|
|
24
|
+
return value.replaceAll("\\", "/");
|
|
25
|
+
}
|
|
26
|
+
export function isGitHubDependabotPath(path) {
|
|
27
|
+
return /(?:^|\/)\.github\/dependabot\.ya?ml$/iu.test(normalizeDependabotPath(path));
|
|
28
|
+
}
|
|
29
|
+
function extractSchedule(value) {
|
|
30
|
+
const schedule = asRecord(value);
|
|
31
|
+
if (!schedule) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const result = {
|
|
35
|
+
interval: asString(schedule.interval),
|
|
36
|
+
day: asString(schedule.day),
|
|
37
|
+
time: asString(schedule.time),
|
|
38
|
+
timezone: asString(schedule.timezone),
|
|
39
|
+
};
|
|
40
|
+
return result.interval || result.day || result.time || result.timezone ? result : undefined;
|
|
41
|
+
}
|
|
42
|
+
function extractCooldown(value) {
|
|
43
|
+
const cooldown = asRecord(value);
|
|
44
|
+
if (!cooldown) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const result = {
|
|
48
|
+
defaultDays: asNumber(cooldown["default-days"]),
|
|
49
|
+
semverMajorDays: asNumber(cooldown["semver-major-days"]),
|
|
50
|
+
semverMinorDays: asNumber(cooldown["semver-minor-days"]),
|
|
51
|
+
semverPatchDays: asNumber(cooldown["semver-patch-days"]),
|
|
52
|
+
};
|
|
53
|
+
return result.defaultDays !== undefined ||
|
|
54
|
+
result.semverMajorDays !== undefined ||
|
|
55
|
+
result.semverMinorDays !== undefined ||
|
|
56
|
+
result.semverPatchDays !== undefined
|
|
57
|
+
? result
|
|
58
|
+
: undefined;
|
|
59
|
+
}
|
|
60
|
+
function extractCommitMessage(value) {
|
|
61
|
+
const commitMessage = asRecord(value);
|
|
62
|
+
if (!commitMessage) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const result = {
|
|
66
|
+
prefix: asString(commitMessage.prefix),
|
|
67
|
+
prefixDevelopment: asString(commitMessage["prefix-development"]) ?? asString(commitMessage.prefixDevelopment),
|
|
68
|
+
include: asString(commitMessage.include),
|
|
69
|
+
};
|
|
70
|
+
return result.prefix || result.prefixDevelopment || result.include ? result : undefined;
|
|
71
|
+
}
|
|
72
|
+
function extractRuleFacts(value) {
|
|
73
|
+
const rule = asRecord(value);
|
|
74
|
+
if (!rule) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const result = {
|
|
78
|
+
dependencyName: asString(rule["dependency-name"]) ?? asString(rule.dependencyName),
|
|
79
|
+
dependencyType: asString(rule["dependency-type"]) ?? asString(rule.dependencyType),
|
|
80
|
+
versions: asStringArray(rule.versions),
|
|
81
|
+
updateTypes: asStringArray(rule["update-types"]) ?? asStringArray(rule.updateTypes),
|
|
82
|
+
patterns: asStringArray(rule.patterns),
|
|
83
|
+
};
|
|
84
|
+
if (result.dependencyName !== undefined ||
|
|
85
|
+
result.dependencyType !== undefined ||
|
|
86
|
+
result.versions !== undefined ||
|
|
87
|
+
result.updateTypes !== undefined ||
|
|
88
|
+
result.patterns !== undefined) {
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function extractGroupFacts(value) {
|
|
94
|
+
const group = asRecord(value);
|
|
95
|
+
if (!group) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const result = {
|
|
99
|
+
dependencyType: asString(group["dependency-type"]) ?? asString(group.dependencyType),
|
|
100
|
+
updateTypes: asStringArray(group["update-types"]) ?? asStringArray(group.updateTypes),
|
|
101
|
+
patterns: asStringArray(group.patterns),
|
|
102
|
+
};
|
|
103
|
+
if (result.dependencyType !== undefined ||
|
|
104
|
+
result.updateTypes !== undefined ||
|
|
105
|
+
result.patterns !== undefined) {
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function extractUpdateFacts(value) {
|
|
111
|
+
const update = asRecord(value);
|
|
112
|
+
if (!update) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const allow = Array.isArray(update.allow)
|
|
116
|
+
? update.allow
|
|
117
|
+
.map((entry) => extractRuleFacts(entry))
|
|
118
|
+
.filter((entry) => entry !== null)
|
|
119
|
+
: undefined;
|
|
120
|
+
const ignore = Array.isArray(update.ignore)
|
|
121
|
+
? update.ignore
|
|
122
|
+
.map((entry) => extractRuleFacts(entry))
|
|
123
|
+
.filter((entry) => entry !== null)
|
|
124
|
+
: undefined;
|
|
125
|
+
const groupsRecord = asRecord(update.groups);
|
|
126
|
+
const groups = {};
|
|
127
|
+
if (groupsRecord) {
|
|
128
|
+
for (const [name, groupValue] of Object.entries(groupsRecord)) {
|
|
129
|
+
const groupFacts = extractGroupFacts(groupValue);
|
|
130
|
+
if (groupFacts) {
|
|
131
|
+
groups[name] = groupFacts;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const result = {
|
|
136
|
+
packageEcosystem: asString(update["package-ecosystem"]) ?? asString(update.packageEcosystem),
|
|
137
|
+
directory: asString(update.directory),
|
|
138
|
+
targetBranch: asString(update["target-branch"]) ?? asString(update.targetBranch),
|
|
139
|
+
openPullRequestsLimit: asNumber(update["open-pull-requests-limit"]) ?? asNumber(update.openPullRequestsLimit),
|
|
140
|
+
insecureExternalCodeExecution: asBoolean(update["insecure-external-code-execution"]) ??
|
|
141
|
+
asBoolean(update.insecureExternalCodeExecution),
|
|
142
|
+
schedule: extractSchedule(update.schedule),
|
|
143
|
+
cooldown: extractCooldown(update.cooldown),
|
|
144
|
+
labels: asStringArray(update.labels),
|
|
145
|
+
assignees: asStringArray(update.assignees),
|
|
146
|
+
reviewers: asStringArray(update.reviewers),
|
|
147
|
+
registries: asStringArray(update.registries),
|
|
148
|
+
commitMessage: extractCommitMessage(update["commit-message"] ?? update.commitMessage),
|
|
149
|
+
allow,
|
|
150
|
+
ignore,
|
|
151
|
+
groups: Object.keys(groups).length > 0 ? groups : undefined,
|
|
152
|
+
};
|
|
153
|
+
if (result.packageEcosystem !== undefined ||
|
|
154
|
+
result.directory !== undefined ||
|
|
155
|
+
result.targetBranch !== undefined ||
|
|
156
|
+
result.openPullRequestsLimit !== undefined ||
|
|
157
|
+
result.insecureExternalCodeExecution !== undefined ||
|
|
158
|
+
result.schedule !== undefined ||
|
|
159
|
+
result.cooldown !== undefined ||
|
|
160
|
+
result.labels !== undefined ||
|
|
161
|
+
result.assignees !== undefined ||
|
|
162
|
+
result.reviewers !== undefined ||
|
|
163
|
+
result.registries !== undefined ||
|
|
164
|
+
result.commitMessage !== undefined ||
|
|
165
|
+
result.allow !== undefined ||
|
|
166
|
+
result.ignore !== undefined ||
|
|
167
|
+
result.groups !== undefined) {
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
export function extractDependabotFacts(parsed) {
|
|
173
|
+
const root = asRecord(parsed);
|
|
174
|
+
if (!root) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const version = asNumber(root.version);
|
|
178
|
+
const updatesRaw = Array.isArray(root.updates) ? root.updates : [];
|
|
179
|
+
const updates = updatesRaw
|
|
180
|
+
.map((entry) => extractUpdateFacts(entry))
|
|
181
|
+
.filter((entry) => entry !== null);
|
|
182
|
+
if (version === undefined && updates.length === 0) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
version,
|
|
187
|
+
updates,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface DependabotScheduleFacts {
|
|
2
|
+
interval?: string;
|
|
3
|
+
day?: string;
|
|
4
|
+
time?: string;
|
|
5
|
+
timezone?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface DependabotCooldownFacts {
|
|
8
|
+
defaultDays?: number;
|
|
9
|
+
semverMajorDays?: number;
|
|
10
|
+
semverMinorDays?: number;
|
|
11
|
+
semverPatchDays?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface DependabotCommitMessageFacts {
|
|
14
|
+
prefix?: string;
|
|
15
|
+
prefixDevelopment?: string;
|
|
16
|
+
include?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DependabotRuleFacts {
|
|
19
|
+
dependencyName?: string;
|
|
20
|
+
dependencyType?: string;
|
|
21
|
+
versions?: string[];
|
|
22
|
+
updateTypes?: string[];
|
|
23
|
+
patterns?: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface DependabotGroupFacts {
|
|
26
|
+
dependencyType?: string;
|
|
27
|
+
updateTypes?: string[];
|
|
28
|
+
patterns?: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface DependabotUpdateFacts {
|
|
31
|
+
packageEcosystem?: string;
|
|
32
|
+
directory?: string;
|
|
33
|
+
targetBranch?: string;
|
|
34
|
+
openPullRequestsLimit?: number;
|
|
35
|
+
insecureExternalCodeExecution?: boolean;
|
|
36
|
+
schedule?: DependabotScheduleFacts;
|
|
37
|
+
cooldown?: DependabotCooldownFacts;
|
|
38
|
+
labels?: string[];
|
|
39
|
+
assignees?: string[];
|
|
40
|
+
reviewers?: string[];
|
|
41
|
+
registries?: string[];
|
|
42
|
+
commitMessage?: DependabotCommitMessageFacts;
|
|
43
|
+
allow?: DependabotRuleFacts[];
|
|
44
|
+
ignore?: DependabotRuleFacts[];
|
|
45
|
+
groups?: Record<string, DependabotGroupFacts>;
|
|
46
|
+
}
|
|
47
|
+
export interface DependabotFacts {
|
|
48
|
+
version?: number;
|
|
49
|
+
updates: DependabotUpdateFacts[];
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|