auditor-lambda 0.3.18 → 0.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/audit-code-wrapper-lib.mjs +165 -27
- package/dist/cli.js +3 -56
- package/dist/extractors/browserExtension.d.ts +13 -0
- package/dist/extractors/browserExtension.js +378 -0
- package/dist/extractors/disposition.js +8 -1
- package/dist/extractors/fsIntake.js +1 -0
- package/dist/extractors/graph.js +6 -0
- package/dist/extractors/pathPatterns.js +15 -1
- package/dist/extractors/surfaces.d.ts +4 -1
- package/dist/extractors/surfaces.js +15 -2
- package/dist/io/runArtifacts.js +48 -0
- package/dist/orchestrator/autoFixExecutor.js +41 -10
- package/dist/orchestrator/internalExecutors.js +6 -2
- package/dist/orchestrator/planning.js +5 -1
- package/dist/orchestrator/syntaxResolutionExecutor.js +10 -1
- package/dist/orchestrator/unitBuilder.d.ts +3 -1
- package/dist/orchestrator/unitBuilder.js +19 -4
- package/dist/supervisor/operatorHandoff.js +9 -2
- package/docs/operator-guide.md +3 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +141 -1
- package/skills/audit-code/SKILL.md +5 -0
- package/skills/audit-code/agents/openai.yaml +4 -0
- package/skills/audit-code/audit-code.prompt.md +41 -6
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import { isAuditExcludedStatus } from "./disposition.js";
|
|
3
|
+
import { graphEdge, normalizeGraphPath, resolveCandidate, } from "./graphPathUtils.js";
|
|
4
|
+
export const BROWSER_EXTENSION_HEURISTIC_NOTE = "Chrome extension manifest and HTML asset references were resolved deterministically from local paths; verify unusual dynamic registration manually.";
|
|
5
|
+
const CHROME_EXTENSION_BACKGROUND_EDGE = "chrome-extension-background-link";
|
|
6
|
+
const CHROME_EXTENSION_CONTENT_SCRIPT_EDGE = "chrome-extension-content-script-link";
|
|
7
|
+
const CHROME_EXTENSION_CONTENT_STYLE_EDGE = "chrome-extension-content-style-link";
|
|
8
|
+
const CHROME_EXTENSION_UI_PAGE_EDGE = "chrome-extension-ui-page-link";
|
|
9
|
+
const CHROME_EXTENSION_WEB_ACCESSIBLE_EDGE = "chrome-extension-web-accessible-resource-link";
|
|
10
|
+
const HTML_RESOURCE_EDGE = "html-resource-link";
|
|
11
|
+
const CHROME_EXTENSION_EDGE_CONFIDENCE = 0.94;
|
|
12
|
+
const HTML_RESOURCE_EDGE_CONFIDENCE = 0.86;
|
|
13
|
+
const EXTENSION_SURFACE_EDGE_KINDS = new Set([
|
|
14
|
+
CHROME_EXTENSION_BACKGROUND_EDGE,
|
|
15
|
+
CHROME_EXTENSION_CONTENT_SCRIPT_EDGE,
|
|
16
|
+
CHROME_EXTENSION_UI_PAGE_EDGE,
|
|
17
|
+
]);
|
|
18
|
+
const HIGH_RISK_PERMISSION_TOKENS = [
|
|
19
|
+
"<all_urls>",
|
|
20
|
+
"activeTab",
|
|
21
|
+
"debugger",
|
|
22
|
+
"declarativeNetRequest",
|
|
23
|
+
"downloads",
|
|
24
|
+
"downloads.open",
|
|
25
|
+
"nativeMessaging",
|
|
26
|
+
"proxy",
|
|
27
|
+
"scripting",
|
|
28
|
+
"tabs",
|
|
29
|
+
"unlimitedStorage",
|
|
30
|
+
"webNavigation",
|
|
31
|
+
"webRequest",
|
|
32
|
+
];
|
|
33
|
+
function isRecord(value) {
|
|
34
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
35
|
+
}
|
|
36
|
+
function asString(value) {
|
|
37
|
+
return typeof value === "string" && value.trim().length > 0
|
|
38
|
+
? value.trim()
|
|
39
|
+
: undefined;
|
|
40
|
+
}
|
|
41
|
+
function asStringArray(value) {
|
|
42
|
+
return Array.isArray(value)
|
|
43
|
+
? value
|
|
44
|
+
.map((item) => asString(item))
|
|
45
|
+
.filter((item) => item !== undefined)
|
|
46
|
+
: [];
|
|
47
|
+
}
|
|
48
|
+
function parseJsonObject(content) {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(content);
|
|
51
|
+
return isRecord(parsed) ? parsed : undefined;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function isBrowserExtensionManifestPath(path) {
|
|
58
|
+
return posix.basename(normalizeGraphPath(path)).toLowerCase() === "manifest.json";
|
|
59
|
+
}
|
|
60
|
+
function isBrowserExtensionManifest(value) {
|
|
61
|
+
return (typeof value.manifest_version === "number" &&
|
|
62
|
+
(isRecord(value.background) ||
|
|
63
|
+
Array.isArray(value.content_scripts) ||
|
|
64
|
+
isRecord(value.action) ||
|
|
65
|
+
isRecord(value.browser_action) ||
|
|
66
|
+
isRecord(value.page_action) ||
|
|
67
|
+
isRecord(value.side_panel) ||
|
|
68
|
+
isRecord(value.options_ui) ||
|
|
69
|
+
typeof value.options_page === "string" ||
|
|
70
|
+
typeof value.devtools_page === "string" ||
|
|
71
|
+
isRecord(value.chrome_url_overrides) ||
|
|
72
|
+
Array.isArray(value.web_accessible_resources)));
|
|
73
|
+
}
|
|
74
|
+
function localPathCandidate(specifier) {
|
|
75
|
+
const withoutQuery = specifier.trim().split(/[?#]/, 1)[0]?.trim() ?? "";
|
|
76
|
+
if (withoutQuery.length === 0 ||
|
|
77
|
+
withoutQuery.startsWith("<") ||
|
|
78
|
+
withoutQuery.includes("*") ||
|
|
79
|
+
/^[a-z][a-z0-9+.-]*:/i.test(withoutQuery) ||
|
|
80
|
+
withoutQuery.startsWith("//")) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return normalizeGraphPath(withoutQuery).replace(/^\/+/, "");
|
|
84
|
+
}
|
|
85
|
+
function resolveLocalReference(fromPath, specifier, pathLookup) {
|
|
86
|
+
const local = localPathCandidate(specifier);
|
|
87
|
+
if (!local) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const isRootRelative = specifier.trim().startsWith("/");
|
|
91
|
+
const baseDir = posix.dirname(normalizeGraphPath(fromPath));
|
|
92
|
+
const candidate = isRootRelative || baseDir === "." ? local : posix.join(baseDir, local);
|
|
93
|
+
return resolveCandidate(candidate, pathLookup);
|
|
94
|
+
}
|
|
95
|
+
function addManifestReference(edges, params) {
|
|
96
|
+
const target = resolveLocalReference(params.fromPath, params.specifier, params.pathLookup);
|
|
97
|
+
if (!target) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
edges.push(graphEdge({
|
|
101
|
+
from: params.fromPath,
|
|
102
|
+
to: target,
|
|
103
|
+
kind: params.kind,
|
|
104
|
+
confidence: CHROME_EXTENSION_EDGE_CONFIDENCE,
|
|
105
|
+
reason: `Chrome extension manifest field '${params.field}' references '${params.specifier}'.`,
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
function collectExtensionUiPageReferences(manifest) {
|
|
109
|
+
const entries = [];
|
|
110
|
+
for (const objectField of ["action", "browser_action", "page_action"]) {
|
|
111
|
+
const value = manifest[objectField];
|
|
112
|
+
if (isRecord(value)) {
|
|
113
|
+
const popup = asString(value.default_popup);
|
|
114
|
+
if (popup)
|
|
115
|
+
entries.push({ field: `${objectField}.default_popup`, specifier: popup });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const sidePanel = manifest.side_panel;
|
|
119
|
+
if (isRecord(sidePanel)) {
|
|
120
|
+
const path = asString(sidePanel.default_path);
|
|
121
|
+
if (path)
|
|
122
|
+
entries.push({ field: "side_panel.default_path", specifier: path });
|
|
123
|
+
}
|
|
124
|
+
const optionsPage = asString(manifest.options_page);
|
|
125
|
+
if (optionsPage)
|
|
126
|
+
entries.push({ field: "options_page", specifier: optionsPage });
|
|
127
|
+
const optionsUi = manifest.options_ui;
|
|
128
|
+
if (isRecord(optionsUi)) {
|
|
129
|
+
const page = asString(optionsUi.page);
|
|
130
|
+
if (page)
|
|
131
|
+
entries.push({ field: "options_ui.page", specifier: page });
|
|
132
|
+
}
|
|
133
|
+
const devtoolsPage = asString(manifest.devtools_page);
|
|
134
|
+
if (devtoolsPage)
|
|
135
|
+
entries.push({ field: "devtools_page", specifier: devtoolsPage });
|
|
136
|
+
const overrides = manifest.chrome_url_overrides;
|
|
137
|
+
if (isRecord(overrides)) {
|
|
138
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
139
|
+
const page = asString(value);
|
|
140
|
+
if (page)
|
|
141
|
+
entries.push({ field: `chrome_url_overrides.${key}`, specifier: page });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const sandbox = manifest.sandbox;
|
|
145
|
+
if (isRecord(sandbox)) {
|
|
146
|
+
asStringArray(sandbox.pages).forEach((page, index) => entries.push({ field: `sandbox.pages.${index}`, specifier: page }));
|
|
147
|
+
}
|
|
148
|
+
return entries;
|
|
149
|
+
}
|
|
150
|
+
function collectWebAccessibleReferences(manifest) {
|
|
151
|
+
const entries = [];
|
|
152
|
+
const resources = manifest.web_accessible_resources;
|
|
153
|
+
if (!Array.isArray(resources)) {
|
|
154
|
+
return entries;
|
|
155
|
+
}
|
|
156
|
+
resources.forEach((item, index) => {
|
|
157
|
+
if (typeof item === "string") {
|
|
158
|
+
entries.push({
|
|
159
|
+
field: `web_accessible_resources.${index}`,
|
|
160
|
+
specifier: item,
|
|
161
|
+
});
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (!isRecord(item)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
asStringArray(item.resources).forEach((resource, resourceIndex) => entries.push({
|
|
168
|
+
field: `web_accessible_resources.${index}.resources.${resourceIndex}`,
|
|
169
|
+
specifier: resource,
|
|
170
|
+
}));
|
|
171
|
+
});
|
|
172
|
+
return entries;
|
|
173
|
+
}
|
|
174
|
+
export function extractChromeExtensionManifestEdges(fromPath, content, pathLookup) {
|
|
175
|
+
if (!isBrowserExtensionManifestPath(fromPath)) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
const manifest = parseJsonObject(content);
|
|
179
|
+
if (!manifest || !isBrowserExtensionManifest(manifest)) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
const edges = [];
|
|
183
|
+
const background = manifest.background;
|
|
184
|
+
if (isRecord(background)) {
|
|
185
|
+
const serviceWorker = asString(background.service_worker);
|
|
186
|
+
if (serviceWorker) {
|
|
187
|
+
addManifestReference(edges, {
|
|
188
|
+
fromPath,
|
|
189
|
+
field: "background.service_worker",
|
|
190
|
+
kind: CHROME_EXTENSION_BACKGROUND_EDGE,
|
|
191
|
+
specifier: serviceWorker,
|
|
192
|
+
pathLookup,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
asStringArray(background.scripts).forEach((script, index) => addManifestReference(edges, {
|
|
196
|
+
fromPath,
|
|
197
|
+
field: `background.scripts.${index}`,
|
|
198
|
+
kind: CHROME_EXTENSION_BACKGROUND_EDGE,
|
|
199
|
+
specifier: script,
|
|
200
|
+
pathLookup,
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
const contentScripts = manifest.content_scripts;
|
|
204
|
+
if (Array.isArray(contentScripts)) {
|
|
205
|
+
contentScripts.forEach((item, index) => {
|
|
206
|
+
if (!isRecord(item)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
asStringArray(item.js).forEach((script, scriptIndex) => addManifestReference(edges, {
|
|
210
|
+
fromPath,
|
|
211
|
+
field: `content_scripts.${index}.js.${scriptIndex}`,
|
|
212
|
+
kind: CHROME_EXTENSION_CONTENT_SCRIPT_EDGE,
|
|
213
|
+
specifier: script,
|
|
214
|
+
pathLookup,
|
|
215
|
+
}));
|
|
216
|
+
asStringArray(item.css).forEach((style, styleIndex) => addManifestReference(edges, {
|
|
217
|
+
fromPath,
|
|
218
|
+
field: `content_scripts.${index}.css.${styleIndex}`,
|
|
219
|
+
kind: CHROME_EXTENSION_CONTENT_STYLE_EDGE,
|
|
220
|
+
specifier: style,
|
|
221
|
+
pathLookup,
|
|
222
|
+
}));
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
for (const { field, specifier } of collectExtensionUiPageReferences(manifest)) {
|
|
226
|
+
addManifestReference(edges, {
|
|
227
|
+
fromPath,
|
|
228
|
+
field,
|
|
229
|
+
kind: CHROME_EXTENSION_UI_PAGE_EDGE,
|
|
230
|
+
specifier,
|
|
231
|
+
pathLookup,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
for (const { field, specifier } of collectWebAccessibleReferences(manifest)) {
|
|
235
|
+
addManifestReference(edges, {
|
|
236
|
+
fromPath,
|
|
237
|
+
field,
|
|
238
|
+
kind: CHROME_EXTENSION_WEB_ACCESSIBLE_EDGE,
|
|
239
|
+
specifier,
|
|
240
|
+
pathLookup,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return edges;
|
|
244
|
+
}
|
|
245
|
+
function extractHtmlAttributeReferences(content, elementName, attributeName) {
|
|
246
|
+
const unquotedAttributeValue = "[^\\s\"'<>`]+";
|
|
247
|
+
const pattern = new RegExp(`<${elementName}\\b[^>]*\\b${attributeName}\\s*=\\s*(?:"([^"]+)"|'([^']+)'|(${unquotedAttributeValue}))`, "gi");
|
|
248
|
+
const values = [];
|
|
249
|
+
for (const match of content.matchAll(pattern)) {
|
|
250
|
+
const value = match[1] ?? match[2] ?? match[3];
|
|
251
|
+
if (value)
|
|
252
|
+
values.push(value);
|
|
253
|
+
}
|
|
254
|
+
return values;
|
|
255
|
+
}
|
|
256
|
+
export function extractHtmlResourceEdges(fromPath, content, pathLookup) {
|
|
257
|
+
const normalized = normalizeGraphPath(fromPath).toLowerCase();
|
|
258
|
+
if (!normalized.endsWith(".html") && !normalized.endsWith(".htm")) {
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
const references = [
|
|
262
|
+
...extractHtmlAttributeReferences(content, "script", "src"),
|
|
263
|
+
...extractHtmlAttributeReferences(content, "link", "href"),
|
|
264
|
+
];
|
|
265
|
+
const edges = [];
|
|
266
|
+
for (const specifier of references) {
|
|
267
|
+
const target = resolveLocalReference(fromPath, specifier, pathLookup);
|
|
268
|
+
if (!target) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
edges.push(graphEdge({
|
|
272
|
+
from: fromPath,
|
|
273
|
+
to: target,
|
|
274
|
+
kind: HTML_RESOURCE_EDGE,
|
|
275
|
+
confidence: HTML_RESOURCE_EDGE_CONFIDENCE,
|
|
276
|
+
reason: `HTML resource attribute references '${specifier}'.`,
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
return edges;
|
|
280
|
+
}
|
|
281
|
+
export function hasBrowserExtensionManifestFile(repoManifest) {
|
|
282
|
+
return repoManifest.files.some((file) => normalizeGraphPath(file.path).toLowerCase() === "manifest.json");
|
|
283
|
+
}
|
|
284
|
+
export function deriveBrowserExtensionLensesForPath(path) {
|
|
285
|
+
const normalized = normalizeGraphPath(path).toLowerCase();
|
|
286
|
+
if (normalized === "manifest.json") {
|
|
287
|
+
return ["security", "correctness", "config_deployment", "operability"];
|
|
288
|
+
}
|
|
289
|
+
if (normalized.startsWith("service/") ||
|
|
290
|
+
normalized.startsWith("background/") ||
|
|
291
|
+
normalized.includes("service-worker") ||
|
|
292
|
+
normalized.includes("background")) {
|
|
293
|
+
return ["security", "correctness", "reliability", "observability"];
|
|
294
|
+
}
|
|
295
|
+
if (normalized.startsWith("content/") || normalized.includes("content-script")) {
|
|
296
|
+
return ["security", "correctness", "reliability"];
|
|
297
|
+
}
|
|
298
|
+
if (normalized.includes("popup") ||
|
|
299
|
+
normalized.includes("sidebar") ||
|
|
300
|
+
normalized.includes("side-panel") ||
|
|
301
|
+
normalized.includes("panel") ||
|
|
302
|
+
normalized.endsWith(".html")) {
|
|
303
|
+
return ["security", "correctness", "maintainability"];
|
|
304
|
+
}
|
|
305
|
+
if (normalized.includes("worker")) {
|
|
306
|
+
return ["correctness", "reliability", "performance"];
|
|
307
|
+
}
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
export function inferBrowserExtensionUnitKind(path) {
|
|
311
|
+
const normalized = normalizeGraphPath(path).toLowerCase();
|
|
312
|
+
if (normalized === "manifest.json")
|
|
313
|
+
return "extension_config";
|
|
314
|
+
if (normalized.startsWith("service/") || normalized.startsWith("background/")) {
|
|
315
|
+
return "extension_background";
|
|
316
|
+
}
|
|
317
|
+
if (normalized.startsWith("content/"))
|
|
318
|
+
return "extension_content";
|
|
319
|
+
if (normalized.includes("worker"))
|
|
320
|
+
return "worker";
|
|
321
|
+
if (normalized.endsWith(".html"))
|
|
322
|
+
return "extension_ui";
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
function isExecutableExtensionSurfaceTarget(path) {
|
|
326
|
+
const normalized = normalizeGraphPath(path).toLowerCase();
|
|
327
|
+
return (normalized.endsWith(".js") ||
|
|
328
|
+
normalized.endsWith(".mjs") ||
|
|
329
|
+
normalized.endsWith(".cjs") ||
|
|
330
|
+
normalized.endsWith(".html") ||
|
|
331
|
+
normalized.endsWith(".htm"));
|
|
332
|
+
}
|
|
333
|
+
export function buildBrowserExtensionSurfacesFromGraph(graphBundle, disposition) {
|
|
334
|
+
const references = Array.isArray(graphBundle?.graphs.references)
|
|
335
|
+
? graphBundle.graphs.references
|
|
336
|
+
: [];
|
|
337
|
+
const dispositionMap = new Map(disposition?.files.map((item) => [item.path, item.status]) ?? []);
|
|
338
|
+
const surfaces = [];
|
|
339
|
+
const seen = new Set();
|
|
340
|
+
for (const edge of references) {
|
|
341
|
+
if (!edge.kind || !EXTENSION_SURFACE_EDGE_KINDS.has(edge.kind)) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (!isExecutableExtensionSurfaceTarget(edge.to)) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const status = dispositionMap.get(edge.to);
|
|
348
|
+
if (status && isAuditExcludedStatus(status)) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
const kind = edge.kind === CHROME_EXTENSION_BACKGROUND_EDGE ? "background" : "interface";
|
|
352
|
+
const key = `${kind}:${edge.to}`;
|
|
353
|
+
if (seen.has(key)) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
seen.add(key);
|
|
357
|
+
surfaces.push({
|
|
358
|
+
id: `surface:${edge.to}`,
|
|
359
|
+
kind,
|
|
360
|
+
entrypoint: edge.to,
|
|
361
|
+
exposure: edge.kind === CHROME_EXTENSION_CONTENT_SCRIPT_EDGE ? "network" : "local",
|
|
362
|
+
notes: [BROWSER_EXTENSION_HEURISTIC_NOTE],
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
return surfaces.sort((a, b) => a.entrypoint.localeCompare(b.entrypoint) || a.kind.localeCompare(b.kind));
|
|
366
|
+
}
|
|
367
|
+
export function chromeExtensionRiskSignalsForManifest(content) {
|
|
368
|
+
const manifest = parseJsonObject(content);
|
|
369
|
+
if (!manifest || !isBrowserExtensionManifest(manifest)) {
|
|
370
|
+
return [];
|
|
371
|
+
}
|
|
372
|
+
const permissions = [
|
|
373
|
+
...asStringArray(manifest.permissions),
|
|
374
|
+
...asStringArray(manifest.optional_permissions),
|
|
375
|
+
...asStringArray(manifest.host_permissions),
|
|
376
|
+
];
|
|
377
|
+
return HIGH_RISK_PERMISSION_TOKENS.filter((token) => permissions.some((permission) => permission === token));
|
|
378
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isNodeModulesOrGit, isBuildOutput, isVendorPath, isBinaryArtifact, isLicensePath, isLockfilePath, isLogPath, isDocPath, isAuditArtifactPath, isGeneratedTestArtifactPath, isGeneratedInstallArtifactPath, isExamplesOrFixturesPath, normalizeExtractorPath, } from "./pathPatterns.js";
|
|
1
|
+
import { isNodeModulesOrGit, isBuildOutput, isVendorPath, isBinaryArtifact, isLicensePath, isLockfilePath, isLogPath, isDocPath, isGeneratedPath, isAuditArtifactPath, isGeneratedTestArtifactPath, isGeneratedInstallArtifactPath, isExamplesOrFixturesPath, normalizeExtractorPath, } from "./pathPatterns.js";
|
|
2
2
|
function inferDisposition(path) {
|
|
3
3
|
const normalized = normalizeExtractorPath(path);
|
|
4
4
|
if (isNodeModulesOrGit(normalized)) {
|
|
@@ -33,6 +33,13 @@ function inferDisposition(path) {
|
|
|
33
33
|
reason: "Generated audit artifact.",
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
+
if (isGeneratedPath(normalized)) {
|
|
37
|
+
return {
|
|
38
|
+
path,
|
|
39
|
+
status: "generated",
|
|
40
|
+
reason: "Generated artifact path.",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
36
43
|
if (isGeneratedTestArtifactPath(normalized)) {
|
|
37
44
|
return {
|
|
38
45
|
path,
|
package/dist/extractors/graph.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { posix } from "node:path";
|
|
4
4
|
import { isAuditExcludedStatus } from "./disposition.js";
|
|
5
|
+
import { extractChromeExtensionManifestEdges, extractHtmlResourceEdges, } from "./browserExtension.js";
|
|
5
6
|
import { extractCargoWorkspaceMemberEdges, extractGoWorkspaceModuleEdges, extractMavenModuleEdges, extractPackageEntrypointEdges, extractPackageScriptEdges, extractPyprojectTestpathLinks, extractTypescriptProjectReferenceEdges, extractWorkspacePackageEdges, extractYamlPathReferenceEdges, isCargoManifestPath, isGoWorkspaceManifestPath, isMavenPomPath, isPyprojectPath, } from "./graphManifestEdges.js";
|
|
6
7
|
import { graphEdge, graphLookupKey, normalizeGraphPath, resolveCandidate, } from "./graphPathUtils.js";
|
|
7
8
|
import { isTestPath, normalizeExtractorPath } from "./pathPatterns.js";
|
|
@@ -10,6 +11,7 @@ const SOURCE_LANGUAGES = new Set([
|
|
|
10
11
|
"typescript",
|
|
11
12
|
"javascript",
|
|
12
13
|
"json",
|
|
14
|
+
"html",
|
|
13
15
|
"yaml",
|
|
14
16
|
"python",
|
|
15
17
|
"go",
|
|
@@ -27,6 +29,8 @@ const SOURCE_EXTENSIONS = [
|
|
|
27
29
|
".mjs",
|
|
28
30
|
".cjs",
|
|
29
31
|
".json",
|
|
32
|
+
".html",
|
|
33
|
+
".htm",
|
|
30
34
|
".yml",
|
|
31
35
|
".yaml",
|
|
32
36
|
".py",
|
|
@@ -1309,6 +1313,8 @@ export function buildGraphBundle(repoManifest, disposition, options = {}) {
|
|
|
1309
1313
|
references.push(...extractReferenceEdges(file.path, content, pathLookup));
|
|
1310
1314
|
references.push(...extractJsonSchemaReferenceEdges(file.path, content, pathLookup));
|
|
1311
1315
|
references.push(...extractPackageEntrypointEdges(file.path, content, pathLookup));
|
|
1316
|
+
references.push(...extractChromeExtensionManifestEdges(file.path, content, pathLookup));
|
|
1317
|
+
references.push(...extractHtmlResourceEdges(file.path, content, pathLookup));
|
|
1312
1318
|
references.push(...extractPackageScriptEdges(file.path, content, pathLookup));
|
|
1313
1319
|
references.push(...extractWorkspacePackageEdges(file.path, content, pathLookup));
|
|
1314
1320
|
references.push(...extractTypescriptProjectReferenceEdges(file.path, content, pathLookup));
|
|
@@ -10,6 +10,16 @@ const BINARY_EXTENSIONS = [
|
|
|
10
10
|
".jpg",
|
|
11
11
|
".jpeg",
|
|
12
12
|
".gif",
|
|
13
|
+
".webp",
|
|
14
|
+
".svg",
|
|
15
|
+
".ico",
|
|
16
|
+
".bmp",
|
|
17
|
+
".avif",
|
|
18
|
+
".wasm",
|
|
19
|
+
".woff",
|
|
20
|
+
".woff2",
|
|
21
|
+
".ttf",
|
|
22
|
+
".otf",
|
|
13
23
|
".pdf",
|
|
14
24
|
".zip",
|
|
15
25
|
];
|
|
@@ -216,7 +226,11 @@ export function isDeploymentConfigPath(normalized) {
|
|
|
216
226
|
return hasToken(normalized, DEPLOYMENT_KEYWORDS) || endsWithAny(normalized, [".yml", ".yaml"]);
|
|
217
227
|
}
|
|
218
228
|
export function isGeneratedPath(normalized) {
|
|
219
|
-
return isVendorPath(normalized) ||
|
|
229
|
+
return (isVendorPath(normalized) ||
|
|
230
|
+
normalized.endsWith(".map") ||
|
|
231
|
+
normalized.endsWith(".wasm.mjs") ||
|
|
232
|
+
normalized.endsWith(".wasm.js") ||
|
|
233
|
+
hasToken(normalized, ["generated", "autogenerated"]));
|
|
220
234
|
}
|
|
221
235
|
export function isSurfacePath(normalized) {
|
|
222
236
|
return (hasSegment(normalized, "api") ||
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { RepoManifest } from "../types.js";
|
|
2
2
|
import type { FileDisposition } from "../types/disposition.js";
|
|
3
|
+
import type { GraphBundle } from "../types/graph.js";
|
|
3
4
|
import type { SurfaceManifest } from "../types/surfaces.js";
|
|
4
5
|
/**
|
|
5
6
|
* Detects likely execution surfaces from file paths using the shared extractor
|
|
6
7
|
* heuristics, primarily to seed later audit planning.
|
|
7
8
|
*/
|
|
8
|
-
export declare function buildSurfaceManifest(repoManifest: RepoManifest, disposition?: FileDisposition
|
|
9
|
+
export declare function buildSurfaceManifest(repoManifest: RepoManifest, disposition?: FileDisposition, options?: {
|
|
10
|
+
graphBundle?: GraphBundle;
|
|
11
|
+
}): SurfaceManifest;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildBrowserExtensionSurfacesFromGraph } from "./browserExtension.js";
|
|
1
2
|
import { isAuditExcludedStatus } from "./disposition.js";
|
|
2
3
|
import { EXTRACTOR_HEURISTIC_NOTE, isBackgroundSurfacePath, isNetworkSurfacePath, isSurfacePath, normalizeExtractorPath, } from "./pathPatterns.js";
|
|
3
4
|
function methodsForPath(path) {
|
|
@@ -11,9 +12,18 @@ function methodsForPath(path) {
|
|
|
11
12
|
* Detects likely execution surfaces from file paths using the shared extractor
|
|
12
13
|
* heuristics, primarily to seed later audit planning.
|
|
13
14
|
*/
|
|
14
|
-
export function buildSurfaceManifest(repoManifest, disposition) {
|
|
15
|
+
export function buildSurfaceManifest(repoManifest, disposition, options = {}) {
|
|
15
16
|
const surfaces = [];
|
|
17
|
+
const seen = new Set();
|
|
16
18
|
const dispositionMap = new Map(disposition?.files.map((item) => [item.path, item.status]) ?? []);
|
|
19
|
+
function addSurface(surface) {
|
|
20
|
+
const key = `${surface.kind}:${surface.entrypoint}`;
|
|
21
|
+
if (seen.has(key)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
seen.add(key);
|
|
25
|
+
surfaces.push(surface);
|
|
26
|
+
}
|
|
17
27
|
for (const file of repoManifest.files) {
|
|
18
28
|
const status = dispositionMap.get(file.path);
|
|
19
29
|
if (status && isAuditExcludedStatus(status)) {
|
|
@@ -21,7 +31,7 @@ export function buildSurfaceManifest(repoManifest, disposition) {
|
|
|
21
31
|
}
|
|
22
32
|
const normalized = normalizeExtractorPath(file.path);
|
|
23
33
|
if (isSurfacePath(normalized)) {
|
|
24
|
-
|
|
34
|
+
addSurface({
|
|
25
35
|
id: `surface:${file.path}`,
|
|
26
36
|
kind: isBackgroundSurfacePath(normalized) ? "background" : "interface",
|
|
27
37
|
entrypoint: file.path,
|
|
@@ -31,5 +41,8 @@ export function buildSurfaceManifest(repoManifest, disposition) {
|
|
|
31
41
|
});
|
|
32
42
|
}
|
|
33
43
|
}
|
|
44
|
+
for (const surface of buildBrowserExtensionSurfacesFromGraph(options.graphBundle, disposition)) {
|
|
45
|
+
addSurface(surface);
|
|
46
|
+
}
|
|
34
47
|
return { surfaces };
|
|
35
48
|
}
|
package/dist/io/runArtifacts.js
CHANGED
|
@@ -10,6 +10,8 @@ const findingSchemaPath = join(packageRoot, "schemas", "finding.schema.json");
|
|
|
10
10
|
const CURRENT_TASK_FILENAME = "current-task.json";
|
|
11
11
|
const CURRENT_PROMPT_FILENAME = "current-prompt.md";
|
|
12
12
|
const CURRENT_TASKS_FILENAME = "current-tasks.json";
|
|
13
|
+
const CURRENT_SINGLE_TASK_FILENAME = "current-single-task.json";
|
|
14
|
+
const CURRENT_SINGLE_TASK_PROMPT_FILENAME = "current-single-task-prompt.md";
|
|
13
15
|
const CURRENT_SCHEMA_FILENAME = "audit-result.schema.json";
|
|
14
16
|
const CURRENT_RESULTS_SCHEMA_FILENAME = "audit-results.schema.json";
|
|
15
17
|
const CURRENT_FINDING_SCHEMA_FILENAME = "finding.schema.json";
|
|
@@ -63,6 +65,49 @@ async function writeDispatchSchemaFiles(artifactsDir) {
|
|
|
63
65
|
await writeFile(join(dispatchDir, CURRENT_RESULTS_SCHEMA_FILENAME), await readFile(auditResultsSchemaPath, "utf8"), "utf8");
|
|
64
66
|
await writeFile(join(dispatchDir, CURRENT_FINDING_SCHEMA_FILENAME), await readFile(findingSchemaPath, "utf8"), "utf8");
|
|
65
67
|
}
|
|
68
|
+
function renderSingleTaskFallbackPrompt(task, auditTask) {
|
|
69
|
+
const commandArgv = JSON.stringify(task.worker_command);
|
|
70
|
+
const lineCounts = auditTask.file_paths
|
|
71
|
+
.map((path) => `- ${path}: ${auditTask.file_line_counts?.[path] ?? 0} lines`)
|
|
72
|
+
.join("\n");
|
|
73
|
+
return [
|
|
74
|
+
"# audit-code single-task fallback",
|
|
75
|
+
"",
|
|
76
|
+
"Use this file only when the conversation host cannot dispatch subagents.",
|
|
77
|
+
"This prompt is generated deterministically from the first pending task.",
|
|
78
|
+
"",
|
|
79
|
+
`run_id: ${task.run_id}`,
|
|
80
|
+
`task_id: ${auditTask.task_id}`,
|
|
81
|
+
`unit_id: ${auditTask.unit_id}`,
|
|
82
|
+
`pass_id: ${auditTask.pass_id}`,
|
|
83
|
+
`lens: ${auditTask.lens}`,
|
|
84
|
+
`rationale: ${auditTask.rationale}`,
|
|
85
|
+
"",
|
|
86
|
+
"Assigned files and line counts:",
|
|
87
|
+
lineCounts,
|
|
88
|
+
"",
|
|
89
|
+
"Instructions:",
|
|
90
|
+
"1. Read only the assigned files above.",
|
|
91
|
+
"2. Produce exactly one AuditResult object for task_id above, wrapped in a JSON array.",
|
|
92
|
+
"3. Write that JSON array to audit_results_path.",
|
|
93
|
+
"4. Run worker_command exactly, then stop without checking audit state or reading a report.",
|
|
94
|
+
"",
|
|
95
|
+
`audit_results_path: ${task.audit_results_path}`,
|
|
96
|
+
`worker_command: ${commandArgv}`,
|
|
97
|
+
"",
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
100
|
+
async function writeSingleTaskFallbackFiles(artifactsDir, task, currentTasks) {
|
|
101
|
+
if (task.preferred_executor !== "agent" ||
|
|
102
|
+
!task.audit_results_path ||
|
|
103
|
+
!currentTasks ||
|
|
104
|
+
currentTasks.length === 0) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const firstTask = currentTasks[0];
|
|
108
|
+
await writeJsonFile(join(artifactsDir, "dispatch", CURRENT_SINGLE_TASK_FILENAME), firstTask);
|
|
109
|
+
await writeFile(join(artifactsDir, "dispatch", CURRENT_SINGLE_TASK_PROMPT_FILENAME), renderSingleTaskFallbackPrompt(task, firstTask), "utf8");
|
|
110
|
+
}
|
|
66
111
|
export async function writeWorkerTaskFiles(task, prompt, paths, artifactsDir, currentTasks, options = {}) {
|
|
67
112
|
await mkdir(paths.runDir, { recursive: true });
|
|
68
113
|
await writeJsonFile(paths.taskPath, task);
|
|
@@ -78,6 +123,7 @@ export async function writeWorkerTaskFiles(task, prompt, paths, artifactsDir, cu
|
|
|
78
123
|
await writeJsonFile(join(artifactsDir, "dispatch", CURRENT_TASK_FILENAME), task);
|
|
79
124
|
await writeFile(join(artifactsDir, "dispatch", CURRENT_PROMPT_FILENAME), prompt, "utf8");
|
|
80
125
|
await writeJsonFile(join(artifactsDir, "dispatch", CURRENT_TASKS_FILENAME), currentTasks ?? []);
|
|
126
|
+
await writeSingleTaskFallbackFiles(artifactsDir, task, currentTasks);
|
|
81
127
|
await writeDispatchSchemaFiles(artifactsDir);
|
|
82
128
|
}
|
|
83
129
|
export async function writeDispatchBatchFiles(artifactsDir, runs, currentTasks) {
|
|
@@ -121,6 +167,8 @@ export async function clearDispatchFiles(artifactsDir) {
|
|
|
121
167
|
CURRENT_TASK_FILENAME,
|
|
122
168
|
CURRENT_PROMPT_FILENAME,
|
|
123
169
|
CURRENT_TASKS_FILENAME,
|
|
170
|
+
CURRENT_SINGLE_TASK_FILENAME,
|
|
171
|
+
CURRENT_SINGLE_TASK_PROMPT_FILENAME,
|
|
124
172
|
CURRENT_SCHEMA_FILENAME,
|
|
125
173
|
CURRENT_RESULTS_SCHEMA_FILENAME,
|
|
126
174
|
CURRENT_FINDING_SCHEMA_FILENAME,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
1
2
|
import { join } from "node:path";
|
|
2
3
|
import { isAuditExcludedStatus } from "../extractors/disposition.js";
|
|
3
4
|
import { resolveNodeTool, runFirstAvailableCommand, } from "./localCommands.js";
|
|
@@ -5,6 +6,35 @@ function tryRunConfiguredFormatter(root, candidates) {
|
|
|
5
6
|
const result = runFirstAvailableCommand(root, candidates);
|
|
6
7
|
return result !== null && !result.error && result.exitCode === 0;
|
|
7
8
|
}
|
|
9
|
+
const PRETTIER_CONFIG_FILES = [
|
|
10
|
+
".prettierrc",
|
|
11
|
+
".prettierrc.json",
|
|
12
|
+
".prettierrc.yml",
|
|
13
|
+
".prettierrc.yaml",
|
|
14
|
+
".prettierrc.json5",
|
|
15
|
+
".prettierrc.js",
|
|
16
|
+
".prettierrc.cjs",
|
|
17
|
+
".prettierrc.mjs",
|
|
18
|
+
"prettier.config.js",
|
|
19
|
+
"prettier.config.cjs",
|
|
20
|
+
"prettier.config.mjs",
|
|
21
|
+
];
|
|
22
|
+
function hasPrettierConfig(root) {
|
|
23
|
+
if (PRETTIER_CONFIG_FILES.some((file) => existsSync(join(root, file)))) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const packageJsonPath = join(root, "package.json");
|
|
27
|
+
if (!existsSync(packageJsonPath)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
32
|
+
return packageJson.prettier !== undefined;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
8
38
|
export function runAutoFixExecutor(bundle, root) {
|
|
9
39
|
if (!bundle.file_disposition) {
|
|
10
40
|
throw new Error("Cannot run auto fix executor without file_disposition");
|
|
@@ -20,16 +50,17 @@ export function runAutoFixExecutor(bundle, root) {
|
|
|
20
50
|
}
|
|
21
51
|
const executedTools = [];
|
|
22
52
|
// JS, TS, HTML, CSS, JSON, YAML, MD
|
|
23
|
-
if (
|
|
24
|
-
extensions.has("
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
53
|
+
if (hasPrettierConfig(root) &&
|
|
54
|
+
(extensions.has("ts") ||
|
|
55
|
+
extensions.has("js") ||
|
|
56
|
+
extensions.has("tsx") ||
|
|
57
|
+
extensions.has("jsx") ||
|
|
58
|
+
extensions.has("html") ||
|
|
59
|
+
extensions.has("css") ||
|
|
60
|
+
extensions.has("json") ||
|
|
61
|
+
extensions.has("yml") ||
|
|
62
|
+
extensions.has("yaml") ||
|
|
63
|
+
extensions.has("md"))) {
|
|
33
64
|
if (tryRunConfiguredFormatter(root, [
|
|
34
65
|
...resolveNodeTool(root, join("node_modules", "prettier", "bin", "prettier.cjs"), ["--write", "."], "prettier --write ."),
|
|
35
66
|
{ command: "prettier", args: ["--write", "."], display: "prettier --write ." },
|