argusqa-os 9.6.6 → 9.7.4

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.
@@ -18,6 +18,7 @@
18
18
  * • HTTP resource on HTTPS page (D6.9) — skips loopback; only fires on real HTTPS origins
19
19
  */
20
20
 
21
+ import { execFile } from 'child_process';
21
22
  import { thresholds } from '../config/targets.js';
22
23
  import { childLogger } from './logger.js';
23
24
 
@@ -111,7 +112,27 @@ export const SECURITY_ANALYSIS_SCRIPT = `async () => {
111
112
  }
112
113
  } catch (e) {}
113
114
 
114
- return JSON.stringify({ storageTokenKeys: storageTokenKeys, evalUsage: evalUsage, jsCookies: jsCookies, hasCSP: hasCSP, hasXFrame: hasXFrame, unsandboxedIframes: unsandboxedIframes, unsafeBlankLinks: unsafeBlankLinks });
115
+ // 7. SRI check external scripts and stylesheets without integrity attribute
116
+ var sriViolations = [];
117
+ try {
118
+ var pageOrigin = location.origin;
119
+ var extScripts = Array.prototype.slice.call(document.querySelectorAll('script[src]:not([integrity])'));
120
+ for (var sri_i = 0; sri_i < extScripts.length && sri_i < 20; sri_i++) {
121
+ var scriptSrc = extScripts[sri_i].src || '';
122
+ if (scriptSrc && !scriptSrc.startsWith(pageOrigin) && !scriptSrc.startsWith('/') && !scriptSrc.startsWith('blob:') && !scriptSrc.startsWith('data:')) {
123
+ sriViolations.push({ tag: 'script', src: scriptSrc.slice(0, 200) });
124
+ }
125
+ }
126
+ var extLinks = Array.prototype.slice.call(document.querySelectorAll('link[rel="stylesheet"][href]:not([integrity])'));
127
+ for (var sri_j = 0; sri_j < extLinks.length && sri_j < 20; sri_j++) {
128
+ var linkHref = extLinks[sri_j].href || '';
129
+ if (linkHref && !linkHref.startsWith(pageOrigin) && !linkHref.startsWith('/') && !linkHref.startsWith('blob:') && !linkHref.startsWith('data:')) {
130
+ sriViolations.push({ tag: 'link', src: linkHref.slice(0, 200) });
131
+ }
132
+ }
133
+ } catch (e) {}
134
+
135
+ return JSON.stringify({ storageTokenKeys: storageTokenKeys, evalUsage: evalUsage, jsCookies: jsCookies, hasCSP: hasCSP, hasXFrame: hasXFrame, unsandboxedIframes: unsandboxedIframes, unsafeBlankLinks: unsafeBlankLinks, sriViolations: sriViolations });
115
136
  }`;
116
137
 
117
138
  /**
@@ -219,6 +240,20 @@ export function parseSecurityAnalysisResult(rawResult, url) {
219
240
  });
220
241
  }
221
242
 
243
+ // SRI violations — external scripts/stylesheets without integrity attribute
244
+ if (Array.isArray(data.sriViolations) && data.sriViolations.length > 0) {
245
+ for (const v of data.sriViolations) {
246
+ bugs.push({
247
+ type: 'security_missing_sri',
248
+ tag: v.tag,
249
+ src: v.src,
250
+ message: `External <${v.tag}> without integrity attribute: "${String(v.src).slice(0, 200)}" — add integrity="sha384-..." to prevent supply-chain attacks`,
251
+ severity: 'warning',
252
+ url,
253
+ });
254
+ }
255
+ }
256
+
222
257
  return bugs;
223
258
  }
224
259
 
@@ -300,3 +335,99 @@ export function analyzeSecurityNetwork(networkReqs, url) {
300
335
  }
301
336
  return bugs;
302
337
  }
338
+
339
+ /**
340
+ * Detect source map files being served in production.
341
+ * Source maps expose original unminified source code to anyone with DevTools open.
342
+ *
343
+ * @param {object[]} networkReqs - Network request entries ({ url })
344
+ * @param {string} url - Page URL for context
345
+ * @returns {object[]}
346
+ */
347
+ export function checkSourceMapExposure(networkReqs, url) {
348
+ const bugs = [];
349
+ for (const req of (Array.isArray(networkReqs) ? networkReqs : [])) {
350
+ const reqUrl = req.url ?? req.requestUrl ?? '';
351
+ if (!reqUrl) continue;
352
+ if (/\.(js|css)\.map(\?|$)/i.test(reqUrl) || /\/[^/]+\.map(\?|$)/.test(reqUrl)) {
353
+ bugs.push({
354
+ type: 'security_sourcemap_exposed',
355
+ requestUrl: reqUrl,
356
+ message: `Source map publicly accessible: "${reqUrl.slice(0, 200)}" — remove or restrict .map files in production to protect original source code`,
357
+ severity: 'warning',
358
+ url,
359
+ });
360
+ }
361
+ }
362
+ return bugs;
363
+ }
364
+
365
+ /**
366
+ * Detect open redirect parameters in network request URLs.
367
+ * Open redirects allow attackers to craft phishing URLs that appear to come from
368
+ * the legitimate domain.
369
+ *
370
+ * @param {object[]} networkReqs - Network request entries ({ url })
371
+ * @param {string} url - Page URL for context
372
+ * @returns {object[]}
373
+ */
374
+ export function checkOpenRedirects(networkReqs, url) {
375
+ // 'to', 'target', 'url' excluded — too common in non-redirect contexts (CDN proxies, nav params).
376
+ const redirectParams = /[?&](redirect|return|next|dest|destination|goto|redir|forward)=/i;
377
+ const bugs = [];
378
+ for (const req of (Array.isArray(networkReqs) ? networkReqs : [])) {
379
+ const reqUrl = req.url ?? req.requestUrl ?? '';
380
+ if (!reqUrl || !redirectParams.test(reqUrl)) continue;
381
+ bugs.push({
382
+ type: 'security_open_redirect',
383
+ requestUrl: reqUrl,
384
+ message: `Potential open redirect parameter in URL: "${reqUrl.slice(0, 200)}" — validate redirect targets server-side against an allowlist`,
385
+ severity: 'warning',
386
+ url,
387
+ });
388
+ }
389
+ return bugs;
390
+ }
391
+
392
+ /**
393
+ * Run `npm audit --json` in the given project directory and convert CVEs to findings.
394
+ * Skips silently if projectDir is falsy, npm is not available, or the project has
395
+ * no package.json (not a Node project).
396
+ *
397
+ * @param {string|null} projectDir - Absolute path to the project root
398
+ * @returns {Promise<object[]>}
399
+ */
400
+ export async function auditNpmDependencies(projectDir) {
401
+ if (!projectDir) return [];
402
+
403
+ return new Promise(resolve => {
404
+ // shell: true resolves npm.cmd on Windows; harmless on macOS/Linux.
405
+ execFile('npm', ['audit', '--json'], { cwd: projectDir, maxBuffer: 4 * 1024 * 1024, shell: true }, (err, stdout) => {
406
+ // npm audit exits non-zero when vulnerabilities exist — we still want stdout.
407
+ if (!stdout) return resolve([]);
408
+ let report;
409
+ try { report = JSON.parse(stdout); } catch { return resolve([]); }
410
+
411
+ const bugs = [];
412
+ const vulns = report?.vulnerabilities ?? report?.advisories ?? {};
413
+
414
+ for (const [name, info] of Object.entries(vulns)) {
415
+ const sev = String(info.severity ?? 'moderate').toLowerCase();
416
+ const via = Array.isArray(info.via)
417
+ ? info.via.filter(v => typeof v === 'string').join(', ')
418
+ : '';
419
+ bugs.push({
420
+ type: 'security_npm_vulnerability',
421
+ package: name,
422
+ severity: sev === 'critical' || sev === 'high' ? 'critical' : 'warning',
423
+ message: `npm vulnerability in "${name}"${via ? ` via ${via}` : ''} (${sev}) — run \`npm audit fix\` to resolve`,
424
+ via,
425
+ });
426
+ }
427
+
428
+ // Deduplicate by package name (advisories-style reports can have duplicates).
429
+ const seen = new Set();
430
+ resolve(bugs.filter(b => { if (seen.has(b.package)) return false; seen.add(b.package); return true; }));
431
+ });
432
+ });
433
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ARGUS Theme Analyzer (Sprint 1 — A7: Theme & Dark Mode)
2
+ * ARGUS Theme Analyzer (A7: Theme & Dark Mode)
3
3
  *
4
4
  * Detects dark mode support gaps and theme consistency issues by:
5
5
  * 1. Scanning all stylesheets for @media (prefers-color-scheme: dark) rules
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ARGUS Visual Regression Analyzer (Sprint 3 — A8)
2
+ * ARGUS Visual Regression Analyzer (A8)
3
3
  *
4
4
  * Per-route visual regression detection via screenshot baseline comparison.
5
5
  * Takes a PNG screenshot, compares it pixel-by-pixel against a stored baseline,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ARGUS Web Vitals Analyzer (Sprint 9 — Advanced Performance Metrics)
2
+ * ARGUS Web Vitals Analyzer (Advanced Performance Metrics)
3
3
  *
4
4
  * Captures Core Web Vitals and performance metrics directly via the browser
5
5
  * Performance API. Unlike Lighthouse, this works in headless Chrome — metrics