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.
Files changed (147) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +390 -0
  3. package/dist/cli-prompts.d.ts +6 -0
  4. package/dist/cli-prompts.js +94 -0
  5. package/dist/cli.d.ts +64 -0
  6. package/dist/cli.js +443 -0
  7. package/dist/commands/run-policy.d.ts +27 -0
  8. package/dist/commands/run-policy.js +39 -0
  9. package/dist/commands/scan-command/helpers.d.ts +28 -0
  10. package/dist/commands/scan-command/helpers.js +233 -0
  11. package/dist/commands/scan-command.d.ts +90 -0
  12. package/dist/commands/scan-command.js +403 -0
  13. package/dist/commands/undo.d.ts +5 -0
  14. package/dist/commands/undo.js +14 -0
  15. package/dist/config.d.ts +50 -0
  16. package/dist/config.js +187 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/knowledge-base/claude-code.json +152 -0
  20. package/dist/knowledge-base/cline.json +224 -0
  21. package/dist/knowledge-base/codex.json +162 -0
  22. package/dist/knowledge-base/copilot.json +132 -0
  23. package/dist/knowledge-base/cursor.json +134 -0
  24. package/dist/knowledge-base/gemini-cli.json +112 -0
  25. package/dist/knowledge-base/jetbrains-junie.json +208 -0
  26. package/dist/knowledge-base/kiro.json +102 -0
  27. package/dist/knowledge-base/opencode.json +128 -0
  28. package/dist/knowledge-base/roo-code.json +116 -0
  29. package/dist/knowledge-base/schema.json +77 -0
  30. package/dist/knowledge-base/windsurf.json +80 -0
  31. package/dist/knowledge-base/zed.json +88 -0
  32. package/dist/layer1-discovery/config-parser.d.ts +12 -0
  33. package/dist/layer1-discovery/config-parser.js +52 -0
  34. package/dist/layer1-discovery/file-walker.d.ts +13 -0
  35. package/dist/layer1-discovery/file-walker.js +77 -0
  36. package/dist/layer1-discovery/knowledge-base.d.ts +36 -0
  37. package/dist/layer1-discovery/knowledge-base.js +58 -0
  38. package/dist/layer1-discovery/tool-detector.d.ts +20 -0
  39. package/dist/layer1-discovery/tool-detector.js +138 -0
  40. package/dist/layer2-static/detectors/command-exec.d.ts +11 -0
  41. package/dist/layer2-static/detectors/command-exec.js +343 -0
  42. package/dist/layer2-static/detectors/consent-bypass.d.ts +8 -0
  43. package/dist/layer2-static/detectors/consent-bypass.js +330 -0
  44. package/dist/layer2-static/detectors/env-override.d.ts +8 -0
  45. package/dist/layer2-static/detectors/env-override.js +132 -0
  46. package/dist/layer2-static/detectors/git-hooks.d.ts +11 -0
  47. package/dist/layer2-static/detectors/git-hooks.js +61 -0
  48. package/dist/layer2-static/detectors/ide-settings.d.ts +8 -0
  49. package/dist/layer2-static/detectors/ide-settings.js +66 -0
  50. package/dist/layer2-static/detectors/plugin-manifest.d.ts +9 -0
  51. package/dist/layer2-static/detectors/plugin-manifest.js +1943 -0
  52. package/dist/layer2-static/detectors/rule-file.d.ts +7 -0
  53. package/dist/layer2-static/detectors/rule-file.js +299 -0
  54. package/dist/layer2-static/detectors/symlink.d.ts +9 -0
  55. package/dist/layer2-static/detectors/symlink.js +45 -0
  56. package/dist/layer2-static/engine.d.ts +28 -0
  57. package/dist/layer2-static/engine.js +83 -0
  58. package/dist/layer2-static/evidence.d.ts +12 -0
  59. package/dist/layer2-static/evidence.js +128 -0
  60. package/dist/layer2-static/rule-engine.d.ts +24 -0
  61. package/dist/layer2-static/rule-engine.js +138 -0
  62. package/dist/layer2-static/state/scan-state.d.ts +32 -0
  63. package/dist/layer2-static/state/scan-state.js +296 -0
  64. package/dist/layer3-dynamic/command-builder.d.ts +15 -0
  65. package/dist/layer3-dynamic/command-builder.js +39 -0
  66. package/dist/layer3-dynamic/local-text-analysis.d.ts +19 -0
  67. package/dist/layer3-dynamic/local-text-analysis.js +73 -0
  68. package/dist/layer3-dynamic/meta-agent.d.ts +17 -0
  69. package/dist/layer3-dynamic/meta-agent.js +33 -0
  70. package/dist/layer3-dynamic/prompt-templates/local-text-analysis.md +32 -0
  71. package/dist/layer3-dynamic/prompt-templates/security-analysis.md +13 -0
  72. package/dist/layer3-dynamic/prompt-templates/tool-poisoning.md +15 -0
  73. package/dist/layer3-dynamic/resource-fetcher.d.ts +25 -0
  74. package/dist/layer3-dynamic/resource-fetcher.js +119 -0
  75. package/dist/layer3-dynamic/sandbox.d.ts +13 -0
  76. package/dist/layer3-dynamic/sandbox.js +40 -0
  77. package/dist/layer3-dynamic/tool-description-acquisition.d.ts +22 -0
  78. package/dist/layer3-dynamic/tool-description-acquisition.js +76 -0
  79. package/dist/layer3-dynamic/tool-description-scanner.d.ts +11 -0
  80. package/dist/layer3-dynamic/tool-description-scanner.js +53 -0
  81. package/dist/layer3-dynamic/toxic-flow.d.ts +12 -0
  82. package/dist/layer3-dynamic/toxic-flow.js +57 -0
  83. package/dist/layer4-remediation/actions/quarantine.d.ts +1 -0
  84. package/dist/layer4-remediation/actions/quarantine.js +8 -0
  85. package/dist/layer4-remediation/actions/remove-field.d.ts +5 -0
  86. package/dist/layer4-remediation/actions/remove-field.js +53 -0
  87. package/dist/layer4-remediation/actions/replace-value.d.ts +5 -0
  88. package/dist/layer4-remediation/actions/replace-value.js +26 -0
  89. package/dist/layer4-remediation/actions/strip-unicode.d.ts +5 -0
  90. package/dist/layer4-remediation/actions/strip-unicode.js +8 -0
  91. package/dist/layer4-remediation/backup-manager.d.ts +32 -0
  92. package/dist/layer4-remediation/backup-manager.js +138 -0
  93. package/dist/layer4-remediation/diff-generator.d.ts +6 -0
  94. package/dist/layer4-remediation/diff-generator.js +29 -0
  95. package/dist/layer4-remediation/remediation-runner.d.ts +36 -0
  96. package/dist/layer4-remediation/remediation-runner.js +230 -0
  97. package/dist/layer4-remediation/remediator.d.ts +36 -0
  98. package/dist/layer4-remediation/remediator.js +117 -0
  99. package/dist/path-display.d.ts +1 -0
  100. package/dist/path-display.js +20 -0
  101. package/dist/pipeline.d.ts +34 -0
  102. package/dist/pipeline.js +259 -0
  103. package/dist/report-summary.d.ts +6 -0
  104. package/dist/report-summary.js +48 -0
  105. package/dist/reporter/html.d.ts +2 -0
  106. package/dist/reporter/html.js +103 -0
  107. package/dist/reporter/json.d.ts +2 -0
  108. package/dist/reporter/json.js +3 -0
  109. package/dist/reporter/markdown.d.ts +2 -0
  110. package/dist/reporter/markdown.js +52 -0
  111. package/dist/reporter/sarif.d.ts +2 -0
  112. package/dist/reporter/sarif.js +84 -0
  113. package/dist/reporter/terminal.d.ts +5 -0
  114. package/dist/reporter/terminal.js +94 -0
  115. package/dist/runtime/signal-handlers.d.ts +10 -0
  116. package/dist/runtime/signal-handlers.js +17 -0
  117. package/dist/scan-target/helpers.d.ts +20 -0
  118. package/dist/scan-target/helpers.js +268 -0
  119. package/dist/scan-target/staging.d.ts +5 -0
  120. package/dist/scan-target/staging.js +114 -0
  121. package/dist/scan-target/types.d.ts +18 -0
  122. package/dist/scan-target/types.js +1 -0
  123. package/dist/scan-target.d.ts +3 -0
  124. package/dist/scan-target.js +31 -0
  125. package/dist/scan.d.ts +54 -0
  126. package/dist/scan.js +593 -0
  127. package/dist/tui/app.d.ts +10 -0
  128. package/dist/tui/app.js +21 -0
  129. package/dist/tui/theme.d.ts +8 -0
  130. package/dist/tui/theme.js +7 -0
  131. package/dist/tui/views/dashboard.d.ts +6 -0
  132. package/dist/tui/views/dashboard.js +8 -0
  133. package/dist/tui/views/deep-scan-consent.d.ts +5 -0
  134. package/dist/tui/views/deep-scan-consent.js +6 -0
  135. package/dist/tui/views/progress.d.ts +4 -0
  136. package/dist/tui/views/progress.js +6 -0
  137. package/dist/tui/views/summary.d.ts +5 -0
  138. package/dist/tui/views/summary.js +16 -0
  139. package/dist/types/discovery.d.ts +12 -0
  140. package/dist/types/discovery.js +1 -0
  141. package/dist/types/finding.d.ts +46 -0
  142. package/dist/types/finding.js +15 -0
  143. package/dist/types/report.d.ts +25 -0
  144. package/dist/types/report.js +23 -0
  145. package/dist/wrapper.d.ts +35 -0
  146. package/dist/wrapper.js +220 -0
  147. package/package.json +97 -0
@@ -0,0 +1,259 @@
1
+ import { runStaticEngine, } from "./layer2-static/engine.js";
2
+ import { createEmptyReport } from "./types/report.js";
3
+ import { scanToolDescriptions, } from "./layer3-dynamic/tool-description-scanner.js";
4
+ import { detectToxicFlows } from "./layer3-dynamic/toxic-flow.js";
5
+ import { applyReportSummary } from "./report-summary.js";
6
+ export function runStaticPipeline(input) {
7
+ const findings = runStaticEngine({
8
+ projectRoot: input.projectRoot,
9
+ files: input.files,
10
+ symlinkEscapes: input.symlinkEscapes,
11
+ hooks: input.hooks,
12
+ config: input.config,
13
+ });
14
+ const report = createEmptyReport({
15
+ version: input.version,
16
+ kbVersion: input.kbVersion,
17
+ scanTarget: input.scanTarget,
18
+ toolsDetected: input.toolsDetected,
19
+ exitCode: 0,
20
+ });
21
+ return applyReportSummary({
22
+ ...report,
23
+ findings,
24
+ });
25
+ }
26
+ function parseSeverity(value) {
27
+ if (value === "CRITICAL" || value === "HIGH" || value === "MEDIUM" || value === "LOW") {
28
+ return value;
29
+ }
30
+ return "INFO";
31
+ }
32
+ function parseConfidence(value) {
33
+ if (value === "HIGH" || value === "MEDIUM") {
34
+ return value;
35
+ }
36
+ return "LOW";
37
+ }
38
+ function parseCategory(value) {
39
+ if (value === "ENV_OVERRIDE" ||
40
+ value === "COMMAND_EXEC" ||
41
+ value === "CONSENT_BYPASS" ||
42
+ value === "RULE_INJECTION" ||
43
+ value === "IDE_SETTINGS" ||
44
+ value === "SYMLINK_ESCAPE" ||
45
+ value === "GIT_HOOK" ||
46
+ value === "CONFIG_PRESENT" ||
47
+ value === "CONFIG_CHANGE" ||
48
+ value === "NEW_SERVER" ||
49
+ value === "TOXIC_FLOW") {
50
+ return value;
51
+ }
52
+ return "PARSE_ERROR";
53
+ }
54
+ function parseLayer3Response(resourceId, metadata) {
55
+ if (!metadata || typeof metadata !== "object") {
56
+ return [];
57
+ }
58
+ const root = metadata;
59
+ if (!Array.isArray(root.findings)) {
60
+ return [];
61
+ }
62
+ return root.findings
63
+ .filter((item) => typeof item === "object" && item !== null)
64
+ .map((item, index) => {
65
+ const findingId = item.id ?? `L3-${resourceId}-${index}`;
66
+ return {
67
+ rule_id: item.id ?? "layer3-analysis-finding",
68
+ finding_id: findingId,
69
+ severity: parseSeverity(item.severity),
70
+ category: parseCategory(item.category),
71
+ layer: "L3",
72
+ file_path: item.file_path ?? resourceId,
73
+ location: { field: item.field },
74
+ description: item.description ?? "Layer 3 analysis finding",
75
+ affected_tools: [],
76
+ cve: null,
77
+ owasp: Array.isArray(item.owasp) ? item.owasp : [],
78
+ cwe: item.cwe ?? "CWE-20",
79
+ confidence: parseConfidence(item.confidence),
80
+ evidence: typeof item.evidence === "string" ? item.evidence : null,
81
+ fixable: item.fixable ?? false,
82
+ remediation_actions: item.remediation_actions ?? [],
83
+ source_config: item.source_config ?? null,
84
+ suppressed: false,
85
+ };
86
+ });
87
+ }
88
+ function asRecord(value) {
89
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
90
+ return null;
91
+ }
92
+ return value;
93
+ }
94
+ function parseToxicClass(value) {
95
+ if (value === "untrusted_input" ||
96
+ value === "sensitive_access" ||
97
+ value === "exfiltration_sink") {
98
+ return value;
99
+ }
100
+ return null;
101
+ }
102
+ function parseToxicClasses(value) {
103
+ if (typeof value === "string") {
104
+ const parsed = parseToxicClass(value);
105
+ return parsed ? [parsed] : [];
106
+ }
107
+ if (!Array.isArray(value)) {
108
+ return [];
109
+ }
110
+ const classes = [];
111
+ for (const item of value) {
112
+ if (typeof item !== "string") {
113
+ continue;
114
+ }
115
+ const parsed = parseToxicClass(item);
116
+ if (parsed) {
117
+ classes.push(parsed);
118
+ }
119
+ }
120
+ return classes;
121
+ }
122
+ function parseToolEntries(metadata) {
123
+ const root = asRecord(metadata);
124
+ if (!root || !Array.isArray(root.tools)) {
125
+ return [];
126
+ }
127
+ return root.tools
128
+ .map((entry) => asRecord(entry))
129
+ .filter((entry) => entry !== null)
130
+ .map((entry) => {
131
+ const name = typeof entry.name === "string" ? entry.name : "";
132
+ const description = typeof entry.description === "string" ? entry.description : "";
133
+ const classifications = parseToxicClasses(entry.classifications ?? entry.classification);
134
+ return { name, description, classifications };
135
+ })
136
+ .filter((entry) => entry.name.length > 0 && entry.description.length > 0);
137
+ }
138
+ function parseToolClassifications(metadata, tools) {
139
+ const map = {};
140
+ const root = asRecord(metadata);
141
+ for (const tool of tools) {
142
+ if (!tool.classifications || tool.classifications.length === 0) {
143
+ continue;
144
+ }
145
+ map[tool.name] = tool.classifications;
146
+ }
147
+ const classificationRoot = asRecord(root?.tool_classifications ?? root?.toolClassifications);
148
+ if (!classificationRoot) {
149
+ return map;
150
+ }
151
+ for (const [toolName, value] of Object.entries(classificationRoot)) {
152
+ const parsed = parseToxicClasses(value);
153
+ if (parsed.length === 0) {
154
+ continue;
155
+ }
156
+ map[toolName] = parsed;
157
+ }
158
+ return map;
159
+ }
160
+ function deriveLayer3ToolFindings(resourceId, metadata, options = {}) {
161
+ const toolEntries = parseToolEntries(metadata);
162
+ if (toolEntries.length === 0) {
163
+ return [];
164
+ }
165
+ const toolDescriptions = toolEntries.map((entry) => ({
166
+ name: entry.name,
167
+ description: entry.description,
168
+ }));
169
+ const knownClassifications = parseToolClassifications(metadata, toolEntries);
170
+ return [
171
+ ...scanToolDescriptions({
172
+ serverId: resourceId,
173
+ tools: toolDescriptions,
174
+ unicodeAnalysis: options.unicodeAnalysis,
175
+ }),
176
+ ...detectToxicFlows({
177
+ scopeId: resourceId,
178
+ tools: toolDescriptions,
179
+ knownClassifications,
180
+ }),
181
+ ];
182
+ }
183
+ function layer3ErrorFinding(resourceId, status, description) {
184
+ const severity = status === "timeout" ? "MEDIUM" : status === "skipped_without_consent" ? "INFO" : "LOW";
185
+ return {
186
+ rule_id: `layer3-${status}`,
187
+ finding_id: `L3-${status}-${resourceId}`,
188
+ severity,
189
+ category: "PARSE_ERROR",
190
+ layer: "L3",
191
+ file_path: resourceId,
192
+ location: { field: "layer3" },
193
+ description,
194
+ affected_tools: [],
195
+ cve: null,
196
+ owasp: [],
197
+ cwe: "CWE-20",
198
+ confidence: "HIGH",
199
+ fixable: false,
200
+ remediation_actions: [],
201
+ suppressed: false,
202
+ };
203
+ }
204
+ function isRegistryMetadataResource(resourceId) {
205
+ return (resourceId.startsWith("npm:") || resourceId.startsWith("pypi:") || resourceId.startsWith("git:"));
206
+ }
207
+ export function layer3OutcomesToFindings(outcomes, options = {}) {
208
+ const findings = [];
209
+ for (const outcome of outcomes) {
210
+ if (!outcome.approved || outcome.status === "skipped_without_consent") {
211
+ findings.push(layer3ErrorFinding(outcome.resourceId, "skipped_without_consent", "Deep scan skipped because consent was not granted"));
212
+ continue;
213
+ }
214
+ if (outcome.status !== "ok" || !outcome.result) {
215
+ findings.push(layer3ErrorFinding(outcome.resourceId, outcome.status, `Deep scan failed with status: ${outcome.status}`));
216
+ continue;
217
+ }
218
+ const parsed = parseLayer3Response(outcome.resourceId, outcome.result.metadata);
219
+ const derived = deriveLayer3ToolFindings(outcome.resourceId, outcome.result.metadata, options);
220
+ const combined = [...parsed, ...derived];
221
+ if (combined.length === 0) {
222
+ if (isRegistryMetadataResource(outcome.resourceId)) {
223
+ continue;
224
+ }
225
+ findings.push(layer3ErrorFinding(outcome.resourceId, "network_error", "Deep scan response schema mismatch: expected metadata.findings[] or metadata.tools[]"));
226
+ continue;
227
+ }
228
+ findings.push(...combined);
229
+ }
230
+ return findings;
231
+ }
232
+ export function mergeLayer3Findings(baseReport, layer3Findings) {
233
+ return applyReportSummary({
234
+ ...baseReport,
235
+ findings: [...baseReport.findings, ...layer3Findings],
236
+ });
237
+ }
238
+ export async function runDeepScanWithConsent(resources, requestConsent, execute) {
239
+ const outcomes = [];
240
+ for (const resource of resources) {
241
+ const approved = await requestConsent(resource);
242
+ if (!approved) {
243
+ outcomes.push({
244
+ resourceId: resource.id,
245
+ approved: false,
246
+ status: "skipped_without_consent",
247
+ });
248
+ continue;
249
+ }
250
+ const result = await execute(resource);
251
+ outcomes.push({
252
+ resourceId: resource.id,
253
+ approved: true,
254
+ status: result.status,
255
+ result,
256
+ });
257
+ }
258
+ return outcomes;
259
+ }
@@ -0,0 +1,6 @@
1
+ import type { Finding } from "./types/finding.js";
2
+ import type { CodeGateReport, ReportSummary } from "./types/report.js";
3
+ export type ReportThreshold = "critical" | "high" | "medium" | "low" | "info";
4
+ export declare function computeExitCode(findings: Finding[], threshold?: ReportThreshold): number;
5
+ export declare function summarizeFindings(findings: Finding[], threshold?: ReportThreshold): ReportSummary;
6
+ export declare function applyReportSummary(report: CodeGateReport, threshold?: ReportThreshold): CodeGateReport;
@@ -0,0 +1,48 @@
1
+ const SEVERITY_LEVEL = {
2
+ CRITICAL: 4,
3
+ HIGH: 3,
4
+ MEDIUM: 2,
5
+ LOW: 1,
6
+ INFO: 0,
7
+ };
8
+ const THRESHOLD_LEVEL = {
9
+ critical: 4,
10
+ high: 3,
11
+ medium: 2,
12
+ low: 1,
13
+ info: 0,
14
+ };
15
+ export function computeExitCode(findings, threshold = "high") {
16
+ const unsuppressed = findings.filter((finding) => !finding.suppressed);
17
+ if (unsuppressed.length === 0) {
18
+ return 0;
19
+ }
20
+ const thresholdLevel = THRESHOLD_LEVEL[threshold];
21
+ const hasDangerous = unsuppressed.some((finding) => SEVERITY_LEVEL[finding.severity] >= thresholdLevel);
22
+ return hasDangerous ? 2 : 1;
23
+ }
24
+ export function summarizeFindings(findings, threshold = "high") {
25
+ const bySeverity = {
26
+ CRITICAL: 0,
27
+ HIGH: 0,
28
+ MEDIUM: 0,
29
+ LOW: 0,
30
+ INFO: 0,
31
+ };
32
+ for (const finding of findings) {
33
+ bySeverity[finding.severity] = (bySeverity[finding.severity] ?? 0) + 1;
34
+ }
35
+ return {
36
+ total: findings.length,
37
+ by_severity: bySeverity,
38
+ fixable: findings.filter((finding) => finding.fixable).length,
39
+ suppressed: findings.filter((finding) => finding.suppressed).length,
40
+ exit_code: computeExitCode(findings, threshold),
41
+ };
42
+ }
43
+ export function applyReportSummary(report, threshold = "high") {
44
+ return {
45
+ ...report,
46
+ summary: summarizeFindings(report.findings, threshold),
47
+ };
48
+ }
@@ -0,0 +1,2 @@
1
+ import type { CodeGateReport } from "../types/report.js";
2
+ export declare function renderHtmlReport(report: CodeGateReport): string;
@@ -0,0 +1,103 @@
1
+ function escapeHtml(value) {
2
+ return value
3
+ .replaceAll("&", "&")
4
+ .replaceAll("<", "&lt;")
5
+ .replaceAll(">", "&gt;")
6
+ .replaceAll('"', "&quot;")
7
+ .replaceAll("'", "&#39;");
8
+ }
9
+ function formatLocation(location) {
10
+ const parts = [];
11
+ if (location.field) {
12
+ parts.push(location.field);
13
+ }
14
+ if (typeof location.line === "number") {
15
+ parts.push(`line ${location.line}`);
16
+ }
17
+ if (typeof location.column === "number") {
18
+ parts.push(`col ${location.column}`);
19
+ }
20
+ return parts.join(", ") || "-";
21
+ }
22
+ function renderSummary(report) {
23
+ return `
24
+ <ul>
25
+ <li><strong>Total findings:</strong> ${report.summary.total}</li>
26
+ <li><strong>CRITICAL:</strong> ${report.summary.by_severity.CRITICAL ?? 0}</li>
27
+ <li><strong>HIGH:</strong> ${report.summary.by_severity.HIGH ?? 0}</li>
28
+ <li><strong>MEDIUM:</strong> ${report.summary.by_severity.MEDIUM ?? 0}</li>
29
+ <li><strong>LOW:</strong> ${report.summary.by_severity.LOW ?? 0}</li>
30
+ <li><strong>INFO:</strong> ${report.summary.by_severity.INFO ?? 0}</li>
31
+ <li><strong>Fixable:</strong> ${report.summary.fixable}</li>
32
+ <li><strong>Suppressed:</strong> ${report.summary.suppressed}</li>
33
+ <li><strong>Exit code:</strong> ${report.summary.exit_code}</li>
34
+ </ul>
35
+ `;
36
+ }
37
+ function renderFindings(report) {
38
+ if (report.findings.length === 0) {
39
+ return "<p>No findings.</p>";
40
+ }
41
+ const rows = report.findings
42
+ .map((finding) => {
43
+ const location = formatLocation(finding.location);
44
+ return `
45
+ <tr>
46
+ <td>${escapeHtml(finding.severity)}</td>
47
+ <td>${escapeHtml(finding.category)}</td>
48
+ <td>${escapeHtml(finding.file_path)}</td>
49
+ <td>${escapeHtml(location)}</td>
50
+ <td>${escapeHtml(finding.description)}</td>
51
+ </tr>
52
+ `;
53
+ })
54
+ .join("\n");
55
+ return `
56
+ <table>
57
+ <thead>
58
+ <tr>
59
+ <th>Severity</th>
60
+ <th>Category</th>
61
+ <th>File</th>
62
+ <th>Location</th>
63
+ <th>Description</th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ ${rows}
68
+ </tbody>
69
+ </table>
70
+ `;
71
+ }
72
+ export function renderHtmlReport(report) {
73
+ return `<!doctype html>
74
+ <html lang="en">
75
+ <head>
76
+ <meta charset="utf-8" />
77
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
78
+ <title>CodeGate Report</title>
79
+ <style>
80
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 24px; color: #0f172a; }
81
+ h1, h2 { margin: 0 0 12px 0; }
82
+ .meta { margin: 0 0 20px 0; color: #334155; }
83
+ table { border-collapse: collapse; width: 100%; margin-top: 12px; }
84
+ th, td { border: 1px solid #cbd5e1; padding: 8px; text-align: left; vertical-align: top; }
85
+ th { background: #f1f5f9; }
86
+ code { background: #f8fafc; padding: 2px 4px; border-radius: 4px; }
87
+ </style>
88
+ </head>
89
+ <body>
90
+ <h1>CodeGate Report</h1>
91
+ <p class="meta">
92
+ Version <code>${escapeHtml(report.version)}</code> |
93
+ Target <code>${escapeHtml(report.scan_target)}</code> |
94
+ KB <code>${escapeHtml(report.kb_version)}</code> |
95
+ Generated <code>${escapeHtml(report.timestamp)}</code>
96
+ </p>
97
+ <h2>Summary</h2>
98
+ ${renderSummary(report)}
99
+ <h2>Findings</h2>
100
+ ${renderFindings(report)}
101
+ </body>
102
+ </html>`;
103
+ }
@@ -0,0 +1,2 @@
1
+ import type { CodeGateReport } from "../types/report.js";
2
+ export declare function renderJsonReport(report: CodeGateReport): string;
@@ -0,0 +1,3 @@
1
+ export function renderJsonReport(report) {
2
+ return JSON.stringify(report, null, 2);
3
+ }
@@ -0,0 +1,2 @@
1
+ import type { CodeGateReport } from "../types/report.js";
2
+ export declare function renderMarkdownReport(report: CodeGateReport): string;
@@ -0,0 +1,52 @@
1
+ function escapePipes(value) {
2
+ return value.replaceAll("|", "\\|");
3
+ }
4
+ function formatLocation(location) {
5
+ const parts = [];
6
+ if (location.field) {
7
+ parts.push(location.field);
8
+ }
9
+ if (typeof location.line === "number") {
10
+ parts.push(`line ${location.line}`);
11
+ }
12
+ if (typeof location.column === "number") {
13
+ parts.push(`col ${location.column}`);
14
+ }
15
+ return parts.join(", ") || "-";
16
+ }
17
+ export function renderMarkdownReport(report) {
18
+ const lines = [];
19
+ lines.push("# CodeGate Report");
20
+ lines.push("");
21
+ lines.push(`- Version: \`${report.version}\``);
22
+ lines.push(`- Target: \`${report.scan_target}\``);
23
+ lines.push(`- Timestamp: \`${report.timestamp}\``);
24
+ lines.push(`- KB Version: \`${report.kb_version}\``);
25
+ lines.push(`- Exit Code: \`${report.summary.exit_code}\``);
26
+ lines.push("");
27
+ lines.push("## Summary");
28
+ lines.push("");
29
+ lines.push("| Metric | Value |");
30
+ lines.push("| --- | --- |");
31
+ lines.push(`| Total findings | ${report.summary.total} |`);
32
+ lines.push(`| CRITICAL | ${report.summary.by_severity.CRITICAL ?? 0} |`);
33
+ lines.push(`| HIGH | ${report.summary.by_severity.HIGH ?? 0} |`);
34
+ lines.push(`| MEDIUM | ${report.summary.by_severity.MEDIUM ?? 0} |`);
35
+ lines.push(`| LOW | ${report.summary.by_severity.LOW ?? 0} |`);
36
+ lines.push(`| INFO | ${report.summary.by_severity.INFO ?? 0} |`);
37
+ lines.push(`| Fixable | ${report.summary.fixable} |`);
38
+ lines.push(`| Suppressed | ${report.summary.suppressed} |`);
39
+ lines.push("");
40
+ lines.push("## Findings");
41
+ lines.push("");
42
+ if (report.findings.length === 0) {
43
+ lines.push("No findings.");
44
+ return lines.join("\n");
45
+ }
46
+ lines.push("| Severity | Category | File | Location | Description |");
47
+ lines.push("| --- | --- | --- | --- | --- |");
48
+ for (const finding of report.findings) {
49
+ lines.push(`| ${finding.severity} | ${finding.category} | \`${escapePipes(finding.file_path)}\` | ${escapePipes(formatLocation(finding.location))} | ${escapePipes(finding.description)} |`);
50
+ }
51
+ return lines.join("\n");
52
+ }
@@ -0,0 +1,2 @@
1
+ import type { CodeGateReport } from "../types/report.js";
2
+ export declare function renderSarifReport(report: CodeGateReport): string;
@@ -0,0 +1,84 @@
1
+ function toSarifLevel(severity) {
2
+ if (severity === "CRITICAL" || severity === "HIGH") {
3
+ return "error";
4
+ }
5
+ if (severity === "MEDIUM") {
6
+ return "warning";
7
+ }
8
+ return "note";
9
+ }
10
+ function findingToLocation(finding) {
11
+ const region = {};
12
+ if (typeof finding.location.line === "number") {
13
+ region.startLine = finding.location.line;
14
+ }
15
+ if (typeof finding.location.column === "number") {
16
+ region.startColumn = finding.location.column;
17
+ }
18
+ return {
19
+ physicalLocation: {
20
+ artifactLocation: {
21
+ uri: finding.file_path,
22
+ },
23
+ region: Object.keys(region).length > 0 ? region : undefined,
24
+ },
25
+ };
26
+ }
27
+ function buildRules(findings) {
28
+ const byRuleId = new Map();
29
+ for (const finding of findings) {
30
+ if (byRuleId.has(finding.rule_id)) {
31
+ continue;
32
+ }
33
+ byRuleId.set(finding.rule_id, {
34
+ id: finding.rule_id,
35
+ shortDescription: { text: finding.description },
36
+ properties: {
37
+ category: finding.category,
38
+ layer: finding.layer,
39
+ },
40
+ });
41
+ }
42
+ return Array.from(byRuleId.values());
43
+ }
44
+ function findingToResult(finding) {
45
+ return {
46
+ ruleId: finding.rule_id,
47
+ level: toSarifLevel(finding.severity),
48
+ message: { text: finding.description },
49
+ locations: [findingToLocation(finding)],
50
+ properties: {
51
+ finding_id: finding.finding_id,
52
+ severity: finding.severity,
53
+ category: finding.category,
54
+ layer: finding.layer,
55
+ confidence: finding.confidence,
56
+ cve: finding.cve,
57
+ owasp: finding.owasp,
58
+ cwe: finding.cwe,
59
+ evidence: finding.evidence ?? null,
60
+ fixable: finding.fixable,
61
+ suppressed: finding.suppressed,
62
+ source_config: finding.source_config ?? null,
63
+ },
64
+ };
65
+ }
66
+ export function renderSarifReport(report) {
67
+ const sarif = {
68
+ version: "2.1.0",
69
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
70
+ runs: [
71
+ {
72
+ tool: {
73
+ driver: {
74
+ name: "CodeGate",
75
+ semanticVersion: report.version,
76
+ rules: buildRules(report.findings),
77
+ },
78
+ },
79
+ results: report.findings.map(findingToResult),
80
+ },
81
+ ],
82
+ };
83
+ return JSON.stringify(sarif, null, 2);
84
+ }
@@ -0,0 +1,5 @@
1
+ import type { CodeGateReport } from "../types/report.js";
2
+ export interface TerminalRenderOptions {
3
+ verbose?: boolean;
4
+ }
5
+ export declare function renderTerminalReport(report: CodeGateReport, options?: TerminalRenderOptions): string;
@@ -0,0 +1,94 @@
1
+ import { toAbsoluteDisplayPath } from "../path-display.js";
2
+ function appendLabeledList(lines, label, values) {
3
+ if (values.length === 0) {
4
+ return;
5
+ }
6
+ lines.push(` ${label}:`);
7
+ for (const value of values) {
8
+ lines.push(` - ${value}`);
9
+ }
10
+ }
11
+ function appendLabeledText(lines, label, value) {
12
+ if (value.length === 0) {
13
+ return;
14
+ }
15
+ lines.push(` ${label}: ${value}`);
16
+ }
17
+ function appendEvidence(lines, evidence) {
18
+ const evidenceLines = evidence.split("\n");
19
+ if (evidenceLines.length === 1) {
20
+ lines.push(` Evidence: ${evidenceLines[0]}`);
21
+ return;
22
+ }
23
+ lines.push(" Evidence:");
24
+ for (const evidenceLine of evidenceLines) {
25
+ lines.push(` ${evidenceLine}`);
26
+ }
27
+ }
28
+ function formatLocation(location) {
29
+ const parts = [];
30
+ if (location.field) {
31
+ parts.push(location.field);
32
+ }
33
+ if (typeof location.line === "number") {
34
+ const column = typeof location.column === "number" ? `:${location.column}` : "";
35
+ parts.push(`line ${location.line}${column}`);
36
+ }
37
+ return parts.length > 0 ? parts.join(" @ ") : null;
38
+ }
39
+ export function renderTerminalReport(report, options = {}) {
40
+ const verbose = options.verbose === true;
41
+ const lines = [];
42
+ lines.push(`CodeGate v${report.version}`);
43
+ lines.push(`Target: ${report.scan_target}`);
44
+ lines.push(`Findings: ${report.summary.total}`);
45
+ lines.push(`CRITICAL: ${report.summary.by_severity.CRITICAL ?? 0}`);
46
+ lines.push(`HIGH: ${report.summary.by_severity.HIGH ?? 0}`);
47
+ lines.push(`MEDIUM: ${report.summary.by_severity.MEDIUM ?? 0}`);
48
+ lines.push(`LOW: ${report.summary.by_severity.LOW ?? 0}`);
49
+ lines.push(`INFO: ${report.summary.by_severity.INFO ?? 0}`);
50
+ lines.push("");
51
+ if (report.findings.length === 0) {
52
+ lines.push("No findings.");
53
+ return lines.join("\n");
54
+ }
55
+ for (const finding of report.findings) {
56
+ lines.push(`[${finding.severity}] ${toAbsoluteDisplayPath(report.scan_target, finding.file_path)}`);
57
+ lines.push(` ${finding.description}`);
58
+ if (finding.incident_title) {
59
+ appendLabeledText(lines, "Incident", finding.incident_title);
60
+ }
61
+ if (finding.evidence && finding.evidence.length > 0) {
62
+ appendEvidence(lines, finding.evidence);
63
+ }
64
+ appendLabeledList(lines, "Observed", finding.observed ?? []);
65
+ if (finding.inference) {
66
+ appendLabeledText(lines, "Inference", finding.inference);
67
+ }
68
+ appendLabeledList(lines, "Not verified", finding.not_verified ?? []);
69
+ if (verbose) {
70
+ lines.push(` Rule: ${finding.rule_id}`);
71
+ lines.push(` Finding ID: ${finding.finding_id}`);
72
+ lines.push(` Category: ${finding.category} | Layer: ${finding.layer} | Confidence: ${finding.confidence}`);
73
+ const formattedLocation = formatLocation(finding.location);
74
+ if (formattedLocation) {
75
+ lines.push(` Location: ${formattedLocation}`);
76
+ }
77
+ if (finding.cve) {
78
+ lines.push(` CVE: ${finding.cve}`);
79
+ }
80
+ lines.push(` CWE: ${finding.cwe}`);
81
+ if (finding.owasp.length > 0) {
82
+ lines.push(` OWASP: ${finding.owasp.join(", ")}`);
83
+ }
84
+ if (finding.remediation_actions.length > 0) {
85
+ lines.push(` Remediation: ${finding.remediation_actions.join(", ")}`);
86
+ }
87
+ }
88
+ if (finding.layer === "L3" && finding.source_config) {
89
+ const fieldSuffix = finding.source_config.field ? ` (${finding.source_config.field})` : "";
90
+ lines.push(` source config: ${finding.source_config.file_path}${fieldSuffix}`);
91
+ }
92
+ }
93
+ return lines.join("\n");
94
+ }
@@ -0,0 +1,10 @@
1
+ export interface SignalProcessLike {
2
+ on: (event: string, listener: () => void) => unknown;
3
+ off: (event: string, listener: () => void) => unknown;
4
+ }
5
+ export interface RegisterSignalHandlersOptions {
6
+ processLike?: SignalProcessLike;
7
+ signals?: Array<"SIGINT" | "SIGTERM">;
8
+ onSignal: (signal: "SIGINT" | "SIGTERM") => void;
9
+ }
10
+ export declare function registerSignalHandlers(options: RegisterSignalHandlersOptions): () => void;