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,17 @@
|
|
|
1
|
+
export function registerSignalHandlers(options) {
|
|
2
|
+
const processLike = options.processLike ?? process;
|
|
3
|
+
const signals = options.signals ?? ["SIGINT", "SIGTERM"];
|
|
4
|
+
const handlers = new Map();
|
|
5
|
+
for (const signal of signals) {
|
|
6
|
+
const handler = () => {
|
|
7
|
+
options.onSignal(signal);
|
|
8
|
+
};
|
|
9
|
+
handlers.set(signal, handler);
|
|
10
|
+
processLike.on(signal, handler);
|
|
11
|
+
}
|
|
12
|
+
return () => {
|
|
13
|
+
for (const [signal, handler] of handlers.entries()) {
|
|
14
|
+
processLike.off(signal, handler);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DiscoveryFormat } from "../types/discovery.js";
|
|
2
|
+
import type { ExplicitScanCandidate } from "./types.js";
|
|
3
|
+
export interface GitHubFileSource {
|
|
4
|
+
repoUrl: string;
|
|
5
|
+
filePath: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function cleanupTempDir(path: string): void;
|
|
8
|
+
export declare function isLikelyHttpUrl(value: string): boolean;
|
|
9
|
+
export declare function isLikelyGitRepoUrl(value: URL): boolean;
|
|
10
|
+
export declare function sanitizePathSegment(value: string): string;
|
|
11
|
+
export declare function preserveTailSegments(pathname: string, count: number): string;
|
|
12
|
+
export declare function inferLocalFileStagePath(absolutePath: string): string;
|
|
13
|
+
export declare function inferRemoteFileStagePath(url: URL): string;
|
|
14
|
+
export declare function inferRemoteCandidateFormat(relativePath: string, contentType: string | null): DiscoveryFormat | null;
|
|
15
|
+
export declare function shouldStageContainingFolder(filePath: string): boolean;
|
|
16
|
+
export declare function inferTextLikeFormat(filePath: string): DiscoveryFormat | null;
|
|
17
|
+
export declare function inferToolFromReportPath(reportPath: string): string;
|
|
18
|
+
export declare function copyDirectoryRecursive(sourceDir: string, destinationDir: string): void;
|
|
19
|
+
export declare function collectExplicitCandidates(root: string): ExplicitScanCandidate[];
|
|
20
|
+
export declare function parseGitHubFileSource(rawTarget: string): GitHubFileSource | null;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { copyFileSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
|
+
const REPO_HOSTS = new Set([
|
|
4
|
+
"github.com",
|
|
5
|
+
"www.github.com",
|
|
6
|
+
"gitlab.com",
|
|
7
|
+
"www.gitlab.com",
|
|
8
|
+
"bitbucket.org",
|
|
9
|
+
]);
|
|
10
|
+
const RECURSIVE_ARTIFACT_FILE_NAMES = new Set([
|
|
11
|
+
"skill.md",
|
|
12
|
+
"agents.md",
|
|
13
|
+
"claude.md",
|
|
14
|
+
"codex.md",
|
|
15
|
+
"plugins.json",
|
|
16
|
+
"extensions.json",
|
|
17
|
+
"marketplace.json",
|
|
18
|
+
"product.json",
|
|
19
|
+
]);
|
|
20
|
+
export function cleanupTempDir(path) {
|
|
21
|
+
rmSync(path, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
export function isLikelyHttpUrl(value) {
|
|
24
|
+
return /^https?:\/\//iu.test(value);
|
|
25
|
+
}
|
|
26
|
+
export function isLikelyGitRepoUrl(value) {
|
|
27
|
+
if (!REPO_HOSTS.has(value.hostname.toLowerCase())) {
|
|
28
|
+
return value.pathname.toLowerCase().endsWith(".git");
|
|
29
|
+
}
|
|
30
|
+
const pathname = value.pathname.replace(/\/+$/u, "");
|
|
31
|
+
if (pathname.toLowerCase().endsWith(".git")) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
if (/(?:^|\/)(blob|raw|tree)\//iu.test(pathname)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const segments = pathname.split("/").filter((segment) => segment.length > 0);
|
|
38
|
+
return segments.length === 2;
|
|
39
|
+
}
|
|
40
|
+
export function sanitizePathSegment(value) {
|
|
41
|
+
const sanitized = value.replace(/[^a-z0-9._-]/giu, "-");
|
|
42
|
+
return sanitized.length > 0 ? sanitized : "artifact";
|
|
43
|
+
}
|
|
44
|
+
function normalizeReportPath(value) {
|
|
45
|
+
return value.replaceAll("\\", "/");
|
|
46
|
+
}
|
|
47
|
+
export function preserveTailSegments(pathname, count) {
|
|
48
|
+
const segments = pathname
|
|
49
|
+
.split(/[\\/]+/u)
|
|
50
|
+
.filter((segment) => segment.length > 0)
|
|
51
|
+
.map((segment) => sanitizePathSegment(segment));
|
|
52
|
+
if (segments.length === 0) {
|
|
53
|
+
return "artifact";
|
|
54
|
+
}
|
|
55
|
+
return normalizeReportPath(join(...segments.slice(-count)));
|
|
56
|
+
}
|
|
57
|
+
export function inferLocalFileStagePath(absolutePath) {
|
|
58
|
+
const fileName = basename(absolutePath);
|
|
59
|
+
const lower = fileName.toLowerCase();
|
|
60
|
+
if (lower === "product.json") {
|
|
61
|
+
return normalizeReportPath(join(".kiro", "product.json"));
|
|
62
|
+
}
|
|
63
|
+
if (lower === "skill.md" ||
|
|
64
|
+
lower === "plugins.json" ||
|
|
65
|
+
lower === "extensions.json" ||
|
|
66
|
+
lower === "marketplace.json") {
|
|
67
|
+
return preserveTailSegments(absolutePath, 2);
|
|
68
|
+
}
|
|
69
|
+
return normalizeReportPath(fileName);
|
|
70
|
+
}
|
|
71
|
+
export function inferRemoteFileStagePath(url) {
|
|
72
|
+
const fileName = basename(url.pathname) || "artifact";
|
|
73
|
+
const lower = fileName.toLowerCase();
|
|
74
|
+
if (lower === "product.json") {
|
|
75
|
+
return normalizeReportPath(join(".kiro", "product.json"));
|
|
76
|
+
}
|
|
77
|
+
if (lower === "skill.md" ||
|
|
78
|
+
lower === "plugins.json" ||
|
|
79
|
+
lower === "extensions.json" ||
|
|
80
|
+
lower === "marketplace.json") {
|
|
81
|
+
return preserveTailSegments(url.pathname, 3);
|
|
82
|
+
}
|
|
83
|
+
return normalizeReportPath(sanitizePathSegment(fileName));
|
|
84
|
+
}
|
|
85
|
+
export function inferRemoteCandidateFormat(relativePath, contentType) {
|
|
86
|
+
const inferredFromPath = inferTextLikeFormat(relativePath);
|
|
87
|
+
if (inferredFromPath) {
|
|
88
|
+
return inferredFromPath;
|
|
89
|
+
}
|
|
90
|
+
const normalizedContentType = contentType?.toLowerCase() ?? "";
|
|
91
|
+
if (normalizedContentType.includes("json")) {
|
|
92
|
+
return "json";
|
|
93
|
+
}
|
|
94
|
+
if (normalizedContentType.includes("yaml") || normalizedContentType.includes("yml")) {
|
|
95
|
+
return "yaml";
|
|
96
|
+
}
|
|
97
|
+
if (normalizedContentType.includes("toml")) {
|
|
98
|
+
return "toml";
|
|
99
|
+
}
|
|
100
|
+
if (normalizedContentType.includes("markdown")) {
|
|
101
|
+
return "markdown";
|
|
102
|
+
}
|
|
103
|
+
if (normalizedContentType.startsWith("text/")) {
|
|
104
|
+
return "text";
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
export function shouldStageContainingFolder(filePath) {
|
|
109
|
+
const fileName = basename(filePath).toLowerCase();
|
|
110
|
+
return RECURSIVE_ARTIFACT_FILE_NAMES.has(fileName) || fileName.endsWith(".mdc");
|
|
111
|
+
}
|
|
112
|
+
export function inferTextLikeFormat(filePath) {
|
|
113
|
+
const lower = basename(filePath).toLowerCase();
|
|
114
|
+
if (lower === ".env" || lower.endsWith(".env")) {
|
|
115
|
+
return "dotenv";
|
|
116
|
+
}
|
|
117
|
+
if (lower.endsWith(".json")) {
|
|
118
|
+
return "json";
|
|
119
|
+
}
|
|
120
|
+
if (lower.endsWith(".jsonc")) {
|
|
121
|
+
return "jsonc";
|
|
122
|
+
}
|
|
123
|
+
if (lower.endsWith(".toml")) {
|
|
124
|
+
return "toml";
|
|
125
|
+
}
|
|
126
|
+
if (lower.endsWith(".yaml") || lower.endsWith(".yml")) {
|
|
127
|
+
return "yaml";
|
|
128
|
+
}
|
|
129
|
+
if (lower.endsWith(".md") || lower.endsWith(".mdc")) {
|
|
130
|
+
return "markdown";
|
|
131
|
+
}
|
|
132
|
+
if (lower.endsWith(".txt") ||
|
|
133
|
+
lower.endsWith(".sh") ||
|
|
134
|
+
lower.endsWith(".bash") ||
|
|
135
|
+
lower.endsWith(".zsh") ||
|
|
136
|
+
lower.endsWith(".fish") ||
|
|
137
|
+
lower.endsWith(".ps1") ||
|
|
138
|
+
lower.endsWith(".js") ||
|
|
139
|
+
lower.endsWith(".mjs") ||
|
|
140
|
+
lower.endsWith(".cjs") ||
|
|
141
|
+
lower.endsWith(".ts") ||
|
|
142
|
+
lower.endsWith(".py")) {
|
|
143
|
+
return "text";
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
export function inferToolFromReportPath(reportPath) {
|
|
148
|
+
const lower = reportPath.toLowerCase();
|
|
149
|
+
if (lower.endsWith("skill.md") || lower.endsWith("codex.md")) {
|
|
150
|
+
return "codex-cli";
|
|
151
|
+
}
|
|
152
|
+
if (lower.endsWith("agents.md") || lower.endsWith("claude.md")) {
|
|
153
|
+
return "claude-code";
|
|
154
|
+
}
|
|
155
|
+
if (lower.endsWith("plugins.json")) {
|
|
156
|
+
return lower.includes(".claude/") ? "claude-code" : "opencode";
|
|
157
|
+
}
|
|
158
|
+
if (lower.endsWith("extensions.json")) {
|
|
159
|
+
if (lower.includes(".zed/")) {
|
|
160
|
+
return "zed";
|
|
161
|
+
}
|
|
162
|
+
if (lower.includes(".gemini/")) {
|
|
163
|
+
return "gemini-cli";
|
|
164
|
+
}
|
|
165
|
+
return "vscode";
|
|
166
|
+
}
|
|
167
|
+
if (lower.endsWith("marketplace.json")) {
|
|
168
|
+
return lower.includes(".cline/") ? "cline" : "roo-code";
|
|
169
|
+
}
|
|
170
|
+
if (lower.endsWith("product.json")) {
|
|
171
|
+
return "kiro";
|
|
172
|
+
}
|
|
173
|
+
if (lower.endsWith(".mdc")) {
|
|
174
|
+
return "cursor";
|
|
175
|
+
}
|
|
176
|
+
return "codex-cli";
|
|
177
|
+
}
|
|
178
|
+
export function copyDirectoryRecursive(sourceDir, destinationDir) {
|
|
179
|
+
mkdirSync(destinationDir, { recursive: true });
|
|
180
|
+
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
181
|
+
if (entry.name === ".git") {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const sourcePath = join(sourceDir, entry.name);
|
|
185
|
+
const destinationPath = join(destinationDir, entry.name);
|
|
186
|
+
if (entry.isDirectory()) {
|
|
187
|
+
copyDirectoryRecursive(sourcePath, destinationPath);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (entry.isFile()) {
|
|
191
|
+
mkdirSync(dirname(destinationPath), { recursive: true });
|
|
192
|
+
copyFileSync(sourcePath, destinationPath);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export function collectExplicitCandidates(root) {
|
|
197
|
+
const candidates = [];
|
|
198
|
+
const queue = [root];
|
|
199
|
+
while (queue.length > 0) {
|
|
200
|
+
const current = queue.pop();
|
|
201
|
+
if (!current) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
205
|
+
const absolutePath = join(current, entry.name);
|
|
206
|
+
if (entry.isDirectory()) {
|
|
207
|
+
queue.push(absolutePath);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (!entry.isFile()) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const reportPath = absolutePath.slice(root.length + 1).replaceAll("\\", "/");
|
|
214
|
+
const format = inferTextLikeFormat(reportPath);
|
|
215
|
+
if (!format) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
candidates.push({
|
|
219
|
+
reportPath,
|
|
220
|
+
absolutePath,
|
|
221
|
+
format,
|
|
222
|
+
tool: inferToolFromReportPath(reportPath),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return candidates.sort((left, right) => {
|
|
227
|
+
const depthDifference = left.reportPath.split("/").length - right.reportPath.split("/").length;
|
|
228
|
+
if (depthDifference !== 0) {
|
|
229
|
+
return depthDifference;
|
|
230
|
+
}
|
|
231
|
+
return left.reportPath.localeCompare(right.reportPath);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
export function parseGitHubFileSource(rawTarget) {
|
|
235
|
+
let url;
|
|
236
|
+
try {
|
|
237
|
+
url = new URL(rawTarget);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
if (url.hostname.toLowerCase() === "raw.githubusercontent.com") {
|
|
243
|
+
const segments = url.pathname.split("/").filter((segment) => segment.length > 0);
|
|
244
|
+
if (segments.length < 4) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const [owner, repo, , ...fileSegments] = segments;
|
|
248
|
+
return {
|
|
249
|
+
repoUrl: `https://github.com/${owner}/${repo}.git`,
|
|
250
|
+
filePath: fileSegments.join("/"),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (url.hostname.toLowerCase() === "github.com") {
|
|
254
|
+
const segments = url.pathname.split("/").filter((segment) => segment.length > 0);
|
|
255
|
+
if (segments.length < 5) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const [owner, repo, marker, , ...fileSegments] = segments;
|
|
259
|
+
if (marker !== "blob" && marker !== "raw") {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
repoUrl: `https://github.com/${owner}/${repo}.git`,
|
|
264
|
+
filePath: fileSegments.join("/"),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ResolvedScanTarget } from "./types.js";
|
|
2
|
+
export declare function stageLocalFile(absolutePath: string): ResolvedScanTarget;
|
|
3
|
+
export declare function cloneGitRepo(rawTarget: string): ResolvedScanTarget;
|
|
4
|
+
export declare function stageRepoSubdirectory(repoUrl: string, filePath: string, displayTarget: string): ResolvedScanTarget;
|
|
5
|
+
export declare function downloadRemoteFile(rawTarget: string): Promise<ResolvedScanTarget>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync } from "node:fs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { cleanupTempDir, collectExplicitCandidates, copyDirectoryRecursive, inferRemoteCandidateFormat, inferLocalFileStagePath, inferRemoteFileStagePath, inferToolFromReportPath, parseGitHubFileSource, preserveTailSegments, shouldStageContainingFolder, } from "./helpers.js";
|
|
6
|
+
function cloneRepository(source, destination) {
|
|
7
|
+
const result = spawnSync("git", ["clone", "--depth", "1", "--filter=blob:none", source, destination], {
|
|
8
|
+
encoding: "utf8",
|
|
9
|
+
});
|
|
10
|
+
if (result.status !== 0) {
|
|
11
|
+
const stderr = result.stderr?.trim();
|
|
12
|
+
throw new Error(stderr && stderr.length > 0 ? stderr : `git clone failed for ${source}`);
|
|
13
|
+
}
|
|
14
|
+
cleanupTempDir(join(destination, ".git"));
|
|
15
|
+
}
|
|
16
|
+
export function stageLocalFile(absolutePath) {
|
|
17
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "codegate-scan-target-"));
|
|
18
|
+
if (shouldStageContainingFolder(absolutePath)) {
|
|
19
|
+
const relativeRoot = preserveTailSegments(dirname(absolutePath), 2);
|
|
20
|
+
const destinationDir = join(tempRoot, relativeRoot);
|
|
21
|
+
copyDirectoryRecursive(dirname(absolutePath), destinationDir);
|
|
22
|
+
return {
|
|
23
|
+
scanTarget: tempRoot,
|
|
24
|
+
displayTarget: absolutePath,
|
|
25
|
+
explicitCandidates: collectExplicitCandidates(tempRoot),
|
|
26
|
+
cleanup: () => cleanupTempDir(tempRoot),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const relativePath = inferLocalFileStagePath(absolutePath);
|
|
30
|
+
const stagedPath = join(tempRoot, relativePath);
|
|
31
|
+
mkdirSync(dirname(stagedPath), { recursive: true });
|
|
32
|
+
copyFileSync(absolutePath, stagedPath);
|
|
33
|
+
return {
|
|
34
|
+
scanTarget: tempRoot,
|
|
35
|
+
displayTarget: absolutePath,
|
|
36
|
+
explicitCandidates: collectExplicitCandidates(tempRoot),
|
|
37
|
+
cleanup: () => cleanupTempDir(tempRoot),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function cloneGitRepo(rawTarget) {
|
|
41
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "codegate-scan-repo-"));
|
|
42
|
+
const repoDir = join(tempRoot, "repo");
|
|
43
|
+
try {
|
|
44
|
+
cloneRepository(rawTarget, repoDir);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
cleanupTempDir(tempRoot);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
scanTarget: repoDir,
|
|
52
|
+
displayTarget: rawTarget,
|
|
53
|
+
cleanup: () => cleanupTempDir(tempRoot),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function stageRepoSubdirectory(repoUrl, filePath, displayTarget) {
|
|
57
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "codegate-scan-repo-file-"));
|
|
58
|
+
const repoDir = join(tempRoot, "repo");
|
|
59
|
+
try {
|
|
60
|
+
cloneRepository(repoUrl, repoDir);
|
|
61
|
+
const absoluteFile = join(repoDir, filePath);
|
|
62
|
+
if (!existsSync(absoluteFile)) {
|
|
63
|
+
throw new Error(`Resolved repository file not found after clone: ${filePath}`);
|
|
64
|
+
}
|
|
65
|
+
const stageRoot = join(tempRoot, "staged");
|
|
66
|
+
const relativeRoot = preserveTailSegments(dirname(filePath), 2);
|
|
67
|
+
const destinationDir = join(stageRoot, relativeRoot);
|
|
68
|
+
copyDirectoryRecursive(dirname(absoluteFile), destinationDir);
|
|
69
|
+
return {
|
|
70
|
+
scanTarget: stageRoot,
|
|
71
|
+
displayTarget,
|
|
72
|
+
explicitCandidates: collectExplicitCandidates(stageRoot),
|
|
73
|
+
cleanup: () => cleanupTempDir(tempRoot),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
cleanupTempDir(tempRoot);
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function downloadRemoteFile(rawTarget) {
|
|
82
|
+
const githubFile = parseGitHubFileSource(rawTarget);
|
|
83
|
+
if (githubFile && shouldStageContainingFolder(githubFile.filePath)) {
|
|
84
|
+
return stageRepoSubdirectory(githubFile.repoUrl, githubFile.filePath, rawTarget);
|
|
85
|
+
}
|
|
86
|
+
const response = await fetch(rawTarget);
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(`Failed to download scan target: ${response.status} ${response.statusText}`);
|
|
89
|
+
}
|
|
90
|
+
const tempRoot = mkdtempSync(join(tmpdir(), "codegate-scan-download-"));
|
|
91
|
+
const url = new URL(rawTarget);
|
|
92
|
+
const relativePath = inferRemoteFileStagePath(url);
|
|
93
|
+
const textContent = await response.text();
|
|
94
|
+
const format = inferRemoteCandidateFormat(relativePath, response.headers.get("content-type"));
|
|
95
|
+
if (!format) {
|
|
96
|
+
cleanupTempDir(tempRoot);
|
|
97
|
+
throw new Error(`Unsupported remote scan target format for ${rawTarget}`);
|
|
98
|
+
}
|
|
99
|
+
// Keep direct remote file contents in memory so untrusted downloads are analyzed without
|
|
100
|
+
// persisting arbitrary response bytes to disk.
|
|
101
|
+
const explicitCandidate = {
|
|
102
|
+
reportPath: relativePath,
|
|
103
|
+
absolutePath: join(tempRoot, relativePath),
|
|
104
|
+
format,
|
|
105
|
+
tool: inferToolFromReportPath(relativePath),
|
|
106
|
+
textContent,
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
scanTarget: tempRoot,
|
|
110
|
+
displayTarget: rawTarget,
|
|
111
|
+
explicitCandidates: [explicitCandidate],
|
|
112
|
+
cleanup: () => cleanupTempDir(tempRoot),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DiscoveryFormat } from "../types/discovery.js";
|
|
2
|
+
export interface ExplicitScanCandidate {
|
|
3
|
+
reportPath: string;
|
|
4
|
+
absolutePath: string;
|
|
5
|
+
format: DiscoveryFormat;
|
|
6
|
+
tool: string;
|
|
7
|
+
textContent?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ResolvedScanTarget {
|
|
10
|
+
scanTarget: string;
|
|
11
|
+
displayTarget: string;
|
|
12
|
+
explicitCandidates?: ExplicitScanCandidate[];
|
|
13
|
+
cleanup?: () => Promise<void> | void;
|
|
14
|
+
}
|
|
15
|
+
export interface ResolveScanTargetInput {
|
|
16
|
+
rawTarget: string;
|
|
17
|
+
cwd: string;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ResolvedScanTarget, ResolveScanTargetInput } from "./scan-target/types.js";
|
|
2
|
+
export type { ExplicitScanCandidate, ResolvedScanTarget, ResolveScanTargetInput, } from "./scan-target/types.js";
|
|
3
|
+
export declare function resolveScanTarget(input: ResolveScanTargetInput): Promise<ResolvedScanTarget>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { isLikelyGitRepoUrl, isLikelyHttpUrl } from "./scan-target/helpers.js";
|
|
4
|
+
import { cloneGitRepo, downloadRemoteFile, stageLocalFile } from "./scan-target/staging.js";
|
|
5
|
+
export async function resolveScanTarget(input) {
|
|
6
|
+
const localPath = resolve(input.cwd, input.rawTarget);
|
|
7
|
+
if (existsSync(localPath)) {
|
|
8
|
+
const targetStat = statSync(localPath);
|
|
9
|
+
if (targetStat.isDirectory()) {
|
|
10
|
+
return {
|
|
11
|
+
scanTarget: localPath,
|
|
12
|
+
displayTarget: localPath,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (targetStat.isFile()) {
|
|
16
|
+
return stageLocalFile(localPath);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Local filesystem paths take precedence; only unresolved HTTP(S) targets are treated as remote artifacts.
|
|
20
|
+
if (!isLikelyHttpUrl(input.rawTarget)) {
|
|
21
|
+
return {
|
|
22
|
+
scanTarget: localPath,
|
|
23
|
+
displayTarget: localPath,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const url = new URL(input.rawTarget);
|
|
27
|
+
if (isLikelyGitRepoUrl(url)) {
|
|
28
|
+
return cloneGitRepo(input.rawTarget);
|
|
29
|
+
}
|
|
30
|
+
return downloadRemoteFile(input.rawTarget);
|
|
31
|
+
}
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type LocalTextAnalysisTarget } from "./layer3-dynamic/local-text-analysis.js";
|
|
2
|
+
import { type ParseResult } from "./layer1-discovery/config-parser.js";
|
|
3
|
+
import { type KnowledgeBaseLoadResult } from "./layer1-discovery/knowledge-base.js";
|
|
4
|
+
import { type WalkResult } from "./layer1-discovery/file-walker.js";
|
|
5
|
+
import type { DiscoveryFormat } from "./types/discovery.js";
|
|
6
|
+
import type { CodeGateReport } from "./types/report.js";
|
|
7
|
+
import type { CodeGateConfig } from "./config.js";
|
|
8
|
+
import type { DeepScanResource } from "./pipeline.js";
|
|
9
|
+
export interface ScanEngineInput {
|
|
10
|
+
version: string;
|
|
11
|
+
scanTarget: string;
|
|
12
|
+
kb?: KnowledgeBaseLoadResult;
|
|
13
|
+
config: CodeGateConfig;
|
|
14
|
+
scanStatePath?: string;
|
|
15
|
+
homeDir?: string;
|
|
16
|
+
discoveryContext?: ScanDiscoveryContext;
|
|
17
|
+
}
|
|
18
|
+
export interface DeepScanDiscoveryOptions {
|
|
19
|
+
includeUserScope?: boolean;
|
|
20
|
+
homeDir?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ScanSurfaceOptions {
|
|
23
|
+
includeUserScope?: boolean;
|
|
24
|
+
homeDir?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ScanDiscoveryCandidate {
|
|
27
|
+
reportPath: string;
|
|
28
|
+
absolutePath: string;
|
|
29
|
+
format: DiscoveryFormat;
|
|
30
|
+
tool: string;
|
|
31
|
+
textContent?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ParsedScanDiscoveryCandidate extends ScanDiscoveryCandidate {
|
|
34
|
+
parsed: ParseResult;
|
|
35
|
+
}
|
|
36
|
+
export interface ScanDiscoveryContext {
|
|
37
|
+
absoluteTarget: string;
|
|
38
|
+
kb: KnowledgeBaseLoadResult;
|
|
39
|
+
walked: WalkResult;
|
|
40
|
+
selected: ScanDiscoveryCandidate[];
|
|
41
|
+
parsedCandidates?: ParsedScanDiscoveryCandidate[];
|
|
42
|
+
}
|
|
43
|
+
export interface ScanDiscoveryContextOptions {
|
|
44
|
+
includeUserScope?: boolean;
|
|
45
|
+
homeDir?: string;
|
|
46
|
+
parseSelected?: boolean;
|
|
47
|
+
explicitCandidates?: ScanDiscoveryCandidate[];
|
|
48
|
+
}
|
|
49
|
+
export declare function discoverDeepScanResources(scanTarget: string, kbInput?: KnowledgeBaseLoadResult, options?: DeepScanDiscoveryOptions): DeepScanResource[];
|
|
50
|
+
export declare function createScanDiscoveryContext(scanTarget: string, kbInput?: KnowledgeBaseLoadResult, options?: ScanDiscoveryContextOptions): ScanDiscoveryContext;
|
|
51
|
+
export declare function discoverDeepScanResourcesFromContext(context: ScanDiscoveryContext): DeepScanResource[];
|
|
52
|
+
export declare function collectScanSurface(scanTarget: string, kbInput?: KnowledgeBaseLoadResult, options?: ScanSurfaceOptions): string[];
|
|
53
|
+
export declare function discoverLocalTextAnalysisTargetsFromContext(context: ScanDiscoveryContext): LocalTextAnalysisTarget[];
|
|
54
|
+
export declare function runScanEngine(input: ScanEngineInput): Promise<CodeGateReport>;
|