auditor-lambda 0.3.18 → 0.3.19
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/dist/cli.js +1 -54
- 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/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 +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
3
|
import { Buffer } from "node:buffer";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
@@ -308,30 +308,6 @@ async function listBatchResultFiles(batchDir) {
|
|
|
308
308
|
}
|
|
309
309
|
return files;
|
|
310
310
|
}
|
|
311
|
-
const PROJECT_SIGNALS = [
|
|
312
|
-
"package.json",
|
|
313
|
-
"go.mod",
|
|
314
|
-
"Cargo.toml",
|
|
315
|
-
"pom.xml",
|
|
316
|
-
"build.gradle",
|
|
317
|
-
"pyproject.toml",
|
|
318
|
-
"setup.py",
|
|
319
|
-
"setup.cfg",
|
|
320
|
-
"Makefile",
|
|
321
|
-
"CMakeLists.txt",
|
|
322
|
-
];
|
|
323
|
-
async function detectProjectRoot(root) {
|
|
324
|
-
for (const signal of PROJECT_SIGNALS) {
|
|
325
|
-
try {
|
|
326
|
-
await access(join(root, signal));
|
|
327
|
-
return signal;
|
|
328
|
-
}
|
|
329
|
-
catch {
|
|
330
|
-
// not found, try next
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
311
|
function buildPendingAuditTasks(bundle) {
|
|
336
312
|
const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
|
|
337
313
|
const pendingTasks = (bundle.audit_tasks ?? []).filter((task) => task.status !== "complete" && !completedTaskIds.has(task.task_id));
|
|
@@ -716,35 +692,6 @@ async function cmdRunToCompletion(argv) {
|
|
|
716
692
|
let pendingBatchAuditResults = batchResultsDir
|
|
717
693
|
? await listBatchResultFiles(batchResultsDir)
|
|
718
694
|
: [];
|
|
719
|
-
const earlyBundle = await loadArtifactBundle(artifactsDir);
|
|
720
|
-
if (!earlyBundle.unit_manifest) {
|
|
721
|
-
const foundSignal = await detectProjectRoot(root);
|
|
722
|
-
if (!foundSignal) {
|
|
723
|
-
const blocker = `No recognisable project signals found in ${root}. Expected one of: ${PROJECT_SIGNALS.join(", ")}. Check that --root points to the repository root, not a subdirectory or an unrelated path.`;
|
|
724
|
-
const earlyState = deriveAuditState(earlyBundle);
|
|
725
|
-
const blockedState = buildBlockedAuditState({
|
|
726
|
-
state: earlyState,
|
|
727
|
-
obligationId: null,
|
|
728
|
-
executor: null,
|
|
729
|
-
blocker,
|
|
730
|
-
});
|
|
731
|
-
await emitEnvelope({
|
|
732
|
-
root,
|
|
733
|
-
artifactsDir,
|
|
734
|
-
bundle: { ...earlyBundle, audit_state: blockedState },
|
|
735
|
-
audit_state: blockedState,
|
|
736
|
-
selected_obligation: null,
|
|
737
|
-
selected_executor: null,
|
|
738
|
-
progress_made: false,
|
|
739
|
-
artifacts_written: [],
|
|
740
|
-
progress_summary: blocker,
|
|
741
|
-
next_likely_step: null,
|
|
742
|
-
providerName: provider.name,
|
|
743
|
-
isConfigError: true,
|
|
744
|
-
});
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
695
|
let pendingAuditResultsPath = getFlag(argv, "--results");
|
|
749
696
|
let pendingRuntimeUpdatesPath = getFlag(argv, "--updates");
|
|
750
697
|
let pendingExternalAnalyzerPath = getFlag(argv, "--external-analyzer-results");
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Lens, RepoManifest } from "../types.js";
|
|
2
|
+
import type { FileDisposition } from "../types/disposition.js";
|
|
3
|
+
import type { GraphBundle, GraphEdge } from "../types/graph.js";
|
|
4
|
+
import type { SurfaceRecord } from "../types/surfaces.js";
|
|
5
|
+
export declare const BROWSER_EXTENSION_HEURISTIC_NOTE = "Chrome extension manifest and HTML asset references were resolved deterministically from local paths; verify unusual dynamic registration manually.";
|
|
6
|
+
export declare function isBrowserExtensionManifestPath(path: string): boolean;
|
|
7
|
+
export declare function extractChromeExtensionManifestEdges(fromPath: string, content: string, pathLookup: Map<string, string>): GraphEdge[];
|
|
8
|
+
export declare function extractHtmlResourceEdges(fromPath: string, content: string, pathLookup: Map<string, string>): GraphEdge[];
|
|
9
|
+
export declare function hasBrowserExtensionManifestFile(repoManifest: RepoManifest): boolean;
|
|
10
|
+
export declare function deriveBrowserExtensionLensesForPath(path: string): Lens[];
|
|
11
|
+
export declare function inferBrowserExtensionUnitKind(path: string): string | undefined;
|
|
12
|
+
export declare function buildBrowserExtensionSurfacesFromGraph(graphBundle: GraphBundle | undefined, disposition?: FileDisposition): SurfaceRecord[];
|
|
13
|
+
export declare function chromeExtensionRiskSignalsForManifest(content: string): string[];
|
|
@@ -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
|
}
|
|
@@ -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 ." },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { buildFileDisposition } from "../extractors/disposition.js";
|
|
2
|
+
import { buildFileDisposition, isAuditExcludedStatus, } from "../extractors/disposition.js";
|
|
3
3
|
import { buildGraphBundle, buildGraphBundleFromFs, } from "../extractors/graph.js";
|
|
4
4
|
import { buildCriticalFlowManifest } from "../extractors/flows.js";
|
|
5
5
|
import { buildRiskRegister } from "../extractors/risk.js";
|
|
@@ -123,6 +123,10 @@ export async function runIntakeExecutor(bundle, root) {
|
|
|
123
123
|
hash_files: false,
|
|
124
124
|
});
|
|
125
125
|
const disposition = buildFileDisposition(repoManifest);
|
|
126
|
+
const auditableCount = disposition.files.filter((file) => !isAuditExcludedStatus(file.status)).length;
|
|
127
|
+
if (auditableCount === 0) {
|
|
128
|
+
throw new Error(`No auditable files found in ${root}. The repository may be empty, generated-only, documentation-only, or filtered by .auditorignore.`);
|
|
129
|
+
}
|
|
126
130
|
return {
|
|
127
131
|
updated: {
|
|
128
132
|
...bundle,
|
|
@@ -140,7 +144,6 @@ export async function runStructureExecutor(bundle, root) {
|
|
|
140
144
|
const externalAnalyzerResults = bundle.external_analyzer_results;
|
|
141
145
|
const disposition = bundle.file_disposition ?? buildFileDisposition(bundle.repo_manifest);
|
|
142
146
|
const unitManifest = buildUnitManifest(bundle.repo_manifest, disposition);
|
|
143
|
-
const surfaceManifest = buildSurfaceManifest(bundle.repo_manifest, disposition);
|
|
144
147
|
const graphBundle = root
|
|
145
148
|
? await buildGraphBundleFromFs(bundle.repo_manifest, root, disposition, {
|
|
146
149
|
externalAnalyzerResults,
|
|
@@ -148,6 +151,7 @@ export async function runStructureExecutor(bundle, root) {
|
|
|
148
151
|
: buildGraphBundle(bundle.repo_manifest, disposition, {
|
|
149
152
|
externalAnalyzerResults,
|
|
150
153
|
});
|
|
154
|
+
const surfaceManifest = buildSurfaceManifest(bundle.repo_manifest, disposition, { graphBundle });
|
|
151
155
|
const criticalFlows = buildCriticalFlowManifest(bundle.repo_manifest, surfaceManifest, disposition);
|
|
152
156
|
const riskRegister = buildRiskRegister(unitManifest, criticalFlows, externalAnalyzerResults);
|
|
153
157
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { applyUnitCoverage, createCoverageMatrix, markExcludedPath, } from "../coverage.js";
|
|
2
2
|
import { isAuditExcludedStatus } from "../extractors/disposition.js";
|
|
3
|
+
import { hasBrowserExtensionManifestFile } from "../extractors/browserExtension.js";
|
|
3
4
|
import { deriveRequiredLensesForPath } from "./unitBuilder.js";
|
|
4
5
|
const CATEGORY_LENS_TABLE = [
|
|
5
6
|
[["security", "secret"], ["security", "correctness"]],
|
|
@@ -47,6 +48,7 @@ function applyAnalyzerCoverage(coverage, externalAnalyzerResults) {
|
|
|
47
48
|
}
|
|
48
49
|
export function initializeCoverageFromPlan(repoManifest, unitManifest, disposition, externalAnalyzerResults) {
|
|
49
50
|
const coverage = createCoverageMatrix(repoManifest.files.map((file) => file.path));
|
|
51
|
+
const isBrowserExtensionProject = hasBrowserExtensionManifestFile(repoManifest);
|
|
50
52
|
const dispositionMap = new Map(disposition.files.map((item) => [item.path, item.status]));
|
|
51
53
|
for (const file of repoManifest.files) {
|
|
52
54
|
const status = dispositionMap.get(file.path);
|
|
@@ -66,7 +68,9 @@ export function initializeCoverageFromPlan(repoManifest, unitManifest, dispositi
|
|
|
66
68
|
}
|
|
67
69
|
for (const file of repoManifest.files) {
|
|
68
70
|
const unitIds = unitIdsByPath.get(file.path) ?? [];
|
|
69
|
-
const requiredLenses = deriveRequiredLensesForPath(file.path
|
|
71
|
+
const requiredLenses = deriveRequiredLensesForPath(file.path, {
|
|
72
|
+
isBrowserExtensionProject,
|
|
73
|
+
});
|
|
70
74
|
for (const unitId of unitIds) {
|
|
71
75
|
applyUnitCoverage(coverage, file.path, unitId, requiredLenses);
|
|
72
76
|
}
|
|
@@ -15,6 +15,14 @@ const ESLINT_CONFIG_FILES = [
|
|
|
15
15
|
".eslintrc.yml",
|
|
16
16
|
".eslintrc.yaml",
|
|
17
17
|
];
|
|
18
|
+
const TSCONFIG_FILES = [
|
|
19
|
+
"tsconfig.json",
|
|
20
|
+
"tsconfig.build.json",
|
|
21
|
+
"jsconfig.json",
|
|
22
|
+
];
|
|
23
|
+
function hasTypeScriptConfig(root) {
|
|
24
|
+
return TSCONFIG_FILES.some((file) => existsSync(join(root, file)));
|
|
25
|
+
}
|
|
18
26
|
function hasEslintConfig(root) {
|
|
19
27
|
if (ESLINT_CONFIG_FILES.some((file) => existsSync(join(root, file)))) {
|
|
20
28
|
return true;
|
|
@@ -109,7 +117,8 @@ function runEslint(root) {
|
|
|
109
117
|
}
|
|
110
118
|
export function runSyntaxResolutionExecutor(bundle, root) {
|
|
111
119
|
const items = [];
|
|
112
|
-
if (
|
|
120
|
+
if (hasTypeScriptConfig(root) &&
|
|
121
|
+
bundle.file_disposition?.files.some((f) => f.path.endsWith(".ts"))) {
|
|
113
122
|
items.push(...runTsc(root));
|
|
114
123
|
}
|
|
115
124
|
if (bundle.file_disposition?.files.some((f) => f.path.endsWith(".ts") || f.path.endsWith(".js"))) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Lens, RepoManifest, UnitManifest } from "../types.js";
|
|
2
2
|
import type { FileDisposition } from "../types/disposition.js";
|
|
3
3
|
export declare const LENS_ORDER: Lens[];
|
|
4
|
-
export declare function deriveRequiredLensesForPath(path: string
|
|
4
|
+
export declare function deriveRequiredLensesForPath(path: string, options?: {
|
|
5
|
+
isBrowserExtensionProject?: boolean;
|
|
6
|
+
}): Lens[];
|
|
5
7
|
export declare function buildUnitManifest(repoManifest: RepoManifest, disposition?: FileDisposition): UnitManifest;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deriveBrowserExtensionLensesForPath, hasBrowserExtensionManifestFile, inferBrowserExtensionUnitKind, } from "../extractors/browserExtension.js";
|
|
1
2
|
import { bucketFile } from "../extractors/bucketing.js";
|
|
2
3
|
import { isAuditExcludedStatus } from "../extractors/disposition.js";
|
|
3
4
|
import { pathTokens, normalizeExtractorPath } from "../extractors/pathPatterns.js";
|
|
@@ -14,7 +15,13 @@ const LENS_MAP = {
|
|
|
14
15
|
generated_vendor: ["maintainability"],
|
|
15
16
|
unknown: ["correctness"],
|
|
16
17
|
};
|
|
17
|
-
function inferUnitKind(path) {
|
|
18
|
+
function inferUnitKind(path, isBrowserExtensionProject = false) {
|
|
19
|
+
if (isBrowserExtensionProject) {
|
|
20
|
+
const extensionKind = inferBrowserExtensionUnitKind(path);
|
|
21
|
+
if (extensionKind) {
|
|
22
|
+
return extensionKind;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
18
25
|
const normalized = path.toLowerCase();
|
|
19
26
|
if (normalized.startsWith("apps/") || normalized.startsWith("services/"))
|
|
20
27
|
return "service";
|
|
@@ -94,7 +101,7 @@ function applyExtensionLensGuards(path, lenses) {
|
|
|
94
101
|
}
|
|
95
102
|
return lenses;
|
|
96
103
|
}
|
|
97
|
-
export function deriveRequiredLensesForPath(path) {
|
|
104
|
+
export function deriveRequiredLensesForPath(path, options = {}) {
|
|
98
105
|
const assignment = bucketFile(path);
|
|
99
106
|
const required = new Set();
|
|
100
107
|
for (const bucket of assignment.buckets) {
|
|
@@ -102,6 +109,11 @@ export function deriveRequiredLensesForPath(path) {
|
|
|
102
109
|
required.add(lens);
|
|
103
110
|
}
|
|
104
111
|
}
|
|
112
|
+
if (options.isBrowserExtensionProject) {
|
|
113
|
+
for (const lens of deriveBrowserExtensionLensesForPath(path)) {
|
|
114
|
+
required.add(lens);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
105
117
|
return applyExtensionLensGuards(path, sortLenses(required));
|
|
106
118
|
}
|
|
107
119
|
function inferCriticalFlows(files, requiredLenses) {
|
|
@@ -128,13 +140,14 @@ function inferCriticalFlows(files, requiredLenses) {
|
|
|
128
140
|
}
|
|
129
141
|
export function buildUnitManifest(repoManifest, disposition) {
|
|
130
142
|
const units = new Map();
|
|
143
|
+
const isBrowserExtensionProject = hasBrowserExtensionManifestFile(repoManifest);
|
|
131
144
|
const dispositionMap = new Map(disposition?.files.map((item) => [item.path, item.status]) ?? []);
|
|
132
145
|
for (const file of repoManifest.files) {
|
|
133
146
|
const status = dispositionMap.get(file.path);
|
|
134
147
|
if (file.excluded || (status && isAuditExcludedStatus(status))) {
|
|
135
148
|
continue;
|
|
136
149
|
}
|
|
137
|
-
const kind = inferUnitKind(file.path);
|
|
150
|
+
const kind = inferUnitKind(file.path, isBrowserExtensionProject);
|
|
138
151
|
const unitId = inferUnitId(file.path, kind);
|
|
139
152
|
const existing = units.get(unitId) ?? {
|
|
140
153
|
unit_id: unitId,
|
|
@@ -148,7 +161,9 @@ export function buildUnitManifest(repoManifest, disposition) {
|
|
|
148
161
|
}
|
|
149
162
|
const assignment = bucketFile(file.path);
|
|
150
163
|
const required = new Set(existing.required_lenses);
|
|
151
|
-
for (const lens of deriveRequiredLensesForPath(file.path
|
|
164
|
+
for (const lens of deriveRequiredLensesForPath(file.path, {
|
|
165
|
+
isBrowserExtensionProject,
|
|
166
|
+
})) {
|
|
152
167
|
required.add(lens);
|
|
153
168
|
}
|
|
154
169
|
existing.required_lenses = sortLenses(required);
|
|
@@ -118,7 +118,7 @@ function buildInteractiveProviderHint(status, providerName, sessionConfigPath, i
|
|
|
118
118
|
return null;
|
|
119
119
|
}
|
|
120
120
|
if (isConfigError) {
|
|
121
|
-
return `Configuration error: Verify --root points to
|
|
121
|
+
return `Configuration error: Verify --root points to the intended repository root and that the tree contains auditable files.`;
|
|
122
122
|
}
|
|
123
123
|
const providerLabel = providerName ?? LOCAL_SUBPROCESS_PROVIDER_NAME;
|
|
124
124
|
return `Provider: ${providerLabel}. For automatic LLM review, configure an interactive provider in ${sessionConfigPath}.`;
|