@clear-capabilities/agentic-security-scanner 0.147.0 → 0.147.5

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 (39) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/dist/1122.index.js +79 -2
  3. package/dist/3180.index.js +73 -1
  4. package/dist/5051.index.js +77 -6
  5. package/dist/frontend/index.html +21 -0
  6. package/dist/frontend/src/app.js +176 -0
  7. package/dist/frontend/src/components/evidence-inspector.js +141 -0
  8. package/dist/frontend/src/components/filter-rail.js +119 -0
  9. package/dist/frontend/src/components/query-bar.js +126 -0
  10. package/dist/frontend/src/data/flagship-graph.js +1460 -0
  11. package/dist/frontend/src/export-entry.js +36 -0
  12. package/dist/frontend/src/lib/api-client.js +92 -0
  13. package/dist/frontend/src/lib/contrast.js +34 -0
  14. package/dist/frontend/src/lib/dom.js +24 -0
  15. package/dist/frontend/src/lib/escape-html.js +16 -0
  16. package/dist/frontend/src/lib/flow-path.js +40 -0
  17. package/dist/frontend/src/lib/focus-controls.js +149 -0
  18. package/dist/frontend/src/lib/protection-visual.js +46 -0
  19. package/dist/frontend/src/lib/query-language.js +240 -0
  20. package/dist/frontend/src/lib/row-filters.js +43 -0
  21. package/dist/frontend/src/lib/state.js +84 -0
  22. package/dist/frontend/src/main.js +83 -0
  23. package/dist/frontend/src/shell.js +184 -0
  24. package/dist/frontend/src/views/architecture-view.js +798 -0
  25. package/dist/frontend/src/views/inventory-view.js +292 -0
  26. package/dist/frontend/src/views/privacy-view.js +172 -0
  27. package/dist/frontend/src/views/trace-view.js +206 -0
  28. package/dist/frontend/styles/architecture-view.css +93 -0
  29. package/dist/frontend/styles/filter-rail.css +34 -0
  30. package/dist/frontend/styles/inspector.css +69 -0
  31. package/dist/frontend/styles/inventory-view.css +74 -0
  32. package/dist/frontend/styles/privacy-view.css +86 -0
  33. package/dist/frontend/styles/query-bar.css +107 -0
  34. package/dist/frontend/styles/shell.css +155 -0
  35. package/dist/frontend/styles/tokens.css +128 -0
  36. package/dist/frontend/styles/trace-view.css +95 -0
  37. package/package.json +2 -2
  38. package/src/server/static-assets.js +11 -6
  39. package/src/shared/frontend-root.js +52 -0
@@ -0,0 +1,128 @@
1
+ /*
2
+ * Data Flow Explorer design tokens (PRD §8.1). Dark is the reference theme.
3
+ * Values are copied verbatim from the PRD — do not adjust without a
4
+ * deliberate, separately-reviewed change.
5
+ */
6
+
7
+ :root {
8
+ /* Surfaces */
9
+ --surface-canvas: #061625;
10
+ --surface-panel: #0B1E2F;
11
+ --surface-elevated: #102A40;
12
+ --border-default: #28445A;
13
+
14
+ /* Text */
15
+ --text-primary: #F3F7FA;
16
+ --text-secondary: #9FB3C5;
17
+
18
+ /* Selection / focus */
19
+ --accent-selection: #3DA9FF;
20
+
21
+ /* Protection verdict status */
22
+ --status-protected: #59D17D;
23
+ --status-unprotected: #FF625C;
24
+ --status-unknown: #F5B83D;
25
+
26
+ /* Text color for content painted directly on a status token background
27
+ (e.g. the coverage banner). NOT the same as --text-primary, which is
28
+ sized against --surface-canvas/--surface-panel, not against a status
29
+ color. Verified with lib/contrast.js's contrastRatio(): #061625 on
30
+ dark --status-unknown (#F5B83D) is 10.27:1. See the light-theme block
31
+ below for the light-theme value and its own verified ratio. */
32
+ --status-banner-text: #061625;
33
+
34
+ /* AI processing context */
35
+ --context-ai: #B47AFF;
36
+
37
+ /* Data class overlays */
38
+ --class-pii: #28D1C5;
39
+ --class-phi: #A97BFF;
40
+ --class-pci: #4CA7FF;
41
+
42
+ /* Spacing (8px grid, PRD §7.7) */
43
+ --space-1: 8px;
44
+ --space-2: 16px;
45
+ --space-3: 24px;
46
+ --space-4: 32px;
47
+
48
+ /* Radius / border (PRD §8.2) */
49
+ --radius-default: 8px;
50
+ --border-width: 1px;
51
+
52
+ /* Typography (PRD §8.2) — bundled system stack; never a remote/CDN font */
53
+ --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
54
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
55
+ --font-size-view-title: 23px;
56
+ --font-size-panel-title: 15px;
57
+ --font-size-node-title: 14px;
58
+ --font-size-body: 13px;
59
+ --font-size-code: 12.5px;
60
+
61
+ /* Motion (PRD §8.2 — focus/topology change only, no ambient animation) */
62
+ --motion-duration: 200ms;
63
+ --motion-duration-reduced: 0ms;
64
+
65
+ /* Shell region sizing (PRD §7.7) */
66
+ --header-height: 56px;
67
+ --view-tabs-height: 44px;
68
+ --left-rail-width: 248px;
69
+ --left-rail-collapsed-width: 56px;
70
+ --right-inspector-width: 360px;
71
+ --context-rail-height: 72px;
72
+ }
73
+
74
+ /* Light theme — same semantic tokens, WCAG-AA-equivalent contrast (PRD §8.1/§23) */
75
+ :root[data-theme="light"] {
76
+ --surface-canvas: #FFFFFF;
77
+ --surface-panel: #F4F7FA;
78
+ --surface-elevated: #E7EEF3;
79
+ --border-default: #C7D3DC;
80
+
81
+ --text-primary: #0B1E2F;
82
+ --text-secondary: #3E5568;
83
+
84
+ --accent-selection: #1B6FC2;
85
+
86
+ /* status-protected and status-unknown darkened slightly from their
87
+ original values (#1E8A4C, #9A6B00) — Milestone 3, sub-project A11y,
88
+ found via test/tokens-contrast.test.js that both originals failed
89
+ WCAG AA (4.5:1) against --surface-panel (#F4F7FA): 4.07:1 and 4.36:1
90
+ respectively. Verified with lib/contrast.js's contrastRatio() against
91
+ BOTH backgrounds a status color plausibly renders on: --status-
92
+ protected (#1C8047) is 4.97:1 on --surface-canvas / 4.62:1 on
93
+ --surface-panel; --status-unknown (#956700) is 4.98:1 / 4.63:1. Both
94
+ now clear AA with margin (>=4.6) rather than sitting at the exact
95
+ 4.50 boundary, which is fragile against hex-rounding. --status-
96
+ unprotected was already compliant (5.26:1 / 4.89:1), unchanged. */
97
+ --status-protected: #1C8047;
98
+ --status-unprotected: #C7362D;
99
+ --status-unknown: #956700;
100
+
101
+ /* Verified with lib/contrast.js's contrastRatio(): #FFFFFF on light
102
+ --status-unknown (#956700) is 4.98:1 (>= 4.5:1 AA). The dark-block
103
+ value (#061625, --surface-canvas's own dark value) is 3.90:1 here --
104
+ below AA -- which is the bug this token pair fixes: shell.css must
105
+ never hardcode dark theme's value and expect it to also work in
106
+ light theme. */
107
+ --status-banner-text: #FFFFFF;
108
+
109
+ --context-ai: #6E38B8;
110
+
111
+ --class-pii: #0E7C73;
112
+ --class-phi: #6E44B8;
113
+ --class-pci: #1C63A8;
114
+ }
115
+
116
+ @media (prefers-reduced-motion: reduce) {
117
+ :root {
118
+ --motion-duration: var(--motion-duration-reduced);
119
+ }
120
+ }
121
+
122
+ body {
123
+ background: var(--surface-canvas);
124
+ color: var(--text-primary);
125
+ font-family: var(--font-family);
126
+ font-size: var(--font-size-body);
127
+ margin: 0;
128
+ }
@@ -0,0 +1,95 @@
1
+ .trace-view {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--space-2);
5
+ max-width: 640px;
6
+ }
7
+
8
+ .trace-step {
9
+ display: flex;
10
+ gap: var(--space-2);
11
+ padding: var(--space-2);
12
+ background: var(--surface-panel);
13
+ border: var(--border-width) solid var(--border-default);
14
+ border-radius: var(--radius-default);
15
+ }
16
+
17
+ .trace-step-number {
18
+ flex: 0 0 auto;
19
+ width: 24px;
20
+ height: 24px;
21
+ border-radius: 50%;
22
+ background: var(--surface-elevated);
23
+ color: var(--text-primary);
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ font-size: var(--font-size-code);
28
+ font-weight: 600;
29
+ }
30
+
31
+ .trace-step-body {
32
+ flex: 1 1 auto;
33
+ }
34
+
35
+ .trace-step-kind {
36
+ color: var(--text-secondary);
37
+ font-size: var(--font-size-code);
38
+ text-transform: uppercase;
39
+ }
40
+
41
+ .trace-step-mapping {
42
+ font-family: var(--font-mono);
43
+ font-size: var(--font-size-code);
44
+ color: var(--text-primary);
45
+ margin: 4px 0;
46
+ }
47
+
48
+ .trace-step-node {
49
+ color: var(--text-secondary);
50
+ font-size: var(--font-size-body);
51
+ }
52
+
53
+ .trace-step-boundary {
54
+ display: inline-block;
55
+ border: 1px solid var(--status-unprotected);
56
+ color: var(--status-unprotected);
57
+ border-radius: var(--radius-default);
58
+ padding: 1px 6px;
59
+ font-size: var(--font-size-code);
60
+ margin-top: 4px;
61
+ }
62
+
63
+ .trace-branch-group {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: var(--space-2);
67
+ padding-left: var(--space-2);
68
+ border-left: 2px solid var(--accent-selection);
69
+ }
70
+
71
+ .trace-branch-label {
72
+ color: var(--text-secondary);
73
+ font-size: var(--font-size-code);
74
+ text-transform: uppercase;
75
+ }
76
+
77
+ .trace-alternates {
78
+ margin-top: var(--space-2);
79
+ }
80
+
81
+ .trace-alternate-item {
82
+ padding: var(--space-1) 0;
83
+ cursor: pointer;
84
+ color: var(--text-secondary);
85
+ }
86
+
87
+ .trace-alternate-item:hover {
88
+ color: var(--text-primary);
89
+ }
90
+
91
+ .trace-empty {
92
+ color: var(--text-secondary);
93
+ font-style: italic;
94
+ padding: var(--space-2);
95
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.147.0",
3
+ "version": "0.147.5",
4
4
  "description": "Scanner engine for the agentic-security Claude Code plugin — SAST, SCA (function-level reachability + CISA KEV), secrets, IaC, prompt-injection, MCP/agent-tool audit, auth/authZ deep analysis, attack chains, PoC generation, business logic, toxic-combinations scoring, SBOM, pipeline integrity, compliance attestation, and more.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -56,7 +56,7 @@
56
56
  "lodash-es": "^4.18.1"
57
57
  },
58
58
  "scripts": {
59
- "build": "ncc build bin/agentic-security.js -o dist --minify -e web-tree-sitter -e tree-sitter-wasms && rm -rf dist/compliance-frameworks && mkdir -p dist/compliance-frameworks && cp src/posture/compliance-frameworks/*.json dist/compliance-frameworks/ && mv dist/index.js dist/agentic-security.mjs && rm -f dist/package.json && chmod +x dist/agentic-security.mjs && node -e \"const fs=require('fs');const p='dist/agentic-security.mjs';const c=fs.readFileSync(p,'utf8');if(!c.startsWith('#!'))fs.writeFileSync(p,'#!/usr/bin/env node\\n'+c);\" && node -e \"const fs=require('fs');const c=require('crypto');const h=c.createHash('sha256').update(fs.readFileSync('dist/agentic-security.mjs')).digest('hex');fs.writeFileSync('dist/agentic-security.mjs.sha256',h+' agentic-security.mjs\\n');\"",
59
+ "build": "ncc build bin/agentic-security.js -o dist --minify -e web-tree-sitter -e tree-sitter-wasms && rm -rf dist/compliance-frameworks && mkdir -p dist/compliance-frameworks && cp src/posture/compliance-frameworks/*.json dist/compliance-frameworks/ && node scripts/copy-frontend.mjs && mv dist/index.js dist/agentic-security.mjs && rm -f dist/package.json && chmod +x dist/agentic-security.mjs && node -e \"const fs=require('fs');const p='dist/agentic-security.mjs';const c=fs.readFileSync(p,'utf8');if(!c.startsWith('#!'))fs.writeFileSync(p,'#!/usr/bin/env node\\n'+c);\" && node -e \"const fs=require('fs');const c=require('crypto');const h=c.createHash('sha256').update(fs.readFileSync('dist/agentic-security.mjs')).digest('hex');fs.writeFileSync('dist/agentic-security.mjs.sha256',h+' agentic-security.mjs\\n');\"",
60
60
  "prepare": "node ../scripts/pre-push-gate.mjs --install-hook",
61
61
  "prepublishOnly": "npm run build && node ../scripts/sync-scanner-changelog.mjs && node ../scripts/release-check.mjs",
62
62
  "test": "node ../scripts/run-unit-tests.mjs && AGENTIC_SECURITY_CPP_DATAFLOW=1 node --test test/cpp-dataflow.test.js && node --experimental-test-module-mocks --test test/fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection-k8s-admission.test.js && node --experimental-test-module-mocks --test test/lineage-fault-injection.test.js && npm run test:python",
@@ -14,14 +14,19 @@
14
14
 
15
15
  import * as path from 'node:path';
16
16
  import { fileURLToPath } from 'node:url';
17
+ import { resolveFrontendRoot } from '../shared/frontend-root.js';
17
18
 
18
- // Located the SAME way scanner/src/mcp/server.js locates files relative to
19
- // its own module (path.dirname(fileURLToPath(import.meta.url))) the real,
20
- // existing precedent for this pattern in this codebase, not a new one.
21
- // scanner/src/server/ -> ../../../frontend, computed (not guessed) and
22
- // confirmed to resolve to the real frontend/ directory.
19
+ // Located relative to this module's own directory (path.dirname(
20
+ // fileURLToPath(import.meta.url)), the same pattern scanner/src/mcp/server.js
21
+ // uses), but via resolveFrontendRoot's search-upward strategy rather than a
22
+ // single hardcoded relative depth a fixed `../../../frontend` resolved
23
+ // correctly for this file's unbundled dev location but broke (silently, at
24
+ // runtime, for every real npx/npm user) once `npm run build` split this
25
+ // module into its own dist/ chunk, whose import.meta.url reflects dist/'s
26
+ // own shallower location. See src/shared/frontend-root.js for the full story
27
+ // and scripts/copy-frontend.mjs for the build-time copy this now finds.
23
28
  const _here = path.dirname(fileURLToPath(import.meta.url));
24
- export const FRONTEND_ROOT = path.resolve(_here, '..', '..', '..', 'frontend');
29
+ export const FRONTEND_ROOT = resolveFrontendRoot(_here);
25
30
 
26
31
  export const CONTENT_TYPE_MAP = Object.freeze({
27
32
  '.html': 'text/html; charset=utf-8',
@@ -0,0 +1,52 @@
1
+ // frontend-root.js — locates the Data Flow Explorer's `frontend/` assets
2
+ // (index.html/src/styles) regardless of whether this code is running:
3
+ // - unbundled, straight out of scanner/src/ or scanner/scripts/ (dev/test —
4
+ // frontend/ is a monorepo sibling of scanner/, 2-3 levels up), or
5
+ // - bundled by `npm run build` (ncc splits a dynamic import() into its own
6
+ // chunk file physically written into scanner/dist/, so import.meta.url
7
+ // inside that chunk reflects dist/'s own location, not the original
8
+ // source file's — a fixed relative-levels-up path that was correct for
9
+ // one depth breaks silently at the other), or
10
+ // - installed from the published npm package, where the build copies
11
+ // frontend/'s servable files into scanner/dist/frontend/ (a sibling of
12
+ // the chunk file itself, i.e. 0 levels up) — see scripts/copy-frontend.mjs.
13
+ //
14
+ // Rather than hardcode one of those depths (the bug this file fixes: every
15
+ // consumer used to hardcode the dev-only depth), search upward from the
16
+ // caller's own directory and take the first candidate that actually has a
17
+ // frontend/index.html on disk. Never guessed silently past that — a caller
18
+ // with no match anywhere gets a clear, actionable error instead of a
19
+ // downstream ENOENT/404 with no indication why.
20
+
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+
24
+ const MAX_LEVELS_UP = 4;
25
+
26
+ /**
27
+ * @param {string} startDir - `path.dirname(fileURLToPath(import.meta.url))`
28
+ * of the CALLING module (not this file) — each caller's own bundled/
29
+ * unbundled location determines which candidate depth resolves.
30
+ * @returns {string} absolute path to a real `frontend/` directory containing
31
+ * `index.html`.
32
+ * @throws if no candidate directory up to MAX_LEVELS_UP contains one.
33
+ */
34
+ export function resolveFrontendRoot(startDir) {
35
+ const tried = [];
36
+ for (let up = 0; up <= MAX_LEVELS_UP; up++) {
37
+ const candidate = path.resolve(startDir, ...Array(up).fill('..'), 'frontend');
38
+ tried.push(candidate);
39
+ // Every caller invokes resolveFrontendRoot() once, at module-load time,
40
+ // to compute a top-level FRONTEND_ROOT const (see static-assets.js /
41
+ // generate-html-report.mjs) — never per-request inside the explore
42
+ // server's request handler. At most MAX_LEVELS_UP+1 (5) sync stat calls
43
+ // at process startup is not a request-path DoS surface.
44
+ if (fs.existsSync(path.join(candidate, 'index.html'))) return candidate; // agentic-security-ignore: dos-sync-io
45
+ }
46
+ throw new Error(
47
+ `resolveFrontendRoot: no frontend/index.html found searching up from ${startDir}. ` +
48
+ `Tried: ${tried.join(', ')}. If you're running from a source checkout, run \`npm run build\` ` +
49
+ `first (it copies frontend/ into scanner/dist/frontend/); if you're running the published ` +
50
+ `package, this indicates a packaging defect — report it.`
51
+ );
52
+ }