-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontroller.ts
More file actions
144 lines (122 loc) · 4.28 KB
/
Copy pathcontroller.ts
File metadata and controls
144 lines (122 loc) · 4.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import * as path from "node:path";
import * as vscode from "vscode";
import { Logger } from "../logger";
import { classifyDocument } from "./classify";
import { runDockerScanner, DockerScanResult } from "./docker";
import { parseSarif } from "./sarif";
import * as config from "../config";
/**
* Orchestrates on-open / on-save SAST and IaC scanning via Docker.
* Triggers only on open and save — never on keystroke — so cost is minimal.
*
* One AbortController per file: if the same file is saved again while a scan
* is in flight, the old scan is cancelled before the new one starts.
*/
export class SastController implements vscode.Disposable {
readonly diagnostics: vscode.DiagnosticCollection;
private readonly inflight = new Map<string, AbortController>();
constructor(private readonly logger: Logger) {
this.diagnostics =
vscode.languages.createDiagnosticCollection("DevGuard SAST");
}
async scan(document: vscode.TextDocument): Promise<void> {
if (!config.isSastEnabled()) {
this.clearForDocument(document);
return;
}
const kind = classifyDocument(document.uri.fsPath);
// Iac scan is kinda implemented, but since the current checkov configuration in devguard does not support scanning individual files, we don't trigger it for now.
if (!kind || document.uri.scheme !== "file" || kind === "iac") {
return;
}
const key = document.uri.toString();
this.inflight.get(key)?.abort();
const controller = new AbortController();
this.inflight.set(key, controller);
const repoRoot = this.resolveRepoRoot(document);
this.logger.info(`Running ${kind} scan for ${document.uri.fsPath}...`);
try {
const result = await runDockerScanner(
{ kind, signal: controller.signal },
document.uri,
);
if (this.inflight.get(key) !== controller) {
return;
}
if (result.exitCode === -1 && !result.sarifJson) {
return;
}
let byUri: Map<vscode.Uri, vscode.Diagnostic[]>;
try {
byUri = parseSarif(result.sarifJson, repoRoot);
} catch (parseErr) {
// exit code wasn't 0 and stdout isn't valid SARIF -> genuine crash, not "vulns found"
this.logger.error(`DevGuard ${kind} scan failed`, parseErr);
vscode.window.showErrorMessage(
`DevGuard: ${kind.toUpperCase()} scan failed.\n\n${result.stderr || (parseErr as Error).message}`,
);
return;
}
if (!byUri.has(document.uri)) {
this.diagnostics.delete(document.uri);
}
for (const [uri, diags] of byUri) {
this.diagnostics.set(uri, diags);
}
this.logger.info(
`DevGuard ${kind}: ${countTotal(byUri)} finding(s) in ${path.basename(
document.uri.fsPath,
)}`,
);
if (result.exitCode !== 0) {
this.logger.info(`DevGuard ${kind} scanner exited ${result.exitCode}.`);
}
} catch (err) {
if ((err as Error).message === "Docker scan aborted") {
return;
}
const message = err instanceof Error ? err.message : String(err);
this.logger.error(`DevGuard ${kind} scan failed`, err);
vscode.window.showErrorMessage(
`DevGuard: ${kind.toUpperCase()} scan failed.\n\n${message}`,
);
} finally {
if (this.inflight.get(key) === controller) {
this.inflight.delete(key);
}
}
}
/** Clear diagnostics when a file is closed. */
clearForDocument(document: vscode.TextDocument): void {
this.diagnostics.delete(document.uri);
this.inflight.get(document.uri.toString())?.abort();
this.inflight.delete(document.uri.toString());
}
dispose(): void {
for (const ctrl of this.inflight.values()) {
ctrl.abort();
}
this.inflight.clear();
this.diagnostics.dispose();
}
private resolveRepoRoot(document: vscode.TextDocument): string {
return (
vscode.workspace.getWorkspaceFolder(document.uri)?.uri.fsPath ??
path.dirname(document.uri.fsPath)
);
}
clearAll(): void {
for (const ctrl of this.inflight.values()) {
ctrl.abort();
}
this.inflight.clear();
this.diagnostics.clear();
}
}
function countTotal(byUri: Map<vscode.Uri, vscode.Diagnostic[]>): number {
let n = 0;
for (const diags of byUri.values()) {
n += diags.length;
}
return n;
}