@owasp-aghast/aghast 0.8.3 → 0.9.0-rc.1

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 (113) hide show
  1. package/config/pricing.json +39 -9
  2. package/dist/build-config.d.ts.map +1 -1
  3. package/dist/build-config.js +101 -2
  4. package/dist/build-config.js.map +1 -1
  5. package/dist/check-library.d.ts +25 -0
  6. package/dist/check-library.d.ts.map +1 -1
  7. package/dist/check-library.js +172 -1
  8. package/dist/check-library.js.map +1 -1
  9. package/dist/ci-metadata.d.ts +42 -0
  10. package/dist/ci-metadata.d.ts.map +1 -0
  11. package/dist/ci-metadata.js +131 -0
  12. package/dist/ci-metadata.js.map +1 -0
  13. package/dist/claude-code-provider.d.ts +7 -0
  14. package/dist/claude-code-provider.d.ts.map +1 -1
  15. package/dist/claude-code-provider.js +65 -1
  16. package/dist/claude-code-provider.js.map +1 -1
  17. package/dist/cli.js +4 -1
  18. package/dist/cli.js.map +1 -1
  19. package/dist/concurrency.d.ts +18 -0
  20. package/dist/concurrency.d.ts.map +1 -0
  21. package/dist/concurrency.js +39 -0
  22. package/dist/concurrency.js.map +1 -0
  23. package/dist/cost-tracker.d.ts +45 -0
  24. package/dist/cost-tracker.d.ts.map +1 -0
  25. package/dist/cost-tracker.js +56 -0
  26. package/dist/cost-tracker.js.map +1 -0
  27. package/dist/discoveries/glob-discovery.d.ts +21 -0
  28. package/dist/discoveries/glob-discovery.d.ts.map +1 -0
  29. package/dist/discoveries/glob-discovery.js +215 -0
  30. package/dist/discoveries/glob-discovery.js.map +1 -0
  31. package/dist/discoveries/script-discovery.d.ts +117 -0
  32. package/dist/discoveries/script-discovery.d.ts.map +1 -0
  33. package/dist/discoveries/script-discovery.js +593 -0
  34. package/dist/discoveries/script-discovery.js.map +1 -0
  35. package/dist/docs-url.d.ts +14 -0
  36. package/dist/docs-url.d.ts.map +1 -0
  37. package/dist/docs-url.js +17 -0
  38. package/dist/docs-url.js.map +1 -0
  39. package/dist/error-codes.d.ts +12 -1
  40. package/dist/error-codes.d.ts.map +1 -1
  41. package/dist/error-codes.js +16 -2
  42. package/dist/error-codes.js.map +1 -1
  43. package/dist/formatters/csv-formatter.d.ts +45 -0
  44. package/dist/formatters/csv-formatter.d.ts.map +1 -0
  45. package/dist/formatters/csv-formatter.js +176 -0
  46. package/dist/formatters/csv-formatter.js.map +1 -0
  47. package/dist/formatters/html-formatter.d.ts +39 -0
  48. package/dist/formatters/html-formatter.d.ts.map +1 -0
  49. package/dist/formatters/html-formatter.js +432 -0
  50. package/dist/formatters/html-formatter.js.map +1 -0
  51. package/dist/formatters/index.d.ts.map +1 -1
  52. package/dist/formatters/index.js +6 -0
  53. package/dist/formatters/index.js.map +1 -1
  54. package/dist/formatters/markdown-formatter.d.ts +61 -0
  55. package/dist/formatters/markdown-formatter.d.ts.map +1 -0
  56. package/dist/formatters/markdown-formatter.js +557 -0
  57. package/dist/formatters/markdown-formatter.js.map +1 -0
  58. package/dist/formatters/sarif-formatter.d.ts +7 -0
  59. package/dist/formatters/sarif-formatter.d.ts.map +1 -1
  60. package/dist/formatters/sarif-formatter.js +80 -0
  61. package/dist/formatters/sarif-formatter.js.map +1 -1
  62. package/dist/formatters/types.d.ts +24 -0
  63. package/dist/formatters/types.d.ts.map +1 -1
  64. package/dist/index.d.ts +4 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +365 -10
  67. package/dist/index.js.map +1 -1
  68. package/dist/issue-file-writer.d.ts +21 -0
  69. package/dist/issue-file-writer.d.ts.map +1 -0
  70. package/dist/issue-file-writer.js +307 -0
  71. package/dist/issue-file-writer.js.map +1 -0
  72. package/dist/judge.d.ts +62 -0
  73. package/dist/judge.d.ts.map +1 -0
  74. package/dist/judge.js +305 -0
  75. package/dist/judge.js.map +1 -0
  76. package/dist/mock-agent-provider.d.ts +3 -0
  77. package/dist/mock-agent-provider.d.ts.map +1 -1
  78. package/dist/mock-agent-provider.js +13 -0
  79. package/dist/mock-agent-provider.js.map +1 -1
  80. package/dist/new-check.d.ts.map +1 -1
  81. package/dist/new-check.js +334 -22
  82. package/dist/new-check.js.map +1 -1
  83. package/dist/opencode-provider.d.ts.map +1 -1
  84. package/dist/opencode-provider.js +33 -0
  85. package/dist/opencode-provider.js.map +1 -1
  86. package/dist/repo-scan.d.ts +80 -0
  87. package/dist/repo-scan.d.ts.map +1 -0
  88. package/dist/repo-scan.js +250 -0
  89. package/dist/repo-scan.js.map +1 -0
  90. package/dist/result-handlers/pr-comment-handler.d.ts +136 -0
  91. package/dist/result-handlers/pr-comment-handler.d.ts.map +1 -0
  92. package/dist/result-handlers/pr-comment-handler.js +381 -0
  93. package/dist/result-handlers/pr-comment-handler.js.map +1 -0
  94. package/dist/retry.d.ts +128 -0
  95. package/dist/retry.d.ts.map +1 -0
  96. package/dist/retry.js +261 -0
  97. package/dist/retry.js.map +1 -0
  98. package/dist/runtime-config.d.ts.map +1 -1
  99. package/dist/runtime-config.js +62 -0
  100. package/dist/runtime-config.js.map +1 -1
  101. package/dist/scan-runner.d.ts +31 -22
  102. package/dist/scan-runner.d.ts.map +1 -1
  103. package/dist/scan-runner.js +145 -100
  104. package/dist/scan-runner.js.map +1 -1
  105. package/dist/semgrep-runner.js +6 -4
  106. package/dist/semgrep-runner.js.map +1 -1
  107. package/dist/stats.d.ts.map +1 -1
  108. package/dist/stats.js +4 -1
  109. package/dist/stats.js.map +1 -1
  110. package/dist/types.d.ts +149 -2
  111. package/dist/types.d.ts.map +1 -1
  112. package/dist/types.js.map +1 -1
  113. package/package.json +1 -1
@@ -0,0 +1,593 @@
1
+ /**
2
+ * Script-based target discovery.
3
+ *
4
+ * Runs a user-provided script (`node` or `bash`) in the target repository
5
+ * and parses its stdout to produce discovery targets. Useful when target
6
+ * discovery is too custom for Semgrep / SARIF / OpenAnt — e.g. parsing an
7
+ * OpenAPI spec, querying a build manifest, walking a database schema, etc.
8
+ *
9
+ * ─── Trust model ─────────────────────────────────────────────────────────────
10
+ *
11
+ * Scripts run with the full privileges of the aghast process. They are not
12
+ * sandboxed. Treat any script referenced by a check definition as trusted
13
+ * code — only enable script-discovery checks whose script you (or your team)
14
+ * have read and audited.
15
+ *
16
+ * Defensive measures applied here:
17
+ * - Script path must resolve INSIDE the configured check folder or repo
18
+ * (no `..` escape, no absolute paths to /etc/shadow, etc.).
19
+ * - Symlink-aware: both the script path and `cwd` are realpath'd and
20
+ * re-checked against the repo root, so a symlink inside the check folder
21
+ * pointing outside the repo cannot be used to execute external code.
22
+ * - Spawned with `shell: false` and array args — no shell interpolation, no
23
+ * command injection from check-definition fields.
24
+ * - Spawned with a curated environment: only a small allow-list of neutral
25
+ * env vars (PATH, HOME, locale, temp dirs, Windows essentials) is forwarded.
26
+ * As defence-in-depth, any var matching a known-secret pattern (`*_KEY`,
27
+ * `*_TOKEN`, `*_SECRET`, `*_PASSWORD`, or `ANTHROPIC_*` / `OPENAI_*` /
28
+ * `GITHUB_*` / `AGHAST_*` / `AWS_*` / `GCP_*` / `AZURE_*` prefixes) is
29
+ * also explicitly dropped. Allow-list matching is case-insensitive so
30
+ * the Windows-style `Path` / `SystemRoot` casing works the same as POSIX
31
+ * `PATH`.
32
+ * - cwd, when supplied, must resolve (and realpath) inside the repo root.
33
+ * - Hard timeout (default 30s, configurable per check).
34
+ * - Stdout is bounded (8 MiB) to prevent memory exhaustion from runaway scripts.
35
+ * - Both the line-format and JSON-format output paths cap the number of
36
+ * parsed targets (MAX_LINE_TARGETS / MAX_JSON_TARGETS) to bound memory
37
+ * even if a script floods within the stdout cap.
38
+ * - Output is parsed as data (lines / JSON) — never `eval`-ed.
39
+ * - File paths returned by the script are validated to be relative & inside
40
+ * the repo (no `..` escape, no absolute paths) before they are forwarded
41
+ * to the AI prompt or snippet extractor.
42
+ * - On `bash` scripts, Windows is unsupported (no system bash) and a clear
43
+ * error is raised rather than silently falling back.
44
+ */
45
+ import { spawn } from 'node:child_process';
46
+ import { readFile, realpath } from 'node:fs/promises';
47
+ import { resolve, relative, isAbsolute, normalize } from 'node:path';
48
+ import { logDebug, logProgress, logWarn } from '../logging.js';
49
+ import { ERROR_CODES, formatError } from '../error-codes.js';
50
+ import { DEFAULT_GENERIC_PROMPT } from '../defaults.js';
51
+ const TAG = 'script-discovery';
52
+ /** Default timeout: 30s. Overridable per check via `checkTarget.timeoutMs`. */
53
+ const DEFAULT_TIMEOUT_MS = 30_000;
54
+ /** Cap on total stdout we will buffer (8 MiB). */
55
+ const MAX_STDOUT_BYTES = 8 * 1024 * 1024;
56
+ /** Cap on total stderr we will buffer (1 MiB) — only used for diagnostics. */
57
+ const MAX_STDERR_BYTES = 1 * 1024 * 1024;
58
+ /** Maximum number of lines we will keep when parsing `lines` format. */
59
+ const MAX_LINE_TARGETS = 100_000;
60
+ /** Maximum number of items we will keep when parsing `json-array` / `json-object` format. */
61
+ const MAX_JSON_TARGETS = 100_000;
62
+ /**
63
+ * Build a curated environment for the spawned discovery script.
64
+ *
65
+ * Discovery scripts need to walk the repo, possibly run small helper tools,
66
+ * but they never need API credentials. We start from a minimal allow-list of
67
+ * "neutral" env vars (PATH/locale/temp dirs) and then ALSO drop any var whose
68
+ * name matches a known-secret pattern even if it slipped through. Both layers
69
+ * are defence-in-depth.
70
+ *
71
+ * Exported for testing.
72
+ */
73
+ export function buildScriptEnv(parent = process.env) {
74
+ // Allow-list: variables a normal CLI tool needs to function.
75
+ // Case-insensitive: on Windows `process.env` enumerates keys with their
76
+ // original casing (e.g. `Path`, `SystemRoot`, `ProgramFiles`), so a strict
77
+ // case-sensitive `Set.has` would drop those vars and break script
78
+ // discovery on Windows. We normalize to upper-case for membership checks.
79
+ const ALLOW_LIST_UPPER = new Set([
80
+ 'PATH',
81
+ 'HOME',
82
+ 'USER',
83
+ 'USERNAME',
84
+ 'LANG',
85
+ 'LC_ALL',
86
+ 'LC_CTYPE',
87
+ 'LC_MESSAGES',
88
+ 'LC_NUMERIC',
89
+ 'LC_TIME',
90
+ 'TZ',
91
+ 'TMPDIR',
92
+ 'TMP',
93
+ 'TEMP',
94
+ // Windows essentials (uppercased — case-insensitive match below)
95
+ 'SYSTEMROOT',
96
+ 'SYSTEMDRIVE',
97
+ 'COMSPEC',
98
+ 'PATHEXT',
99
+ 'WINDIR',
100
+ 'APPDATA',
101
+ 'LOCALAPPDATA',
102
+ 'PROGRAMDATA',
103
+ 'PROGRAMFILES',
104
+ 'PROGRAMFILES(X86)',
105
+ 'PUBLIC',
106
+ 'USERPROFILE',
107
+ ].map((s) => s.toUpperCase()));
108
+ // Block-list patterns: never forward, even if they accidentally appear in
109
+ // the allow-list. The `i` flag makes them case-insensitive too.
110
+ const BLOCK_PATTERNS = [
111
+ /(?:^|_)KEY$/i,
112
+ /(?:^|_)TOKEN$/i,
113
+ /(?:^|_)SECRET$/i,
114
+ /(?:^|_)PASSWORD$/i,
115
+ /(?:^|_)PASSWD$/i,
116
+ /^ANTHROPIC_/i,
117
+ /^OPENAI_/i,
118
+ /^GITHUB_/i,
119
+ /^AGHAST_/i, // aghast-internal config should not leak to discovery scripts
120
+ /^AWS_/i,
121
+ /^GCP_/i,
122
+ /^AZURE_/i,
123
+ /^NPM_TOKEN/i,
124
+ ];
125
+ const out = {};
126
+ for (const [k, v] of Object.entries(parent)) {
127
+ if (v === undefined)
128
+ continue;
129
+ if (!ALLOW_LIST_UPPER.has(k.toUpperCase()))
130
+ continue;
131
+ if (BLOCK_PATTERNS.some((re) => re.test(k)))
132
+ continue;
133
+ out[k] = v;
134
+ }
135
+ return out;
136
+ }
137
+ /**
138
+ * True when `candidateAbs` lives inside `rootAbs`. Both must be absolute.
139
+ *
140
+ * Two ways to be outside, and both matter:
141
+ * - a `..` prefix, the ordinary case;
142
+ * - an absolute result, which is what `relative()` returns on Windows when
143
+ * the two paths are on different drives (`C:\repo` vs `D:\checks`).
144
+ *
145
+ * Exported so the rule can be unit-tested against Windows-style paths on any
146
+ * platform. It exists as a single function because it previously did not: two
147
+ * call sites implemented the rule separately, one omitted the absolute-path
148
+ * case, and cross-drive layouts were rejected as a result.
149
+ *
150
+ * Pass a `pathApi` of `path.win32` or `path.posix` to test either flavour.
151
+ */
152
+ export function isInsideRoot(rootAbs, candidateAbs, pathApi = { relative, isAbsolute }) {
153
+ const rel = pathApi.relative(rootAbs, candidateAbs);
154
+ return !rel.startsWith('..') && !pathApi.isAbsolute(rel);
155
+ }
156
+ /**
157
+ * Verify that `candidateAbs` (an already-resolved absolute path) lives inside
158
+ * `rootAbs` (also absolute). Throws via `formatError(E2004, ...)` if the
159
+ * candidate escapes the root. Returns void.
160
+ *
161
+ * Used both for the textual `resolve` check and for the post-`realpath`
162
+ * symlink-aware re-check.
163
+ */
164
+ function assertInsideRoot(rootAbs, candidateAbs, label) {
165
+ if (!isInsideRoot(rootAbs, candidateAbs)) {
166
+ throw new Error(formatError(ERROR_CODES.E2004, `${label} "${candidateAbs}" resolves outside of "${rootAbs}" — refusing to run`));
167
+ }
168
+ }
169
+ /**
170
+ * Symlink-aware path validation. Resolves the realpath of `target` and verifies
171
+ * the realpath is still inside `rootAbs`. If the target does not yet exist
172
+ * (e.g. cwd that the script will create), falls back to the textual path.
173
+ */
174
+ async function assertRealpathInsideRoot(rootAbs, target, label) {
175
+ let realTarget;
176
+ try {
177
+ realTarget = await realpath(target);
178
+ }
179
+ catch {
180
+ // Path may not exist yet — textual check (already performed elsewhere) suffices.
181
+ return;
182
+ }
183
+ // realpath of root may itself differ if the repo is reached via a symlink;
184
+ // resolve both ends to be consistent.
185
+ let realRoot;
186
+ try {
187
+ realRoot = await realpath(rootAbs);
188
+ }
189
+ catch {
190
+ realRoot = rootAbs;
191
+ }
192
+ assertInsideRoot(realRoot, realTarget, `${label} (after symlink resolution)`);
193
+ }
194
+ /**
195
+ * Resolve a path that should live inside `root`. Throws if the resolved
196
+ * absolute path escapes the root directory (path traversal protection).
197
+ *
198
+ * Both `..` segments and absolute-path inputs are handled by re-resolving
199
+ * against `root` and then verifying that `relative(root, resolved)` does
200
+ * not start with `..`.
201
+ *
202
+ * NOTE: This is a textual check only — it does NOT follow symlinks. Callers
203
+ * that care about symlink escape (e.g. when about to spawn the file as code)
204
+ * should additionally call `assertRealpathInsideRoot`.
205
+ */
206
+ function resolveInside(root, candidate, label) {
207
+ if (typeof candidate !== 'string' || candidate.length === 0) {
208
+ throw new Error(formatError(ERROR_CODES.E2004, `${label} must be a non-empty string`));
209
+ }
210
+ // Reject absolute paths up-front: scripts in check definitions should be
211
+ // expressed relative to the check folder / repo, never absolute.
212
+ if (isAbsolute(candidate)) {
213
+ throw new Error(formatError(ERROR_CODES.E2004, `${label} must be a relative path, got absolute: "${candidate}"`));
214
+ }
215
+ const rootAbs = resolve(root);
216
+ const resolved = resolve(rootAbs, candidate);
217
+ assertInsideRoot(rootAbs, resolved, label);
218
+ return resolved;
219
+ }
220
+ /**
221
+ * Validate a file path emitted by a script: must be relative, must not
222
+ * traverse outside the repo via `..`. Returns the normalized path.
223
+ */
224
+ function validateScriptFilePath(file, idx) {
225
+ if (typeof file !== 'string' || file.length === 0) {
226
+ throw new Error(formatError(ERROR_CODES.E7105, `Script target[${idx}].file must be a non-empty string`));
227
+ }
228
+ if (isAbsolute(file)) {
229
+ throw new Error(formatError(ERROR_CODES.E7105, `Script target[${idx}].file must be relative (got absolute: "${file}")`));
230
+ }
231
+ // normalize collapses redundant segments; if the result starts with `..`
232
+ // the path escapes the repo root.
233
+ const norm = normalize(file).replace(/\\/g, '/');
234
+ if (norm === '..' || norm.startsWith('../') || norm.startsWith('..\\')) {
235
+ throw new Error(formatError(ERROR_CODES.E7105, `Script target[${idx}].file "${file}" escapes the repository root via ".."`));
236
+ }
237
+ return norm;
238
+ }
239
+ /**
240
+ * Parse stdout into a list of discovery target inputs.
241
+ * - `lines`: one file path per non-empty, non-comment line.
242
+ * - `json-array`: a JSON array of strings (file paths) or objects.
243
+ * - `json-object`: an object with a `targets` array of strings or objects.
244
+ *
245
+ * Both `lines` and JSON formats cap parsed targets at MAX_LINE_TARGETS /
246
+ * MAX_JSON_TARGETS (100k each); the rest are dropped with a warning log.
247
+ *
248
+ * Output is treated as DATA: never eval'd, never interpolated into commands.
249
+ */
250
+ export function parseScriptOutput(raw, format) {
251
+ if (format === 'lines') {
252
+ // NOTE: the `lines` format treats blank lines and lines starting with `#`
253
+ // as comments and skips them. Documented intentionally — file paths
254
+ // beginning with `#` are unusual and unsupported.
255
+ const out = [];
256
+ let truncated = false;
257
+ for (const line of raw.split(/\r?\n/)) {
258
+ const trimmed = line.trim();
259
+ if (trimmed.length === 0)
260
+ continue;
261
+ if (trimmed.startsWith('#'))
262
+ continue;
263
+ out.push({ file: trimmed });
264
+ if (out.length >= MAX_LINE_TARGETS) {
265
+ truncated = true;
266
+ break;
267
+ }
268
+ }
269
+ if (truncated) {
270
+ logWarn(TAG, `Script output truncated at MAX_LINE_TARGETS=${MAX_LINE_TARGETS}; remaining lines ignored`);
271
+ }
272
+ return out;
273
+ }
274
+ // JSON formats
275
+ let parsed;
276
+ try {
277
+ parsed = JSON.parse(raw);
278
+ }
279
+ catch (err) {
280
+ throw new Error(formatError(ERROR_CODES.E7105, `Script output is not valid JSON for outputFormat=${format}: ${err instanceof Error ? err.message : String(err)}`), { cause: err });
281
+ }
282
+ let arr;
283
+ if (format === 'json-array') {
284
+ if (!Array.isArray(parsed)) {
285
+ throw new Error(formatError(ERROR_CODES.E7105, `Script outputFormat=json-array expects a JSON array at the root`));
286
+ }
287
+ arr = parsed;
288
+ }
289
+ else {
290
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
291
+ throw new Error(formatError(ERROR_CODES.E7105, `Script outputFormat=json-object expects a JSON object with a "targets" array`));
292
+ }
293
+ const targets = parsed.targets;
294
+ if (!Array.isArray(targets)) {
295
+ throw new Error(formatError(ERROR_CODES.E7105, `Script outputFormat=json-object expects a "targets" array; got ${typeof targets}`));
296
+ }
297
+ arr = targets;
298
+ }
299
+ const out = [];
300
+ const items = arr;
301
+ // Cap JSON-parsed item count to bound memory even if the script flooded
302
+ // structured output within the stdout cap.
303
+ const limit = Math.min(items.length, MAX_JSON_TARGETS);
304
+ if (items.length > MAX_JSON_TARGETS) {
305
+ logWarn(TAG, `Script JSON output truncated at MAX_JSON_TARGETS=${MAX_JSON_TARGETS} (script returned ${items.length} items)`);
306
+ }
307
+ for (let i = 0; i < limit; i++) {
308
+ const item = items[i];
309
+ if (typeof item === 'string') {
310
+ out.push({ file: item });
311
+ continue;
312
+ }
313
+ if (typeof item !== 'object' || item === null || Array.isArray(item)) {
314
+ throw new Error(formatError(ERROR_CODES.E7105, `Script target[${i}] must be a string or object, got ${Array.isArray(item) ? 'array' : typeof item}`));
315
+ }
316
+ const obj = item;
317
+ if (typeof obj.file !== 'string' || obj.file.length === 0) {
318
+ throw new Error(formatError(ERROR_CODES.E7105, `Script target[${i}].file must be a non-empty string`));
319
+ }
320
+ const t = { file: obj.file };
321
+ if (obj.startLine !== undefined) {
322
+ if (typeof obj.startLine !== 'number' || !Number.isFinite(obj.startLine) || obj.startLine < 0) {
323
+ throw new Error(formatError(ERROR_CODES.E7105, `Script target[${i}].startLine must be a non-negative number`));
324
+ }
325
+ t.startLine = Math.floor(obj.startLine);
326
+ }
327
+ if (obj.endLine !== undefined) {
328
+ if (typeof obj.endLine !== 'number' || !Number.isFinite(obj.endLine) || obj.endLine < 0) {
329
+ throw new Error(formatError(ERROR_CODES.E7105, `Script target[${i}].endLine must be a non-negative number`));
330
+ }
331
+ t.endLine = Math.floor(obj.endLine);
332
+ }
333
+ if (obj.message !== undefined) {
334
+ if (typeof obj.message !== 'string') {
335
+ throw new Error(formatError(ERROR_CODES.E7105, `Script target[${i}].message must be a string`));
336
+ }
337
+ t.message = obj.message;
338
+ }
339
+ if (obj.snippet !== undefined) {
340
+ if (typeof obj.snippet !== 'string') {
341
+ throw new Error(formatError(ERROR_CODES.E7105, `Script target[${i}].snippet must be a string`));
342
+ }
343
+ t.snippet = obj.snippet;
344
+ }
345
+ out.push(t);
346
+ }
347
+ return out;
348
+ }
349
+ /**
350
+ * Execute the script as a child process and capture stdout.
351
+ * - Uses `spawn` with `shell: false`: never invoke a shell, never interpolate.
352
+ * - Passes a curated env (no API keys / tokens) — see `buildScriptEnv`.
353
+ * - Bounds stdout/stderr buffers and kills the process on timeout.
354
+ */
355
+ export function runScript(opts) {
356
+ return new Promise((resolvePromise, reject) => {
357
+ let stdoutLen = 0;
358
+ let stderrLen = 0;
359
+ const stdoutChunks = [];
360
+ const stderrChunks = [];
361
+ let timedOut = false;
362
+ let stdoutTruncated = false;
363
+ const child = spawn(opts.command, opts.args, {
364
+ cwd: opts.cwd,
365
+ shell: false,
366
+ stdio: ['ignore', 'pipe', 'pipe'],
367
+ windowsHide: true,
368
+ env: opts.env ?? buildScriptEnv(),
369
+ });
370
+ const timer = setTimeout(() => {
371
+ timedOut = true;
372
+ // Kill the whole process; on POSIX SIGKILL is unmaskable. On Windows
373
+ // Node's kill() invokes TerminateProcess regardless of the signal.
374
+ try {
375
+ child.kill('SIGKILL');
376
+ }
377
+ catch {
378
+ // already exited
379
+ }
380
+ }, opts.timeoutMs);
381
+ child.stdout.on('data', (chunk) => {
382
+ if (stdoutTruncated)
383
+ return;
384
+ if (stdoutLen + chunk.length > MAX_STDOUT_BYTES) {
385
+ stdoutTruncated = true;
386
+ try {
387
+ child.kill('SIGKILL');
388
+ }
389
+ catch {
390
+ // ignore
391
+ }
392
+ return;
393
+ }
394
+ stdoutChunks.push(chunk);
395
+ stdoutLen += chunk.length;
396
+ });
397
+ child.stderr.on('data', (chunk) => {
398
+ if (stderrLen + chunk.length > MAX_STDERR_BYTES)
399
+ return;
400
+ stderrChunks.push(chunk);
401
+ stderrLen += chunk.length;
402
+ });
403
+ child.on('error', (err) => {
404
+ clearTimeout(timer);
405
+ reject(err);
406
+ });
407
+ child.on('close', (code, signal) => {
408
+ clearTimeout(timer);
409
+ if (timedOut) {
410
+ reject(new Error(formatError(ERROR_CODES.E7101, `Script discovery timed out after ${opts.timeoutMs}ms`)));
411
+ return;
412
+ }
413
+ if (stdoutTruncated) {
414
+ reject(new Error(formatError(ERROR_CODES.E7103, `Script discovery stdout exceeded ${MAX_STDOUT_BYTES} bytes — aborted`)));
415
+ return;
416
+ }
417
+ resolvePromise({
418
+ stdout: Buffer.concat(stdoutChunks).toString('utf-8'),
419
+ stderr: Buffer.concat(stderrChunks).toString('utf-8'),
420
+ exitCode: code,
421
+ signal,
422
+ });
423
+ });
424
+ });
425
+ }
426
+ /**
427
+ * Resolve the runtime command + leading args for a given scriptType.
428
+ * Returns `[command, ...leadingArgs]`. The script path is appended later.
429
+ */
430
+ function commandForScriptType(scriptType) {
431
+ if (scriptType === 'node') {
432
+ // Use the same Node binary that's running aghast — avoids surprises from
433
+ // PATH lookups picking up an unexpected node.
434
+ return [process.execPath, []];
435
+ }
436
+ if (scriptType === 'bash') {
437
+ if (process.platform === 'win32') {
438
+ throw new Error(formatError(ERROR_CODES.E7106, `scriptType="bash" is not supported on Windows; use scriptType="node" instead`));
439
+ }
440
+ // Use bare `bash` so PATH lookup works on systems where bash lives at
441
+ // /usr/bin/bash, /usr/local/bin/bash, NixOS store paths, etc. spawn() with
442
+ // shell:false still resolves bare command names through PATH.
443
+ return ['bash', []];
444
+ }
445
+ // Should be unreachable due to upstream validation.
446
+ throw new Error(formatError(ERROR_CODES.E7106, `Unknown scriptType: "${scriptType}"`));
447
+ }
448
+ export const scriptDiscovery = {
449
+ name: 'script',
450
+ defaultGenericPrompt: DEFAULT_GENERIC_PROMPT,
451
+ needsInstructions: true,
452
+ // Opted out deliberately, same reasoning as the glob discovery.
453
+ // `supportsDiffFilter` became a required field on TargetDiscovery after this
454
+ // discovery was written (#227). A discovery script can emit either whole-file
455
+ // targets or explicit line ranges, so filter behaviour would vary per script
456
+ // with nothing to validate it against. Opting out preserves the behaviour
457
+ // this discovery was built and tested with: the script decides the target
458
+ // set, and aghast analyses all of it.
459
+ supportsDiffFilter: false,
460
+ async discover(check, repoPath, _options) {
461
+ const checkTarget = check.checkTarget;
462
+ if (!checkTarget.script) {
463
+ throw new Error(formatError(ERROR_CODES.E2004, `Check "${check.id}" uses script discovery but has no "script" in its check definition`));
464
+ }
465
+ if (!checkTarget.scriptType) {
466
+ throw new Error(formatError(ERROR_CODES.E2004, `Check "${check.id}" uses script discovery but has no "scriptType" in its check definition`));
467
+ }
468
+ if (!checkTarget.outputFormat) {
469
+ throw new Error(formatError(ERROR_CODES.E2004, `Check "${check.id}" uses script discovery but has no "outputFormat" in its check definition`));
470
+ }
471
+ // Script path: resolve relative to check folder if available, else repo root.
472
+ // Then re-validate that the absolute resolution is inside the repo (so
473
+ // a check folder symlinked outside the repo can't be used to escape).
474
+ const checkBase = check.checkDir ?? repoPath;
475
+ const repoRoot = resolve(repoPath);
476
+ let scriptAbs;
477
+ try {
478
+ scriptAbs = resolveInside(checkBase, checkTarget.script, 'checkTarget.script');
479
+ }
480
+ catch (err) {
481
+ // Re-throw with check id for context
482
+ const msg = err instanceof Error ? err.message : String(err);
483
+ throw new Error(`[${check.id}] ${msg}`, { cause: err });
484
+ }
485
+ // Defence-in-depth: also enforce script lives inside the repo root, so that
486
+ // even if checkDir points outside the repo we don't run arbitrary scripts.
487
+ // Skip this check when the check folder itself lives outside the repo
488
+ // (the common case during local dev where checks-config/ is a sibling of
489
+ // the target repo) — in that case resolveInside(checkBase, ...) above
490
+ // already constrained the script to the check folder.
491
+ const checkBaseAbs = resolve(checkBase);
492
+ const checkBaseInsideRepo = isInsideRoot(repoRoot, checkBaseAbs);
493
+ if (checkBaseInsideRepo) {
494
+ // re-raise propagates the original E2004-formatted message
495
+ assertInsideRoot(repoRoot, scriptAbs, `[${check.id}] checkTarget.script`);
496
+ }
497
+ // Validate script file exists & is readable.
498
+ try {
499
+ await readFile(scriptAbs, { flag: 'r' });
500
+ }
501
+ catch (err) {
502
+ throw new Error(formatError(ERROR_CODES.E7104, `[${check.id}] script "${checkTarget.script}" not found or unreadable at "${scriptAbs}": ${err instanceof Error ? err.message : String(err)}`), { cause: err });
503
+ }
504
+ // Symlink-aware re-check: if the script (or any parent component) is a
505
+ // symlink whose realpath escapes the constraining root, refuse to run.
506
+ // We use the check folder as the constraining root when checkBase is
507
+ // outside the repo (local-dev case); otherwise we use the repo root.
508
+ const symlinkConstrainingRoot = checkBaseInsideRepo ? repoRoot : checkBaseAbs;
509
+ await assertRealpathInsideRoot(symlinkConstrainingRoot, scriptAbs, `[${check.id}] checkTarget.script`);
510
+ // Resolve cwd: must be inside repo. Defaults to repo root.
511
+ let scriptCwd = repoRoot;
512
+ if (checkTarget.cwd !== undefined) {
513
+ try {
514
+ scriptCwd = resolveInside(repoRoot, checkTarget.cwd, 'checkTarget.cwd');
515
+ }
516
+ catch (err) {
517
+ const msg = err instanceof Error ? err.message : String(err);
518
+ throw new Error(`[${check.id}] ${msg}`, { cause: err });
519
+ }
520
+ // Symlink-aware cwd check.
521
+ await assertRealpathInsideRoot(repoRoot, scriptCwd, `[${check.id}] checkTarget.cwd`);
522
+ }
523
+ const timeoutMs = typeof checkTarget.timeoutMs === 'number' && checkTarget.timeoutMs > 0
524
+ ? Math.floor(checkTarget.timeoutMs)
525
+ : DEFAULT_TIMEOUT_MS;
526
+ const [command, leadingArgs] = commandForScriptType(checkTarget.scriptType);
527
+ // No shell interpolation: array args, shell:false in spawn.
528
+ const args = [...leadingArgs, scriptAbs];
529
+ logProgress(TAG, `Running script discovery for check ${check.id}: ${checkTarget.scriptType} ${checkTarget.script}`);
530
+ logDebug(TAG, `cmd=${command} args=${JSON.stringify(args)} cwd=${scriptCwd} timeout=${timeoutMs}ms`);
531
+ let result;
532
+ try {
533
+ result = await runScript({ command, args, cwd: scriptCwd, timeoutMs });
534
+ }
535
+ catch (err) {
536
+ // Already-formatted E7xxx errors from runScript pass through cause.
537
+ throw new Error(formatError(ERROR_CODES.E7104, `[${check.id}] script discovery failed: ${err instanceof Error ? err.message : String(err)}`), { cause: err });
538
+ }
539
+ if (result.exitCode !== 0) {
540
+ const stderrPreview = result.stderr.length > 500 ? result.stderr.slice(0, 500) + '…' : result.stderr;
541
+ throw new Error(formatError(ERROR_CODES.E7102, `[${check.id}] script exited with code=${result.exitCode} signal=${result.signal ?? 'none'}; stderr: ${stderrPreview}`));
542
+ }
543
+ // Surface non-empty stderr even on success (warnings, deprecations).
544
+ if (result.stderr.trim().length > 0) {
545
+ const preview = result.stderr.length > 500 ? result.stderr.slice(0, 500) + '…' : result.stderr;
546
+ logDebug(TAG, `[${check.id}] script stderr (exit 0): ${preview}`);
547
+ }
548
+ if (result.stdout.trim().length === 0) {
549
+ logDebug(TAG, `[${check.id}] script produced empty output — 0 targets`);
550
+ return [];
551
+ }
552
+ const parsedTargets = parseScriptOutput(result.stdout, checkTarget.outputFormat);
553
+ // Apply maxTargets in two places: here (early — bounds memory) and again in
554
+ // scan-runner (canonical limit applied uniformly across discoveries).
555
+ let limited = parsedTargets;
556
+ if (typeof checkTarget.maxTargets === 'number' &&
557
+ checkTarget.maxTargets > 0 &&
558
+ limited.length > checkTarget.maxTargets) {
559
+ limited = limited.slice(0, checkTarget.maxTargets);
560
+ }
561
+ logDebug(TAG, `[${check.id}] script produced ${parsedTargets.length} targets (using ${limited.length})`);
562
+ // Convert parsed inputs to DiscoveredTarget objects.
563
+ return limited.map((t, idx) => {
564
+ // Reject absolute / `..`-traversing file paths before they reach the
565
+ // AI prompt or the snippet extractor.
566
+ const safeFile = validateScriptFilePath(t.file, idx);
567
+ // Normalize line numbers: DiscoveredTarget is documented as 1-based,
568
+ // so coerce undefined or 0 → 1.
569
+ const rawStart = typeof t.startLine === 'number' ? t.startLine : 1;
570
+ const startLine = rawStart < 1 ? 1 : rawStart;
571
+ const rawEnd = typeof t.endLine === 'number' ? t.endLine : startLine;
572
+ const endLine = rawEnd < 1 ? startLine : rawEnd;
573
+ const enrichment = `\n\nTARGET LOCATION (from script discovery):
574
+
575
+ - File: ${safeFile}
576
+ - Lines: ${startLine}-${endLine}${t.message ? `\n- Message: ${t.message}` : ''}
577
+ `;
578
+ const out = {
579
+ file: safeFile,
580
+ startLine,
581
+ endLine,
582
+ label: `[script-target ${idx + 1}/${limited.length}]`,
583
+ promptEnrichment: enrichment,
584
+ };
585
+ if (t.message !== undefined)
586
+ out.message = t.message;
587
+ if (t.snippet !== undefined)
588
+ out.snippet = t.snippet;
589
+ return out;
590
+ });
591
+ },
592
+ };
593
+ //# sourceMappingURL=script-discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"script-discovery.js","sourceRoot":"","sources":["../../src/discoveries/script-discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Canonical documentation location, surfaced in CLI --help output so users who
3
+ * install aghast via npm can discover the docs for configuration, check types
4
+ * and examples. Single source of truth — do not duplicate these literals.
5
+ */
6
+ export declare const DOCS_BASE = "https://github.com/owasp-aghast/aghast/tree/main/docs";
7
+ /**
8
+ * Build a help footer that links to a specific documentation page (e.g.
9
+ * `scanning.md`), or to the docs index when no page is given. The page names
10
+ * are asserted to exist on disk by tests/docs-links.test.ts, so a renamed or
11
+ * removed page fails CI rather than shipping a dead link.
12
+ */
13
+ export declare function docsFooter(page?: string): string;
14
+ //# sourceMappingURL=docs-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-url.d.ts","sourceRoot":"","sources":["../src/docs-url.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,SAAS,0DAA0D,CAAC;AAEjF;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAGhD"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Canonical documentation location, surfaced in CLI --help output so users who
3
+ * install aghast via npm can discover the docs for configuration, check types
4
+ * and examples. Single source of truth — do not duplicate these literals.
5
+ */
6
+ export const DOCS_BASE = 'https://github.com/owasp-aghast/aghast/tree/main/docs';
7
+ /**
8
+ * Build a help footer that links to a specific documentation page (e.g.
9
+ * `scanning.md`), or to the docs index when no page is given. The page names
10
+ * are asserted to exist on disk by tests/docs-links.test.ts, so a renamed or
11
+ * removed page fails CI rather than shipping a dead link.
12
+ */
13
+ export function docsFooter(page) {
14
+ const url = page ? `${DOCS_BASE}/${page}` : DOCS_BASE;
15
+ return `Documentation:\n ${url}`;
16
+ }
17
+ //# sourceMappingURL=docs-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-url.js","sourceRoot":"","sources":["../src/docs-url.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,uDAAuD,CAAC;AAEjF;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,OAAO,qBAAqB,GAAG,EAAE,CAAC;AACpC,CAAC"}