codegate-ai 0.1.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/LICENSE +22 -0
- package/README.md +390 -0
- package/dist/cli-prompts.d.ts +6 -0
- package/dist/cli-prompts.js +94 -0
- package/dist/cli.d.ts +64 -0
- package/dist/cli.js +443 -0
- package/dist/commands/run-policy.d.ts +27 -0
- package/dist/commands/run-policy.js +39 -0
- package/dist/commands/scan-command/helpers.d.ts +28 -0
- package/dist/commands/scan-command/helpers.js +233 -0
- package/dist/commands/scan-command.d.ts +90 -0
- package/dist/commands/scan-command.js +403 -0
- package/dist/commands/undo.d.ts +5 -0
- package/dist/commands/undo.js +14 -0
- package/dist/config.d.ts +50 -0
- package/dist/config.js +187 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/knowledge-base/claude-code.json +152 -0
- package/dist/knowledge-base/cline.json +224 -0
- package/dist/knowledge-base/codex.json +162 -0
- package/dist/knowledge-base/copilot.json +132 -0
- package/dist/knowledge-base/cursor.json +134 -0
- package/dist/knowledge-base/gemini-cli.json +112 -0
- package/dist/knowledge-base/jetbrains-junie.json +208 -0
- package/dist/knowledge-base/kiro.json +102 -0
- package/dist/knowledge-base/opencode.json +128 -0
- package/dist/knowledge-base/roo-code.json +116 -0
- package/dist/knowledge-base/schema.json +77 -0
- package/dist/knowledge-base/windsurf.json +80 -0
- package/dist/knowledge-base/zed.json +88 -0
- package/dist/layer1-discovery/config-parser.d.ts +12 -0
- package/dist/layer1-discovery/config-parser.js +52 -0
- package/dist/layer1-discovery/file-walker.d.ts +13 -0
- package/dist/layer1-discovery/file-walker.js +77 -0
- package/dist/layer1-discovery/knowledge-base.d.ts +36 -0
- package/dist/layer1-discovery/knowledge-base.js +58 -0
- package/dist/layer1-discovery/tool-detector.d.ts +20 -0
- package/dist/layer1-discovery/tool-detector.js +138 -0
- package/dist/layer2-static/detectors/command-exec.d.ts +11 -0
- package/dist/layer2-static/detectors/command-exec.js +343 -0
- package/dist/layer2-static/detectors/consent-bypass.d.ts +8 -0
- package/dist/layer2-static/detectors/consent-bypass.js +330 -0
- package/dist/layer2-static/detectors/env-override.d.ts +8 -0
- package/dist/layer2-static/detectors/env-override.js +132 -0
- package/dist/layer2-static/detectors/git-hooks.d.ts +11 -0
- package/dist/layer2-static/detectors/git-hooks.js +61 -0
- package/dist/layer2-static/detectors/ide-settings.d.ts +8 -0
- package/dist/layer2-static/detectors/ide-settings.js +66 -0
- package/dist/layer2-static/detectors/plugin-manifest.d.ts +9 -0
- package/dist/layer2-static/detectors/plugin-manifest.js +1943 -0
- package/dist/layer2-static/detectors/rule-file.d.ts +7 -0
- package/dist/layer2-static/detectors/rule-file.js +299 -0
- package/dist/layer2-static/detectors/symlink.d.ts +9 -0
- package/dist/layer2-static/detectors/symlink.js +45 -0
- package/dist/layer2-static/engine.d.ts +28 -0
- package/dist/layer2-static/engine.js +83 -0
- package/dist/layer2-static/evidence.d.ts +12 -0
- package/dist/layer2-static/evidence.js +128 -0
- package/dist/layer2-static/rule-engine.d.ts +24 -0
- package/dist/layer2-static/rule-engine.js +138 -0
- package/dist/layer2-static/state/scan-state.d.ts +32 -0
- package/dist/layer2-static/state/scan-state.js +296 -0
- package/dist/layer3-dynamic/command-builder.d.ts +15 -0
- package/dist/layer3-dynamic/command-builder.js +39 -0
- package/dist/layer3-dynamic/local-text-analysis.d.ts +19 -0
- package/dist/layer3-dynamic/local-text-analysis.js +73 -0
- package/dist/layer3-dynamic/meta-agent.d.ts +17 -0
- package/dist/layer3-dynamic/meta-agent.js +33 -0
- package/dist/layer3-dynamic/prompt-templates/local-text-analysis.md +32 -0
- package/dist/layer3-dynamic/prompt-templates/security-analysis.md +13 -0
- package/dist/layer3-dynamic/prompt-templates/tool-poisoning.md +15 -0
- package/dist/layer3-dynamic/resource-fetcher.d.ts +25 -0
- package/dist/layer3-dynamic/resource-fetcher.js +119 -0
- package/dist/layer3-dynamic/sandbox.d.ts +13 -0
- package/dist/layer3-dynamic/sandbox.js +40 -0
- package/dist/layer3-dynamic/tool-description-acquisition.d.ts +22 -0
- package/dist/layer3-dynamic/tool-description-acquisition.js +76 -0
- package/dist/layer3-dynamic/tool-description-scanner.d.ts +11 -0
- package/dist/layer3-dynamic/tool-description-scanner.js +53 -0
- package/dist/layer3-dynamic/toxic-flow.d.ts +12 -0
- package/dist/layer3-dynamic/toxic-flow.js +57 -0
- package/dist/layer4-remediation/actions/quarantine.d.ts +1 -0
- package/dist/layer4-remediation/actions/quarantine.js +8 -0
- package/dist/layer4-remediation/actions/remove-field.d.ts +5 -0
- package/dist/layer4-remediation/actions/remove-field.js +53 -0
- package/dist/layer4-remediation/actions/replace-value.d.ts +5 -0
- package/dist/layer4-remediation/actions/replace-value.js +26 -0
- package/dist/layer4-remediation/actions/strip-unicode.d.ts +5 -0
- package/dist/layer4-remediation/actions/strip-unicode.js +8 -0
- package/dist/layer4-remediation/backup-manager.d.ts +32 -0
- package/dist/layer4-remediation/backup-manager.js +138 -0
- package/dist/layer4-remediation/diff-generator.d.ts +6 -0
- package/dist/layer4-remediation/diff-generator.js +29 -0
- package/dist/layer4-remediation/remediation-runner.d.ts +36 -0
- package/dist/layer4-remediation/remediation-runner.js +230 -0
- package/dist/layer4-remediation/remediator.d.ts +36 -0
- package/dist/layer4-remediation/remediator.js +117 -0
- package/dist/path-display.d.ts +1 -0
- package/dist/path-display.js +20 -0
- package/dist/pipeline.d.ts +34 -0
- package/dist/pipeline.js +259 -0
- package/dist/report-summary.d.ts +6 -0
- package/dist/report-summary.js +48 -0
- package/dist/reporter/html.d.ts +2 -0
- package/dist/reporter/html.js +103 -0
- package/dist/reporter/json.d.ts +2 -0
- package/dist/reporter/json.js +3 -0
- package/dist/reporter/markdown.d.ts +2 -0
- package/dist/reporter/markdown.js +52 -0
- package/dist/reporter/sarif.d.ts +2 -0
- package/dist/reporter/sarif.js +84 -0
- package/dist/reporter/terminal.d.ts +5 -0
- package/dist/reporter/terminal.js +94 -0
- package/dist/runtime/signal-handlers.d.ts +10 -0
- package/dist/runtime/signal-handlers.js +17 -0
- package/dist/scan-target/helpers.d.ts +20 -0
- package/dist/scan-target/helpers.js +268 -0
- package/dist/scan-target/staging.d.ts +5 -0
- package/dist/scan-target/staging.js +114 -0
- package/dist/scan-target/types.d.ts +18 -0
- package/dist/scan-target/types.js +1 -0
- package/dist/scan-target.d.ts +3 -0
- package/dist/scan-target.js +31 -0
- package/dist/scan.d.ts +54 -0
- package/dist/scan.js +593 -0
- package/dist/tui/app.d.ts +10 -0
- package/dist/tui/app.js +21 -0
- package/dist/tui/theme.d.ts +8 -0
- package/dist/tui/theme.js +7 -0
- package/dist/tui/views/dashboard.d.ts +6 -0
- package/dist/tui/views/dashboard.js +8 -0
- package/dist/tui/views/deep-scan-consent.d.ts +5 -0
- package/dist/tui/views/deep-scan-consent.js +6 -0
- package/dist/tui/views/progress.d.ts +4 -0
- package/dist/tui/views/progress.js +6 -0
- package/dist/tui/views/summary.d.ts +5 -0
- package/dist/tui/views/summary.js +16 -0
- package/dist/types/discovery.d.ts +12 -0
- package/dist/types/discovery.js +1 -0
- package/dist/types/finding.d.ts +46 -0
- package/dist/types/finding.js +15 -0
- package/dist/types/report.d.ts +25 -0
- package/dist/types/report.js +23 -0
- package/dist/wrapper.d.ts +35 -0
- package/dist/wrapper.js +220 -0
- package/package.json +97 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { defaultTheme } from "../theme.js";
|
|
4
|
+
export function ProgressView(props) {
|
|
5
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [_jsx(Text, { color: defaultTheme.title, children: "Progress" }), _jsx(Text, { children: props.progressMessage ?? "Scanning..." })] }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { defaultTheme } from "../theme.js";
|
|
4
|
+
function statusLabel(exitCode) {
|
|
5
|
+
if (exitCode === 0) {
|
|
6
|
+
return { text: "SAFE", color: defaultTheme.ok };
|
|
7
|
+
}
|
|
8
|
+
if (exitCode === 1) {
|
|
9
|
+
return { text: "WARNINGS", color: defaultTheme.warning };
|
|
10
|
+
}
|
|
11
|
+
return { text: "DANGEROUS", color: defaultTheme.danger };
|
|
12
|
+
}
|
|
13
|
+
export function SummaryView(props) {
|
|
14
|
+
const status = statusLabel(props.report.summary.exit_code);
|
|
15
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [_jsx(Text, { color: defaultTheme.title, children: "Summary" }), _jsxs(Text, { children: ["Status: ", _jsx(Text, { color: status.color, children: status.text })] }), _jsxs(Text, { children: ["Total findings: ", props.report.summary.total] }), _jsxs(Text, { children: ["Fixable findings: ", props.report.summary.fixable] }), _jsxs(Text, { children: ["Suppressed findings: ", props.report.summary.suppressed] })] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type DiscoveryFormat = "jsonc" | "json" | "toml" | "yaml" | "dotenv" | "text" | "markdown";
|
|
2
|
+
export type DiscoveryScope = "project" | "user";
|
|
3
|
+
export interface DiscoveryResult {
|
|
4
|
+
tool: string;
|
|
5
|
+
configPath: string;
|
|
6
|
+
absolutePath: string;
|
|
7
|
+
format: DiscoveryFormat;
|
|
8
|
+
scope: DiscoveryScope;
|
|
9
|
+
riskSurfaces: string[];
|
|
10
|
+
isSymlink: boolean;
|
|
11
|
+
symlinkTarget?: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const SEVERITIES: readonly ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"];
|
|
2
|
+
export type Severity = (typeof SEVERITIES)[number];
|
|
3
|
+
export declare const FINDING_CATEGORIES: readonly ["ENV_OVERRIDE", "COMMAND_EXEC", "CONSENT_BYPASS", "RULE_INJECTION", "IDE_SETTINGS", "SYMLINK_ESCAPE", "GIT_HOOK", "CONFIG_PRESENT", "PARSE_ERROR", "CONFIG_CHANGE", "NEW_SERVER", "TOXIC_FLOW"];
|
|
4
|
+
export type FindingCategory = (typeof FINDING_CATEGORIES)[number];
|
|
5
|
+
export type FindingLayer = "L1" | "L2" | "L3";
|
|
6
|
+
export type FindingConfidence = "HIGH" | "MEDIUM" | "LOW";
|
|
7
|
+
export interface FindingLocation {
|
|
8
|
+
field?: string;
|
|
9
|
+
line?: number;
|
|
10
|
+
column?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface FindingSourceConfig {
|
|
13
|
+
file_path: string;
|
|
14
|
+
field?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AffectedLocation {
|
|
17
|
+
file_path: string;
|
|
18
|
+
location?: FindingLocation;
|
|
19
|
+
}
|
|
20
|
+
export interface Finding {
|
|
21
|
+
rule_id: string;
|
|
22
|
+
finding_id: string;
|
|
23
|
+
severity: Severity;
|
|
24
|
+
category: FindingCategory;
|
|
25
|
+
layer: FindingLayer;
|
|
26
|
+
file_path: string;
|
|
27
|
+
location: FindingLocation;
|
|
28
|
+
affected_locations?: AffectedLocation[] | null;
|
|
29
|
+
description: string;
|
|
30
|
+
affected_tools: string[];
|
|
31
|
+
cve?: string | null;
|
|
32
|
+
owasp: string[];
|
|
33
|
+
cwe: string;
|
|
34
|
+
confidence: FindingConfidence;
|
|
35
|
+
fixable: boolean;
|
|
36
|
+
remediation_actions: string[];
|
|
37
|
+
evidence?: string | null;
|
|
38
|
+
observed?: string[] | null;
|
|
39
|
+
inference?: string | null;
|
|
40
|
+
not_verified?: string[] | null;
|
|
41
|
+
incident_id?: string | null;
|
|
42
|
+
incident_title?: string | null;
|
|
43
|
+
incident_primary?: boolean | null;
|
|
44
|
+
source_config?: FindingSourceConfig | null;
|
|
45
|
+
suppressed: boolean;
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const SEVERITIES = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"];
|
|
2
|
+
export const FINDING_CATEGORIES = [
|
|
3
|
+
"ENV_OVERRIDE",
|
|
4
|
+
"COMMAND_EXEC",
|
|
5
|
+
"CONSENT_BYPASS",
|
|
6
|
+
"RULE_INJECTION",
|
|
7
|
+
"IDE_SETTINGS",
|
|
8
|
+
"SYMLINK_ESCAPE",
|
|
9
|
+
"GIT_HOOK",
|
|
10
|
+
"CONFIG_PRESENT",
|
|
11
|
+
"PARSE_ERROR",
|
|
12
|
+
"CONFIG_CHANGE",
|
|
13
|
+
"NEW_SERVER",
|
|
14
|
+
"TOXIC_FLOW",
|
|
15
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Finding } from "./finding.js";
|
|
2
|
+
export interface ReportSummary {
|
|
3
|
+
total: number;
|
|
4
|
+
by_severity: Record<string, number>;
|
|
5
|
+
fixable: number;
|
|
6
|
+
suppressed: number;
|
|
7
|
+
exit_code: number;
|
|
8
|
+
}
|
|
9
|
+
export interface CodeGateReport {
|
|
10
|
+
version: string;
|
|
11
|
+
scan_target: string;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
kb_version: string;
|
|
14
|
+
tools_detected: string[];
|
|
15
|
+
findings: Finding[];
|
|
16
|
+
summary: ReportSummary;
|
|
17
|
+
}
|
|
18
|
+
export interface EmptyReportOptions {
|
|
19
|
+
version: string;
|
|
20
|
+
scanTarget: string;
|
|
21
|
+
kbVersion: string;
|
|
22
|
+
toolsDetected: string[];
|
|
23
|
+
exitCode: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function createEmptyReport(options: EmptyReportOptions): CodeGateReport;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function createEmptyReport(options) {
|
|
2
|
+
return {
|
|
3
|
+
version: options.version,
|
|
4
|
+
scan_target: options.scanTarget,
|
|
5
|
+
timestamp: new Date().toISOString(),
|
|
6
|
+
kb_version: options.kbVersion,
|
|
7
|
+
tools_detected: options.toolsDetected,
|
|
8
|
+
findings: [],
|
|
9
|
+
summary: {
|
|
10
|
+
total: 0,
|
|
11
|
+
by_severity: {
|
|
12
|
+
CRITICAL: 0,
|
|
13
|
+
HIGH: 0,
|
|
14
|
+
MEDIUM: 0,
|
|
15
|
+
LOW: 0,
|
|
16
|
+
INFO: 0,
|
|
17
|
+
},
|
|
18
|
+
fixable: 0,
|
|
19
|
+
suppressed: 0,
|
|
20
|
+
exit_code: options.exitCode,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type CodeGateConfig } from "./config.js";
|
|
2
|
+
import { type ToolDetection } from "./layer1-discovery/tool-detector.js";
|
|
3
|
+
import type { CodeGateReport } from "./types/report.js";
|
|
4
|
+
export declare const RUN_TARGETS: readonly ["claude", "opencode", "codex", "cursor", "windsurf", "kiro"];
|
|
5
|
+
export type RunTarget = (typeof RUN_TARGETS)[number];
|
|
6
|
+
interface LaunchResult {
|
|
7
|
+
status: number | null;
|
|
8
|
+
error?: Error;
|
|
9
|
+
}
|
|
10
|
+
export interface RunCommandInput {
|
|
11
|
+
target: string;
|
|
12
|
+
cwd: string;
|
|
13
|
+
version: string;
|
|
14
|
+
config: CodeGateConfig;
|
|
15
|
+
force?: boolean;
|
|
16
|
+
onReport?: (report: CodeGateReport) => void;
|
|
17
|
+
requestWarningProceed?: (report: CodeGateReport) => Promise<boolean> | boolean;
|
|
18
|
+
}
|
|
19
|
+
interface ScanInput {
|
|
20
|
+
version: string;
|
|
21
|
+
scanTarget: string;
|
|
22
|
+
config: CodeGateConfig;
|
|
23
|
+
}
|
|
24
|
+
export interface WrapperDeps {
|
|
25
|
+
runScan: (input: ScanInput) => Promise<CodeGateReport>;
|
|
26
|
+
detectTools: () => ToolDetection[];
|
|
27
|
+
launchTool: (command: string, args: string[], cwd: string) => LaunchResult;
|
|
28
|
+
collectScanSurface: (scanTarget: string, config: CodeGateConfig) => Promise<string[]> | string[];
|
|
29
|
+
captureSnapshot: (paths: string[]) => Map<string, string>;
|
|
30
|
+
stdout: (message: string) => void;
|
|
31
|
+
stderr: (message: string) => void;
|
|
32
|
+
setExitCode: (code: number) => void;
|
|
33
|
+
}
|
|
34
|
+
export declare function executeWrapperRun(input: RunCommandInput, deps?: WrapperDeps): Promise<void>;
|
|
35
|
+
export {};
|
package/dist/wrapper.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { applyConfigPolicy } from "./config.js";
|
|
7
|
+
import { evaluatePostScanGuard, evaluatePreLaunchGuard } from "./commands/run-policy.js";
|
|
8
|
+
import { detectTools } from "./layer1-discovery/tool-detector.js";
|
|
9
|
+
import { renderTerminalReport } from "./reporter/terminal.js";
|
|
10
|
+
import { collectScanSurface, runScanEngine } from "./scan.js";
|
|
11
|
+
export const RUN_TARGETS = ["claude", "opencode", "codex", "cursor", "windsurf", "kiro"];
|
|
12
|
+
const TARGETS = {
|
|
13
|
+
claude: {
|
|
14
|
+
label: "claude",
|
|
15
|
+
detectorTool: "claude-code",
|
|
16
|
+
binary: "claude",
|
|
17
|
+
guiLike: false,
|
|
18
|
+
},
|
|
19
|
+
opencode: {
|
|
20
|
+
label: "opencode",
|
|
21
|
+
detectorTool: "opencode",
|
|
22
|
+
binary: "opencode",
|
|
23
|
+
guiLike: false,
|
|
24
|
+
},
|
|
25
|
+
codex: {
|
|
26
|
+
label: "codex",
|
|
27
|
+
detectorTool: "codex-cli",
|
|
28
|
+
binary: "codex",
|
|
29
|
+
guiLike: false,
|
|
30
|
+
},
|
|
31
|
+
cursor: {
|
|
32
|
+
label: "cursor",
|
|
33
|
+
detectorTool: "cursor",
|
|
34
|
+
binary: "cursor",
|
|
35
|
+
guiLike: true,
|
|
36
|
+
},
|
|
37
|
+
windsurf: {
|
|
38
|
+
label: "windsurf",
|
|
39
|
+
detectorTool: "windsurf",
|
|
40
|
+
binary: "windsurf",
|
|
41
|
+
guiLike: true,
|
|
42
|
+
},
|
|
43
|
+
kiro: {
|
|
44
|
+
label: "kiro",
|
|
45
|
+
detectorTool: "kiro",
|
|
46
|
+
binary: "kiro",
|
|
47
|
+
guiLike: true,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
function isRunTarget(value) {
|
|
51
|
+
return RUN_TARGETS.includes(value);
|
|
52
|
+
}
|
|
53
|
+
function fingerprintFile(deps) {
|
|
54
|
+
const content = deps.readFile(deps.path);
|
|
55
|
+
const hash = createHash("sha256").update(content).digest("hex");
|
|
56
|
+
const mtime = deps.stat(deps.path);
|
|
57
|
+
return `${mtime}:${hash}`;
|
|
58
|
+
}
|
|
59
|
+
function defaultCaptureSnapshot(paths) {
|
|
60
|
+
const snapshot = new Map();
|
|
61
|
+
for (const filePath of paths) {
|
|
62
|
+
try {
|
|
63
|
+
const mode = statSync(filePath).mode;
|
|
64
|
+
if ((mode & 0o170000) !== 0o100000) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
snapshot.set(filePath, fingerprintFile({
|
|
68
|
+
path: filePath,
|
|
69
|
+
readFile: (path) => readFileSync(path, "utf8"),
|
|
70
|
+
stat: (path) => statSync(path).mtimeMs,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return snapshot;
|
|
78
|
+
}
|
|
79
|
+
function snapshotsEqual(before, after) {
|
|
80
|
+
if (before.size !== after.size) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
for (const [key, value] of before.entries()) {
|
|
84
|
+
if (after.get(key) !== value) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
function expandHomePath(path) {
|
|
91
|
+
if (path === "~") {
|
|
92
|
+
return homedir();
|
|
93
|
+
}
|
|
94
|
+
if (path.startsWith(`~${sep}`) || path.startsWith("~/")) {
|
|
95
|
+
return resolve(homedir(), path.slice(2));
|
|
96
|
+
}
|
|
97
|
+
return path;
|
|
98
|
+
}
|
|
99
|
+
function isTrustedDirectory(cwd, trustedDirectories) {
|
|
100
|
+
const resolvedCwd = resolve(cwd);
|
|
101
|
+
return trustedDirectories.some((trustedPath) => {
|
|
102
|
+
const resolvedTrusted = resolve(expandHomePath(trustedPath));
|
|
103
|
+
const rel = relative(resolvedTrusted, resolvedCwd);
|
|
104
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const defaultWrapperDeps = {
|
|
108
|
+
runScan: async (input) => runScanEngine({
|
|
109
|
+
version: input.version,
|
|
110
|
+
scanTarget: input.scanTarget,
|
|
111
|
+
config: input.config,
|
|
112
|
+
scanStatePath: input.config.scan_state_path,
|
|
113
|
+
}),
|
|
114
|
+
detectTools: () => detectTools(),
|
|
115
|
+
collectScanSurface: (scanTarget, config) => collectScanSurface(scanTarget, undefined, {
|
|
116
|
+
includeUserScope: config.scan_user_scope === true,
|
|
117
|
+
}),
|
|
118
|
+
launchTool: (command, args, cwd) => {
|
|
119
|
+
const result = spawnSync(command, args, { cwd, stdio: "inherit" });
|
|
120
|
+
return {
|
|
121
|
+
status: result.status,
|
|
122
|
+
error: result.error ?? undefined,
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
captureSnapshot: (paths) => defaultCaptureSnapshot(paths),
|
|
126
|
+
stdout: (message) => {
|
|
127
|
+
process.stdout.write(`${message}\n`);
|
|
128
|
+
},
|
|
129
|
+
stderr: (message) => {
|
|
130
|
+
process.stderr.write(`${message}\n`);
|
|
131
|
+
},
|
|
132
|
+
setExitCode: (code) => {
|
|
133
|
+
process.exitCode = code;
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
export async function executeWrapperRun(input, deps = defaultWrapperDeps) {
|
|
137
|
+
const normalizedTarget = input.target.trim().toLowerCase();
|
|
138
|
+
if (!isRunTarget(normalizedTarget)) {
|
|
139
|
+
deps.stderr(`Unknown tool: ${input.target}. Valid targets: ${RUN_TARGETS.join(", ")}.`);
|
|
140
|
+
deps.setExitCode(3);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const target = TARGETS[normalizedTarget];
|
|
144
|
+
const detection = deps
|
|
145
|
+
.detectTools()
|
|
146
|
+
.find((tool) => tool.tool === target.detectorTool && tool.installed);
|
|
147
|
+
if (!detection) {
|
|
148
|
+
deps.stderr(`${target.label} is not installed.`);
|
|
149
|
+
deps.setExitCode(3);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const resolvedCwd = resolve(input.cwd);
|
|
153
|
+
const preScanSurface = await deps.collectScanSurface(input.cwd, input.config);
|
|
154
|
+
const preScanSnapshot = deps.captureSnapshot(preScanSurface);
|
|
155
|
+
const rawReport = await deps.runScan({
|
|
156
|
+
version: input.version,
|
|
157
|
+
scanTarget: input.cwd,
|
|
158
|
+
config: input.config,
|
|
159
|
+
});
|
|
160
|
+
const report = applyConfigPolicy(rawReport, input.config);
|
|
161
|
+
if (input.onReport) {
|
|
162
|
+
input.onReport(report);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
deps.stdout(renderTerminalReport(report));
|
|
166
|
+
}
|
|
167
|
+
const scanSurface = await deps.collectScanSurface(input.cwd, input.config);
|
|
168
|
+
const scanSnapshot = deps.captureSnapshot(scanSurface);
|
|
169
|
+
const postScanDecision = evaluatePostScanGuard({
|
|
170
|
+
report,
|
|
171
|
+
scanSurfaceChanged: !snapshotsEqual(preScanSnapshot, scanSnapshot),
|
|
172
|
+
force: input.force === true,
|
|
173
|
+
autoProceedBelowThreshold: input.config.auto_proceed_below_threshold === true,
|
|
174
|
+
insideTrustedDirectory: isTrustedDirectory(resolvedCwd, input.config.trusted_directories),
|
|
175
|
+
});
|
|
176
|
+
if (postScanDecision.kind === "block") {
|
|
177
|
+
const writer = postScanDecision.stream === "stderr" ? deps.stderr : deps.stdout;
|
|
178
|
+
writer(postScanDecision.message);
|
|
179
|
+
deps.setExitCode(postScanDecision.exitCode);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (postScanDecision.kind === "prompt") {
|
|
183
|
+
if (!input.requestWarningProceed) {
|
|
184
|
+
deps.stderr("Warning findings detected. Re-run with --force to launch non-interactively or enable auto_proceed_below_threshold.");
|
|
185
|
+
deps.setExitCode(1);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const approved = await input.requestWarningProceed(report);
|
|
189
|
+
if (!approved) {
|
|
190
|
+
deps.stdout("Launch cancelled because warning findings require confirmation.");
|
|
191
|
+
deps.setExitCode(1);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const launchSurface = await deps.collectScanSurface(input.cwd, input.config);
|
|
196
|
+
const launchSnapshot = deps.captureSnapshot(launchSurface);
|
|
197
|
+
const preLaunchDecision = evaluatePreLaunchGuard({
|
|
198
|
+
launchSurfaceChanged: !snapshotsEqual(scanSnapshot, launchSnapshot),
|
|
199
|
+
});
|
|
200
|
+
if (preLaunchDecision.kind === "block") {
|
|
201
|
+
const writer = preLaunchDecision.stream === "stderr" ? deps.stderr : deps.stdout;
|
|
202
|
+
writer(preLaunchDecision.message);
|
|
203
|
+
deps.setExitCode(preLaunchDecision.exitCode);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (target.guiLike && detection.source !== "path") {
|
|
207
|
+
deps.stdout(`Scan complete. ${target.label} appears installed without a CLI launcher. Launch it manually.`);
|
|
208
|
+
deps.setExitCode(report.summary.exit_code);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const args = target.guiLike ? ["."] : [];
|
|
212
|
+
const launched = deps.launchTool(target.binary, args, input.cwd);
|
|
213
|
+
if (launched.status !== 0 || launched.error) {
|
|
214
|
+
const reason = launched.error?.message ?? `exit status ${launched.status ?? "unknown"}`;
|
|
215
|
+
deps.stderr(`Failed to launch ${target.label}: ${reason}`);
|
|
216
|
+
deps.setExitCode(3);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
deps.setExitCode(report.summary.exit_code);
|
|
220
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codegate-ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pre-flight security scanner for AI coding tool configurations.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/jonathansantilli/codegate.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/jonathansantilli/codegate#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/jonathansantilli/codegate/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ai-security",
|
|
17
|
+
"cli",
|
|
18
|
+
"code-scanning",
|
|
19
|
+
"codex",
|
|
20
|
+
"config-security",
|
|
21
|
+
"mcp",
|
|
22
|
+
"prompt-injection",
|
|
23
|
+
"security"
|
|
24
|
+
],
|
|
25
|
+
"bin": {
|
|
26
|
+
"codegate": "dist/cli.js"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.json && node scripts/copy-assets.mjs",
|
|
30
|
+
"lint": "eslint .",
|
|
31
|
+
"lint:fix": "eslint . --fix",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:perf": "vitest run tests/perf/scan-performance.test.ts",
|
|
34
|
+
"test:reliability": "vitest run tests/reliability/signal-handling.test.ts",
|
|
35
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
36
|
+
"format": "prettier --write .",
|
|
37
|
+
"format:check": "prettier --check .",
|
|
38
|
+
"release": "semantic-release",
|
|
39
|
+
"release:dry-run": "semantic-release --dry-run --no-ci",
|
|
40
|
+
"prepare": "husky"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"README.md",
|
|
48
|
+
"LICENSE"
|
|
49
|
+
],
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public",
|
|
52
|
+
"provenance": true
|
|
53
|
+
},
|
|
54
|
+
"lint-staged": {
|
|
55
|
+
"*.{js,mjs,cjs,ts,tsx}": [
|
|
56
|
+
"eslint --fix --max-warnings=0 --no-warn-ignored",
|
|
57
|
+
"prettier --write"
|
|
58
|
+
],
|
|
59
|
+
"*.{json,md,yml,yaml}": [
|
|
60
|
+
"prettier --write"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"ajv": "^8.18.0",
|
|
65
|
+
"commander": "^14.0.3",
|
|
66
|
+
"dotenv": "^17.3.1",
|
|
67
|
+
"ink": "^6.8.0",
|
|
68
|
+
"js-yaml": "^4.1.1",
|
|
69
|
+
"jsonc-parser": "^3.3.1",
|
|
70
|
+
"react": "^19.2.4",
|
|
71
|
+
"smol-toml": "^1.6.0",
|
|
72
|
+
"which": "^6.0.1"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@eslint/js": "^10.0.1",
|
|
76
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
77
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
78
|
+
"@semantic-release/git": "^10.0.1",
|
|
79
|
+
"@semantic-release/github": "^11.0.6",
|
|
80
|
+
"@semantic-release/npm": "^12.0.2",
|
|
81
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
82
|
+
"@types/js-yaml": "^4.0.9",
|
|
83
|
+
"@types/node": "^25.3.5",
|
|
84
|
+
"@types/react": "^19.2.14",
|
|
85
|
+
"@types/which": "^3.0.4",
|
|
86
|
+
"eslint": "^10.0.2",
|
|
87
|
+
"globals": "^17.4.0",
|
|
88
|
+
"husky": "^9.1.7",
|
|
89
|
+
"ink-testing-library": "^4.0.0",
|
|
90
|
+
"lint-staged": "^16.3.2",
|
|
91
|
+
"prettier": "^3.8.1",
|
|
92
|
+
"semantic-release": "^24.2.9",
|
|
93
|
+
"typescript": "^5.9.3",
|
|
94
|
+
"typescript-eslint": "^8.56.1",
|
|
95
|
+
"vitest": "^4.0.18"
|
|
96
|
+
}
|
|
97
|
+
}
|