codegate-ai 0.6.1 → 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.d.ts +1 -1
- package/dist/cli.js +59 -41
- package/dist/commands/scan-command/helpers.d.ts +6 -1
- package/dist/commands/scan-command/helpers.js +46 -1
- package/dist/commands/scan-command.js +49 -55
- 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/command-builder.d.ts +1 -0
- package/dist/layer3-dynamic/command-builder.js +44 -2
- package/dist/layer3-dynamic/local-text-analysis.d.ts +9 -1
- package/dist/layer3-dynamic/local-text-analysis.js +12 -27
- package/dist/layer3-dynamic/meta-agent.d.ts +1 -2
- package/dist/layer3-dynamic/meta-agent.js +3 -6
- package/dist/layer3-dynamic/prompt-templates/local-text-analysis.md +33 -21
- package/dist/layer3-dynamic/prompt-templates/security-analysis.md +11 -1
- package/dist/layer3-dynamic/prompt-templates/tool-poisoning.md +9 -1
- 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 +3 -1
package/dist/reporter/sarif.js
CHANGED
|
@@ -49,6 +49,7 @@ function findingToResult(finding) {
|
|
|
49
49
|
locations: [findingToLocation(finding)],
|
|
50
50
|
properties: {
|
|
51
51
|
finding_id: finding.finding_id,
|
|
52
|
+
fingerprint: finding.fingerprint ?? null,
|
|
52
53
|
severity: finding.severity,
|
|
53
54
|
category: finding.category,
|
|
54
55
|
layer: finding.layer,
|
|
@@ -59,6 +60,7 @@ function findingToResult(finding) {
|
|
|
59
60
|
evidence: finding.evidence ?? null,
|
|
60
61
|
fixable: finding.fixable,
|
|
61
62
|
suppressed: finding.suppressed,
|
|
63
|
+
metadata: finding.metadata ?? null,
|
|
62
64
|
source_config: finding.source_config ?? null,
|
|
63
65
|
},
|
|
64
66
|
};
|
|
@@ -15,6 +15,27 @@ function appendLabeledText(lines, label, value) {
|
|
|
15
15
|
}
|
|
16
16
|
lines.push(` ${label}: ${value}`);
|
|
17
17
|
}
|
|
18
|
+
function appendMetadata(lines, metadata) {
|
|
19
|
+
if (!metadata) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const hasContent = (metadata.sources?.length ?? 0) > 0 ||
|
|
23
|
+
(metadata.sinks?.length ?? 0) > 0 ||
|
|
24
|
+
(metadata.referenced_secrets?.length ?? 0) > 0 ||
|
|
25
|
+
(metadata.risk_tags?.length ?? 0) > 0 ||
|
|
26
|
+
typeof metadata.origin === "string";
|
|
27
|
+
if (!hasContent) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
lines.push(" Metadata:");
|
|
31
|
+
appendLabeledList(lines, "Sources", metadata.sources ?? []);
|
|
32
|
+
appendLabeledList(lines, "Sinks", metadata.sinks ?? []);
|
|
33
|
+
appendLabeledList(lines, "Referenced secrets", metadata.referenced_secrets ?? []);
|
|
34
|
+
appendLabeledList(lines, "Risk tags", metadata.risk_tags ?? []);
|
|
35
|
+
if (metadata.origin) {
|
|
36
|
+
appendLabeledText(lines, "Origin", metadata.origin);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
18
39
|
function appendEvidence(lines, evidence) {
|
|
19
40
|
const evidenceLines = evidence.split("\n");
|
|
20
41
|
if (evidenceLines.length === 1) {
|
|
@@ -55,6 +76,9 @@ function appendFinding(lines, report, options, finding) {
|
|
|
55
76
|
if (verbose) {
|
|
56
77
|
lines.push(` Rule: ${finding.rule_id}`);
|
|
57
78
|
lines.push(` Finding ID: ${finding.finding_id}`);
|
|
79
|
+
if (finding.fingerprint) {
|
|
80
|
+
lines.push(` Fingerprint: ${finding.fingerprint}`);
|
|
81
|
+
}
|
|
58
82
|
lines.push(` Category: ${finding.category} | Layer: ${finding.layer} | Confidence: ${finding.confidence}`);
|
|
59
83
|
const formattedLocation = formatLocation(finding.location);
|
|
60
84
|
if (formattedLocation) {
|
|
@@ -70,6 +94,7 @@ function appendFinding(lines, report, options, finding) {
|
|
|
70
94
|
if (finding.remediation_actions.length > 0) {
|
|
71
95
|
lines.push(` Remediation: ${finding.remediation_actions.join(", ")}`);
|
|
72
96
|
}
|
|
97
|
+
appendMetadata(lines, finding.metadata);
|
|
73
98
|
}
|
|
74
99
|
if (finding.layer === "L3" && finding.source_config) {
|
|
75
100
|
const fieldSuffix = finding.source_config.field ? ` (${finding.source_config.field})` : "";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SparseFetchPlan {
|
|
2
|
+
sparsePaths: string[];
|
|
3
|
+
}
|
|
4
|
+
export interface BuildSparseFetchPlanInput {
|
|
5
|
+
preferredSkill?: string;
|
|
6
|
+
inferredSkill?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function buildSparseFetchPlan(source: string, input?: BuildSparseFetchPlanInput): SparseFetchPlan | null;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isLikelyGitRepoUrl } from "./helpers.js";
|
|
2
|
+
function normalizeSkillName(value) {
|
|
3
|
+
const trimmed = value?.trim();
|
|
4
|
+
if (!trimmed) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
return trimmed.replace(/^skills\//iu, "").replace(/^\/+/u, "");
|
|
8
|
+
}
|
|
9
|
+
function selectSkill(input) {
|
|
10
|
+
return normalizeSkillName(input.preferredSkill) ?? normalizeSkillName(input.inferredSkill);
|
|
11
|
+
}
|
|
12
|
+
export function buildSparseFetchPlan(source, input = {}) {
|
|
13
|
+
let url;
|
|
14
|
+
try {
|
|
15
|
+
url = new URL(source);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
if (!isLikelyGitRepoUrl(url)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const selectedSkill = selectSkill(input);
|
|
24
|
+
if (!selectedSkill) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
sparsePaths: ["/*", "!/*/", "/.*/**", "/hooks/**", `/skills/${selectedSkill}/**`],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -2,16 +2,71 @@ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, statSync
|
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
+
import { buildSparseFetchPlan } from "./fetch-plan.js";
|
|
5
6
|
import { cleanupTempDir, collectExplicitCandidates, copyDirectoryRecursive, extractSkillFromRepoPath, inferRemoteCandidateFormat, inferLocalFileStagePath, inferRemoteFileStagePath, inferToolFromReportPath, parseGitHubFileSource, preserveTailSegments, shouldStageContainingFolder, } from "./helpers.js";
|
|
6
|
-
function
|
|
7
|
-
const result = spawnSync("git",
|
|
7
|
+
function runGit(args) {
|
|
8
|
+
const result = spawnSync("git", args, {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
});
|
|
11
|
+
if (result.status !== 0) {
|
|
12
|
+
const stderr = result.stderr?.trim();
|
|
13
|
+
throw new Error(stderr && stderr.length > 0 ? stderr : `git ${args.join(" ")} failed`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function cloneRepository(source, destination, sparsePaths) {
|
|
17
|
+
const cloneArgs = ["clone", "--depth", "1", "--filter=blob:none"];
|
|
18
|
+
if (sparsePaths && sparsePaths.length > 0) {
|
|
19
|
+
cloneArgs.push("--no-checkout");
|
|
20
|
+
}
|
|
21
|
+
cloneArgs.push(source, destination);
|
|
22
|
+
const result = spawnSync("git", cloneArgs, {
|
|
8
23
|
encoding: "utf8",
|
|
9
24
|
});
|
|
10
25
|
if (result.status !== 0) {
|
|
11
26
|
const stderr = result.stderr?.trim();
|
|
12
27
|
throw new Error(stderr && stderr.length > 0 ? stderr : `git clone failed for ${source}`);
|
|
13
28
|
}
|
|
14
|
-
|
|
29
|
+
if (!sparsePaths || sparsePaths.length === 0) {
|
|
30
|
+
cleanupTempDir(join(destination, ".git"));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
runGit(["-C", destination, "sparse-checkout", "init", "--no-cone"]);
|
|
35
|
+
runGit(["-C", destination, "sparse-checkout", "set", "--no-cone", ...sparsePaths]);
|
|
36
|
+
runGit(["-C", destination, "checkout", "--force", "HEAD"]);
|
|
37
|
+
cleanupTempDir(join(destination, ".git"));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
cleanupTempDir(destination);
|
|
42
|
+
const fallback = spawnSync("git", ["clone", "--depth", "1", "--filter=blob:none", source, destination], {
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
});
|
|
45
|
+
if (fallback.status !== 0) {
|
|
46
|
+
const stderr = fallback.stderr?.trim();
|
|
47
|
+
throw new Error(stderr && stderr.length > 0 ? stderr : `git clone failed for ${source}`, {
|
|
48
|
+
cause: error,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
cleanupTempDir(join(destination, ".git"));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function cloneSparseRepository(source, destination, sparsePaths) {
|
|
55
|
+
cloneRepository(source, destination, sparsePaths);
|
|
56
|
+
}
|
|
57
|
+
function cloneFullRepository(source, destination) {
|
|
58
|
+
cloneRepository(source, destination);
|
|
59
|
+
}
|
|
60
|
+
function stageSparseClone(source, destination, preferredSkill, inferredSkill) {
|
|
61
|
+
const sparsePlan = buildSparseFetchPlan(source, {
|
|
62
|
+
preferredSkill,
|
|
63
|
+
inferredSkill,
|
|
64
|
+
});
|
|
65
|
+
if (!sparsePlan) {
|
|
66
|
+
cloneFullRepository(source, destination);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
cloneSparseRepository(source, destination, sparsePlan.sparsePaths);
|
|
15
70
|
}
|
|
16
71
|
export function stageLocalFile(absolutePath) {
|
|
17
72
|
const tempRoot = mkdtempSync(join(tmpdir(), "codegate-scan-target-"));
|
|
@@ -132,7 +187,7 @@ export async function cloneGitRepo(rawTarget, options = {}) {
|
|
|
132
187
|
const tempRoot = mkdtempSync(join(tmpdir(), "codegate-scan-repo-"));
|
|
133
188
|
const repoDir = join(tempRoot, "repo");
|
|
134
189
|
try {
|
|
135
|
-
|
|
190
|
+
stageSparseClone(rawTarget, repoDir, options.preferredSkill, options.inferredSkill);
|
|
136
191
|
return await stageSkillAwareRepository(tempRoot, repoDir, options.displayTarget ?? rawTarget, options);
|
|
137
192
|
}
|
|
138
193
|
catch (error) {
|
|
@@ -144,7 +199,7 @@ export function stageRepoSubdirectory(repoUrl, filePath, displayTarget) {
|
|
|
144
199
|
const tempRoot = mkdtempSync(join(tmpdir(), "codegate-scan-repo-file-"));
|
|
145
200
|
const repoDir = join(tempRoot, "repo");
|
|
146
201
|
try {
|
|
147
|
-
|
|
202
|
+
stageSparseClone(repoUrl, repoDir, undefined, extractSkillFromRepoPath(filePath) ?? undefined);
|
|
148
203
|
const inferredSkill = extractSkillFromRepoPath(filePath);
|
|
149
204
|
if (inferredSkill) {
|
|
150
205
|
const stageRoot = join(tempRoot, "staged");
|
package/dist/scan.js
CHANGED
|
@@ -571,6 +571,9 @@ export async function runScanEngine(input) {
|
|
|
571
571
|
trustedApiDomains: input.config.trusted_api_domains,
|
|
572
572
|
unicodeAnalysis: input.config.unicode_analysis,
|
|
573
573
|
checkIdeSettings: input.config.check_ide_settings,
|
|
574
|
+
rulePackPaths: input.config.rule_pack_paths,
|
|
575
|
+
allowedRules: input.config.allowed_rules,
|
|
576
|
+
skipRules: input.config.skip_rules,
|
|
574
577
|
},
|
|
575
578
|
});
|
|
576
579
|
const snapshots = new Map();
|
package/dist/types/finding.d.ts
CHANGED
|
@@ -13,6 +13,13 @@ export interface FindingSourceConfig {
|
|
|
13
13
|
file_path: string;
|
|
14
14
|
field?: string;
|
|
15
15
|
}
|
|
16
|
+
export interface FindingMetadata {
|
|
17
|
+
sources?: string[];
|
|
18
|
+
sinks?: string[];
|
|
19
|
+
referenced_secrets?: string[];
|
|
20
|
+
risk_tags?: string[];
|
|
21
|
+
origin?: string;
|
|
22
|
+
}
|
|
16
23
|
export interface AffectedLocation {
|
|
17
24
|
file_path: string;
|
|
18
25
|
location?: FindingLocation;
|
|
@@ -20,6 +27,7 @@ export interface AffectedLocation {
|
|
|
20
27
|
export interface Finding {
|
|
21
28
|
rule_id: string;
|
|
22
29
|
finding_id: string;
|
|
30
|
+
fingerprint?: string;
|
|
23
31
|
severity: Severity;
|
|
24
32
|
category: FindingCategory;
|
|
25
33
|
layer: FindingLayer;
|
|
@@ -34,6 +42,7 @@ export interface Finding {
|
|
|
34
42
|
confidence: FindingConfidence;
|
|
35
43
|
fixable: boolean;
|
|
36
44
|
remediation_actions: string[];
|
|
45
|
+
metadata?: FindingMetadata | null;
|
|
37
46
|
evidence?: string | null;
|
|
38
47
|
observed?: string[] | null;
|
|
39
48
|
inference?: string | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codegate-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Pre-flight security scanner for AI coding tool configurations.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"lint": "eslint .",
|
|
32
32
|
"lint:fix": "eslint . --fix",
|
|
33
33
|
"test": "vitest run",
|
|
34
|
+
"test:coverage": "vitest run --coverage",
|
|
34
35
|
"test:perf": "vitest run tests/perf/scan-performance.test.ts",
|
|
35
36
|
"test:reliability": "vitest run tests/reliability/signal-handling.test.ts",
|
|
36
37
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
"@types/node": "^25.3.5",
|
|
86
87
|
"@types/react": "^19.2.14",
|
|
87
88
|
"@types/which": "^3.0.4",
|
|
89
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
88
90
|
"conventional-changelog-conventionalcommits": "^9.3.0",
|
|
89
91
|
"eslint": "^10.0.2",
|
|
90
92
|
"globals": "^17.4.0",
|