sentinelayer-cli 0.6.2 → 0.8.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 (280) hide show
  1. package/README.md +1009 -996
  2. package/bin/create-sentinelayer.js +5 -5
  3. package/bin/sentinelayer-cli.js +4 -4
  4. package/bin/sl.js +5 -5
  5. package/package.json +64 -63
  6. package/src/agents/ai-governance/index.js +12 -0
  7. package/src/agents/ai-governance/tools/base.js +171 -0
  8. package/src/agents/ai-governance/tools/eval-regression.js +47 -0
  9. package/src/agents/ai-governance/tools/hitl-audit.js +81 -0
  10. package/src/agents/ai-governance/tools/index.js +52 -0
  11. package/src/agents/ai-governance/tools/prompt-drift.js +42 -0
  12. package/src/agents/ai-governance/tools/provenance-check.js +69 -0
  13. package/src/agents/backend/index.js +12 -0
  14. package/src/agents/backend/tools/base.js +189 -0
  15. package/src/agents/backend/tools/circuit-breaker-check.js +123 -0
  16. package/src/agents/backend/tools/idempotency-audit.js +105 -0
  17. package/src/agents/backend/tools/index.js +87 -0
  18. package/src/agents/backend/tools/retry-audit.js +132 -0
  19. package/src/agents/backend/tools/timeout-audit.js +144 -0
  20. package/src/agents/code-quality/index.js +12 -0
  21. package/src/agents/code-quality/tools/base.js +159 -0
  22. package/src/agents/code-quality/tools/complexity-measure.js +197 -0
  23. package/src/agents/code-quality/tools/coupling-analysis.js +81 -0
  24. package/src/agents/code-quality/tools/cycle-detect.js +49 -0
  25. package/src/agents/code-quality/tools/dep-graph.js +196 -0
  26. package/src/agents/code-quality/tools/index.js +89 -0
  27. package/src/agents/data-layer/index.js +12 -0
  28. package/src/agents/data-layer/tools/base.js +181 -0
  29. package/src/agents/data-layer/tools/index-audit.js +165 -0
  30. package/src/agents/data-layer/tools/index.js +83 -0
  31. package/src/agents/data-layer/tools/migration-scan.js +135 -0
  32. package/src/agents/data-layer/tools/query-explain.js +120 -0
  33. package/src/agents/data-layer/tools/tenancy-scan.js +166 -0
  34. package/src/agents/documentation/index.js +12 -0
  35. package/src/agents/documentation/tools/api-diff.js +91 -0
  36. package/src/agents/documentation/tools/base.js +151 -0
  37. package/src/agents/documentation/tools/dead-link-check.js +58 -0
  38. package/src/agents/documentation/tools/docstring-coverage.js +78 -0
  39. package/src/agents/documentation/tools/index.js +52 -0
  40. package/src/agents/documentation/tools/readme-freshness.js +61 -0
  41. package/src/agents/envelope/fix-cycle.js +45 -0
  42. package/src/agents/envelope/index.js +31 -0
  43. package/src/agents/envelope/loop.js +150 -0
  44. package/src/agents/envelope/pulse.js +18 -0
  45. package/src/agents/envelope/stream.js +40 -0
  46. package/src/agents/infrastructure/index.js +12 -0
  47. package/src/agents/infrastructure/tools/base.js +171 -0
  48. package/src/agents/infrastructure/tools/checkov-run.js +32 -0
  49. package/src/agents/infrastructure/tools/drift-detect.js +59 -0
  50. package/src/agents/infrastructure/tools/iam-least-priv-check.js +78 -0
  51. package/src/agents/infrastructure/tools/index.js +52 -0
  52. package/src/agents/infrastructure/tools/tflint-run.js +31 -0
  53. package/src/agents/jules/config/definition.js +160 -160
  54. package/src/agents/jules/config/system-prompt.js +182 -182
  55. package/src/agents/jules/error-intake.js +51 -51
  56. package/src/agents/jules/fix-cycle.js +17 -17
  57. package/src/agents/jules/loop.js +460 -450
  58. package/src/agents/jules/pulse.js +10 -10
  59. package/src/agents/jules/stream.js +187 -186
  60. package/src/agents/jules/swarm/file-scanner.js +74 -74
  61. package/src/agents/jules/swarm/index.js +11 -11
  62. package/src/agents/jules/swarm/orchestrator.js +362 -362
  63. package/src/agents/jules/swarm/pattern-hunter.js +123 -123
  64. package/src/agents/jules/swarm/sub-agent.js +315 -309
  65. package/src/agents/jules/tools/aidenid-email.js +189 -189
  66. package/src/agents/jules/tools/auth-audit.js +1708 -1691
  67. package/src/agents/jules/tools/dispatch.js +340 -335
  68. package/src/agents/jules/tools/file-edit.js +2 -2
  69. package/src/agents/jules/tools/file-read.js +2 -2
  70. package/src/agents/jules/tools/frontend-analyze.js +570 -570
  71. package/src/agents/jules/tools/glob.js +2 -2
  72. package/src/agents/jules/tools/grep.js +2 -2
  73. package/src/agents/jules/tools/index.js +29 -29
  74. package/src/agents/jules/tools/path-guards.js +2 -2
  75. package/src/agents/jules/tools/runtime-audit.js +507 -507
  76. package/src/agents/jules/tools/shell.js +2 -2
  77. package/src/agents/jules/tools/url-policy.js +100 -100
  78. package/src/agents/mode.js +113 -0
  79. package/src/agents/observability/index.js +12 -0
  80. package/src/agents/observability/tools/alert-audit.js +39 -0
  81. package/src/agents/observability/tools/base.js +181 -0
  82. package/src/agents/observability/tools/dashboard-gap.js +42 -0
  83. package/src/agents/observability/tools/index.js +54 -0
  84. package/src/agents/observability/tools/log-schema-check.js +74 -0
  85. package/src/agents/observability/tools/span-coverage.js +74 -0
  86. package/src/agents/persona-visuals.js +102 -61
  87. package/src/agents/release/index.js +12 -0
  88. package/src/agents/release/tools/base.js +181 -0
  89. package/src/agents/release/tools/changelog-diff.js +86 -0
  90. package/src/agents/release/tools/feature-flag-audit.js +126 -0
  91. package/src/agents/release/tools/index.js +61 -0
  92. package/src/agents/release/tools/rollback-verify.js +129 -0
  93. package/src/agents/release/tools/semver-check.js +109 -0
  94. package/src/agents/reliability/index.js +12 -0
  95. package/src/agents/reliability/tools/backpressure-check.js +129 -0
  96. package/src/agents/reliability/tools/base.js +181 -0
  97. package/src/agents/reliability/tools/chaos-probe.js +109 -0
  98. package/src/agents/reliability/tools/graceful-degradation-check.js +114 -0
  99. package/src/agents/reliability/tools/health-check-audit.js +111 -0
  100. package/src/agents/reliability/tools/index.js +87 -0
  101. package/src/agents/run-persona.js +109 -0
  102. package/src/agents/security/index.js +12 -0
  103. package/src/agents/security/tools/authz-audit.js +134 -0
  104. package/src/agents/security/tools/base.js +190 -0
  105. package/src/agents/security/tools/crypto-review.js +175 -0
  106. package/src/agents/security/tools/index.js +97 -0
  107. package/src/agents/security/tools/sast-scan.js +175 -0
  108. package/src/agents/security/tools/secrets-scan.js +216 -0
  109. package/src/agents/shared-tools/dispatch-core.js +320 -315
  110. package/src/agents/shared-tools/file-edit.js +180 -180
  111. package/src/agents/shared-tools/file-read.js +100 -100
  112. package/src/agents/shared-tools/glob.js +168 -168
  113. package/src/agents/shared-tools/grep.js +228 -228
  114. package/src/agents/shared-tools/index.js +46 -46
  115. package/src/agents/shared-tools/path-guards.js +161 -161
  116. package/src/agents/shared-tools/shell.js +383 -383
  117. package/src/agents/supply-chain/index.js +12 -0
  118. package/src/agents/supply-chain/tools/attestation-check.js +42 -0
  119. package/src/agents/supply-chain/tools/base.js +151 -0
  120. package/src/agents/supply-chain/tools/index.js +52 -0
  121. package/src/agents/supply-chain/tools/lockfile-integrity.js +73 -0
  122. package/src/agents/supply-chain/tools/package-verify.js +56 -0
  123. package/src/agents/supply-chain/tools/sbom-diff.js +34 -0
  124. package/src/agents/testing/index.js +12 -0
  125. package/src/agents/testing/tools/base.js +202 -0
  126. package/src/agents/testing/tools/coverage-gap.js +144 -0
  127. package/src/agents/testing/tools/flake-detect.js +125 -0
  128. package/src/agents/testing/tools/index.js +85 -0
  129. package/src/agents/testing/tools/mutation-test.js +143 -0
  130. package/src/agents/testing/tools/snapshot-diff.js +103 -0
  131. package/src/ai/aidenid.js +1021 -1009
  132. package/src/ai/client.js +553 -553
  133. package/src/ai/domain-target-store.js +268 -268
  134. package/src/ai/identity-store.js +270 -270
  135. package/src/ai/proxy.js +137 -137
  136. package/src/ai/site-store.js +145 -145
  137. package/src/audit/agents/architecture.js +180 -180
  138. package/src/audit/agents/compliance.js +179 -179
  139. package/src/audit/agents/documentation.js +165 -165
  140. package/src/audit/agents/performance.js +145 -145
  141. package/src/audit/agents/security.js +215 -215
  142. package/src/audit/agents/testing.js +172 -172
  143. package/src/audit/orchestrator.js +557 -557
  144. package/src/audit/package.js +204 -204
  145. package/src/audit/registry.js +284 -284
  146. package/src/audit/replay.js +103 -103
  147. package/src/auth/gate.js +428 -371
  148. package/src/auth/http.js +681 -611
  149. package/src/auth/service.js +1106 -1106
  150. package/src/auth/session-store.js +813 -813
  151. package/src/cli.js +257 -252
  152. package/src/commands/ai/identity-lifecycle.js +1338 -1338
  153. package/src/commands/ai/provision-governance.js +1272 -1272
  154. package/src/commands/ai/shared.js +147 -147
  155. package/src/commands/ai.js +11 -11
  156. package/src/commands/apply.js +12 -12
  157. package/src/commands/audit.js +1171 -1166
  158. package/src/commands/auth.js +419 -419
  159. package/src/commands/chat.js +184 -191
  160. package/src/commands/config.js +184 -184
  161. package/src/commands/cost.js +311 -311
  162. package/src/commands/daemon/core.js +850 -850
  163. package/src/commands/daemon/extended.js +1048 -1048
  164. package/src/commands/daemon/shared.js +213 -213
  165. package/src/commands/daemon.js +11 -11
  166. package/src/commands/guide.js +174 -174
  167. package/src/commands/ingest.js +58 -58
  168. package/src/commands/init.js +55 -55
  169. package/src/commands/legacy-args.js +20 -10
  170. package/src/commands/mcp.js +461 -461
  171. package/src/commands/omargate.js +63 -29
  172. package/src/commands/persona.js +65 -20
  173. package/src/commands/plugin.js +260 -260
  174. package/src/commands/policy.js +132 -132
  175. package/src/commands/prompt.js +238 -238
  176. package/src/commands/review.js +704 -704
  177. package/src/commands/scan.js +865 -872
  178. package/src/commands/session.js +1238 -0
  179. package/src/commands/spec.js +771 -716
  180. package/src/commands/swarm.js +651 -651
  181. package/src/commands/telemetry.js +202 -202
  182. package/src/commands/watch.js +511 -511
  183. package/src/config/agent-dictionary.js +182 -182
  184. package/src/config/io.js +56 -56
  185. package/src/config/paths.js +18 -18
  186. package/src/config/schema.js +55 -55
  187. package/src/config/service.js +184 -184
  188. package/src/coord/events-log.js +141 -0
  189. package/src/coord/handshake.js +719 -0
  190. package/src/coord/index.js +35 -0
  191. package/src/coord/paths.js +84 -0
  192. package/src/coord/priority.js +62 -0
  193. package/src/coord/tarjan.js +157 -0
  194. package/src/cost/budget.js +235 -235
  195. package/src/cost/history.js +188 -188
  196. package/src/cost/tokenizer.js +160 -0
  197. package/src/cost/tracker.js +232 -171
  198. package/src/daemon/artifact-lineage.js +896 -534
  199. package/src/daemon/assignment-ledger.js +1083 -770
  200. package/src/daemon/ast-drift.js +496 -0
  201. package/src/daemon/ast-parser-layer.js +258 -258
  202. package/src/daemon/budget-governor.js +633 -633
  203. package/src/daemon/callgraph-overlay.js +646 -646
  204. package/src/daemon/error-worker.js +1209 -626
  205. package/src/daemon/fix-cycle.js +384 -377
  206. package/src/daemon/hybrid-mapper.js +929 -929
  207. package/src/daemon/ingest-refresh.js +79 -11
  208. package/src/daemon/jira-lifecycle.js +767 -632
  209. package/src/daemon/operator-control.js +657 -657
  210. package/src/daemon/pulse.js +327 -327
  211. package/src/daemon/reliability-lane.js +471 -471
  212. package/src/daemon/scope-engine.js +1068 -0
  213. package/src/daemon/watchdog.js +971 -971
  214. package/src/events/schema.js +190 -0
  215. package/src/guide/generator.js +316 -316
  216. package/src/ingest/engine.js +933 -918
  217. package/src/ingest/ownership.js +380 -0
  218. package/src/interactive/index.js +97 -97
  219. package/src/legacy-cli.js +3228 -2994
  220. package/src/mcp/registry.js +695 -695
  221. package/src/memory/blackboard.js +301 -301
  222. package/src/memory/retrieval.js +581 -581
  223. package/src/orchestrator/kai-chen.js +126 -0
  224. package/src/plugin/manifest.js +553 -553
  225. package/src/policy/packs.js +144 -144
  226. package/src/prompt/generator.js +136 -118
  227. package/src/review/ai-review.js +672 -679
  228. package/src/review/compliance-pack.js +389 -0
  229. package/src/review/investor-dd-config.js +54 -0
  230. package/src/review/investor-dd-file-loop.js +303 -0
  231. package/src/review/investor-dd-file-router.js +406 -0
  232. package/src/review/investor-dd-html-report.js +233 -0
  233. package/src/review/investor-dd-notification.js +120 -0
  234. package/src/review/investor-dd-orchestrator.js +405 -0
  235. package/src/review/investor-dd-persona-runner.js +275 -0
  236. package/src/review/live-validator.js +253 -0
  237. package/src/review/local-review.js +1351 -1305
  238. package/src/review/omargate-interactive.js +68 -68
  239. package/src/review/omargate-orchestrator.js +492 -300
  240. package/src/review/persona-prompts.js +484 -296
  241. package/src/review/reconciliation-rules.js +329 -0
  242. package/src/review/replay.js +235 -235
  243. package/src/review/report.js +664 -664
  244. package/src/review/reproducibility-chain.js +136 -0
  245. package/src/review/scan-modes.js +147 -42
  246. package/src/review/spec-binding.js +487 -487
  247. package/src/scaffold/generator.js +67 -67
  248. package/src/scaffold/templates.js +150 -150
  249. package/src/scan/generator.js +418 -418
  250. package/src/scan/gh-secrets.js +107 -107
  251. package/src/session/agent-registry.js +359 -0
  252. package/src/session/analytics.js +479 -0
  253. package/src/session/daemon.js +1396 -0
  254. package/src/session/file-locks.js +666 -0
  255. package/src/session/paths.js +37 -0
  256. package/src/session/recap.js +567 -0
  257. package/src/session/redact.js +82 -0
  258. package/src/session/runtime-bridge.js +762 -0
  259. package/src/session/scoring.js +406 -0
  260. package/src/session/setup-guides.js +304 -0
  261. package/src/session/store.js +704 -0
  262. package/src/session/stream.js +333 -0
  263. package/src/session/sync.js +753 -0
  264. package/src/session/tasks.js +1054 -0
  265. package/src/session/templates.js +188 -0
  266. package/src/spec/generator.js +619 -519
  267. package/src/spec/regenerate.js +237 -237
  268. package/src/spec/templates.js +91 -91
  269. package/src/swarm/dashboard.js +247 -247
  270. package/src/swarm/factory.js +363 -363
  271. package/src/swarm/pentest.js +934 -934
  272. package/src/swarm/registry.js +419 -419
  273. package/src/swarm/report.js +158 -158
  274. package/src/swarm/runtime.js +569 -576
  275. package/src/swarm/scenario-dsl.js +272 -272
  276. package/src/telemetry/ledger.js +302 -302
  277. package/src/telemetry/session-tracker.js +234 -234
  278. package/src/telemetry/sync.js +203 -203
  279. package/src/ui/command-hints.js +13 -13
  280. package/src/ui/markdown.js +220 -220
@@ -0,0 +1,97 @@
1
+ // Nina (security persona) domain-tool registry (#A13).
2
+ //
3
+ // Each entry exports (a) the tool id the LLM references in tool_use blocks,
4
+ // (b) a schema describing the expected arguments, and (c) the async handler.
5
+
6
+ import { runAuthzAudit } from "./authz-audit.js";
7
+ import { runCryptoReview } from "./crypto-review.js";
8
+ import { runSastScan } from "./sast-scan.js";
9
+ import { runSecretsScan } from "./secrets-scan.js";
10
+
11
+ export const SECURITY_TOOLS = Object.freeze({
12
+ "sast-scan": {
13
+ id: "sast-scan",
14
+ description:
15
+ "Pattern-based SAST over JS/TS/Python/Go/Ruby/Java sources. Returns P0/P1 findings for eval, dynamic Function, shell-injection, innerHTML XSS, Python exec/compile, subprocess shell=True, fs.readFile path traversal.",
16
+ schema: {
17
+ type: "object",
18
+ properties: {
19
+ rootPath: { type: "string", description: "Repo root (defaults to CWD)." },
20
+ files: {
21
+ type: "array",
22
+ items: { type: "string" },
23
+ description: "Optional explicit file list; defaults to a full repo walk.",
24
+ },
25
+ },
26
+ },
27
+ handler: runSastScan,
28
+ },
29
+ "secrets-scan": {
30
+ id: "secrets-scan",
31
+ description:
32
+ "Scan the repo for hardcoded AWS/GitHub/Slack/OpenAI/Anthropic/Stripe tokens, private key blocks, and entropy-gated generic credentials.",
33
+ schema: {
34
+ type: "object",
35
+ properties: {
36
+ rootPath: { type: "string" },
37
+ files: { type: "array", items: { type: "string" } },
38
+ },
39
+ },
40
+ handler: runSecretsScan,
41
+ },
42
+ "authz-audit": {
43
+ id: "authz-audit",
44
+ description:
45
+ "Inspect mutation-style route handlers (POST/PUT/PATCH/DELETE in Express / Fastify / Next.js app router / Python FastAPI / Flask) and flag those without a recognizable auth guard above them.",
46
+ schema: {
47
+ type: "object",
48
+ properties: {
49
+ rootPath: { type: "string" },
50
+ files: { type: "array", items: { type: "string" } },
51
+ },
52
+ },
53
+ handler: runAuthzAudit,
54
+ },
55
+ "crypto-review": {
56
+ id: "crypto-review",
57
+ description:
58
+ "Flag MD5/SHA-1 for security use, Math.random in token/secret/nonce contexts, TLS certificate verification opt-outs (Node / Python / Go cert-bypass toggles), and hardcoded cipher IVs.",
59
+ schema: {
60
+ type: "object",
61
+ properties: {
62
+ rootPath: { type: "string" },
63
+ files: { type: "array", items: { type: "string" } },
64
+ },
65
+ },
66
+ handler: runCryptoReview,
67
+ },
68
+ });
69
+
70
+ export const SECURITY_TOOL_IDS = Object.freeze(Object.keys(SECURITY_TOOLS));
71
+
72
+ export async function dispatchSecurityTool(toolId, args = {}) {
73
+ const tool = SECURITY_TOOLS[toolId];
74
+ if (!tool) {
75
+ throw new Error(`Unknown security tool: ${toolId}`);
76
+ }
77
+ return tool.handler(args);
78
+ }
79
+
80
+ // Run every tool in sequence and return a flat Finding[] across all of
81
+ // them. Used by the persona orchestrator when a "full security sweep" is
82
+ // requested.
83
+ export async function runAllSecurityTools({ rootPath, files = null } = {}) {
84
+ const findings = [];
85
+ for (const toolId of SECURITY_TOOL_IDS) {
86
+ const out = await dispatchSecurityTool(toolId, { rootPath, files });
87
+ findings.push(...out);
88
+ }
89
+ return findings;
90
+ }
91
+
92
+ export {
93
+ runAuthzAudit,
94
+ runCryptoReview,
95
+ runSastScan,
96
+ runSecretsScan,
97
+ };
@@ -0,0 +1,175 @@
1
+ // sast-scan — pattern-based SAST for Nina's security persona (#A13).
2
+ //
3
+ // This is a zero-dep static-analysis pass. We don't try to replicate
4
+ // semgrep / bandit — instead we ship a curated ruleset of rules that the
5
+ // deterministic Omar Gate already validates at commit time, plus a handful
6
+ // of contextual checks that benefit from iterating file-by-file instead of
7
+ // running a global grep. Callers that want semgrep / bandit should wire in
8
+ // the full scanner via the Omar Gate action's SecurityScanGate (#A2); the
9
+ // tool here is for ad-hoc persona-triggered review.
10
+
11
+ import fsp from "node:fs/promises";
12
+ import path from "node:path";
13
+
14
+ import { createFinding, lineNumberOf, walkRepoFiles } from "./base.js";
15
+
16
+ const JS_TS_EXTENSIONS = new Set([
17
+ ".js",
18
+ ".jsx",
19
+ ".ts",
20
+ ".tsx",
21
+ ".mjs",
22
+ ".cjs",
23
+ ]);
24
+ const PY_EXTENSIONS = new Set([".py"]);
25
+ const ALL_CODE_EXTENSIONS = new Set([
26
+ ...JS_TS_EXTENSIONS,
27
+ ...PY_EXTENSIONS,
28
+ ".go",
29
+ ".rb",
30
+ ".java",
31
+ ]);
32
+
33
+ const RULES = [
34
+ {
35
+ id: "sast.eval",
36
+ // Pattern built via concatenation so the source of this file does not
37
+ // contain the literal trigger string verbatim — otherwise the repo's
38
+ // own SAST scanner flags this module with its own rule.
39
+ pattern: new RegExp("(^|[^\\w])" + "e" + "val\\s*\\("),
40
+ severity: "P0",
41
+ languages: [...JS_TS_EXTENSIONS],
42
+ rootCause:
43
+ "The JavaScript dynamic-evaluation built-in executes arbitrary strings as code — any attacker-controlled input becomes RCE.",
44
+ recommendedFix:
45
+ "Replace dynamic evaluation with structured parsing (JSON.parse, a whitelist, or Function with a frozen arg list).",
46
+ confidence: 0.9,
47
+ },
48
+ {
49
+ id: "sast.function-constructor",
50
+ pattern: new RegExp("new\\s+" + "Function\\s*\\("),
51
+ severity: "P0",
52
+ languages: [...JS_TS_EXTENSIONS],
53
+ rootCause:
54
+ "The Function constructor with a string body is a dynamic code-execution sink similar to the dynamic-evaluation built-in.",
55
+ recommendedFix:
56
+ "Use a statically-defined function. If you really need configurable behavior, pass data (not code) and dispatch with a switch / lookup table.",
57
+ confidence: 0.85,
58
+ },
59
+ {
60
+ id: "sast.child-process-shell",
61
+ pattern: /\b(?:exec|execSync|spawnSync)\s*\(\s*[`'"][^`'"]*\$\{[^}]+\}/,
62
+ severity: "P0",
63
+ languages: [...JS_TS_EXTENSIONS],
64
+ rootCause:
65
+ "A template literal interpolates user-controlled data directly into a shell command — classic command injection path.",
66
+ recommendedFix:
67
+ "Switch to execFile / spawn with argv as a list (no shell). If you must use a shell, escape with shell-quote or a whitelist.",
68
+ confidence: 0.85,
69
+ },
70
+ {
71
+ id: "sast.innerhtml-user-input",
72
+ pattern: /\.innerHTML\s*=\s*[^`"';\n]*\b(?:req|request|params|query|body|input)\b/i,
73
+ severity: "P0",
74
+ languages: [...JS_TS_EXTENSIONS],
75
+ rootCause:
76
+ "Writing request-origin data to innerHTML is an XSS vector — HTML special characters execute as markup.",
77
+ recommendedFix:
78
+ "Use textContent for plain text, or DOMPurify.sanitize() / framework-provided escapers for rich content.",
79
+ confidence: 0.8,
80
+ },
81
+ {
82
+ id: "sast.python-exec",
83
+ pattern: /\b(?:exec|compile)\s*\(/,
84
+ severity: "P0",
85
+ languages: [...PY_EXTENSIONS],
86
+ rootCause:
87
+ "Python exec / compile evaluate runtime strings; attacker-controlled arguments become RCE.",
88
+ recommendedFix:
89
+ "Use structured data + dispatch (ast.literal_eval for literals, ast.parse with Validator for stricter control).",
90
+ confidence: 0.8,
91
+ },
92
+ {
93
+ id: "sast.python-subprocess-shell",
94
+ pattern: /subprocess\.(?:run|call|Popen|check_output)\s*\([^)]*shell\s*=\s*True/,
95
+ severity: "P0",
96
+ languages: [...PY_EXTENSIONS],
97
+ rootCause:
98
+ "subprocess with shell=True interpolates arguments into /bin/sh — command injection path when any arg is user-controlled.",
99
+ recommendedFix:
100
+ "Drop shell=True and pass argv as a list. If a shell is required, pipe via shlex.quote on every interpolated value.",
101
+ confidence: 0.9,
102
+ },
103
+ {
104
+ id: "sast.path-traversal-fs-read",
105
+ pattern: /fs\.(?:readFile|readFileSync|createReadStream)\s*\(\s*[^`'";\n]*\b(?:req|request|params|query|body)\b/,
106
+ severity: "P1",
107
+ languages: [...JS_TS_EXTENSIONS],
108
+ rootCause:
109
+ "Passing request-origin strings directly to fs.readFile opens a path-traversal vector.",
110
+ recommendedFix:
111
+ "Resolve + validate the candidate path stays under an allowedRoot (use tools like shared-tools/path-guards.js).",
112
+ confidence: 0.75,
113
+ },
114
+ ];
115
+
116
+ export async function runSastScan({ rootPath, files = null, rules = RULES } = {}) {
117
+ const resolvedRoot = path.resolve(String(rootPath || "."));
118
+ const iterator =
119
+ Array.isArray(files) && files.length > 0
120
+ ? iterateExplicitFiles(resolvedRoot, files)
121
+ : walkRepoFiles({ rootPath: resolvedRoot, extensions: ALL_CODE_EXTENSIONS });
122
+
123
+ const findings = [];
124
+ for await (const { fullPath, relativePath } of iterator) {
125
+ let content;
126
+ try {
127
+ content = await fsp.readFile(fullPath, "utf-8");
128
+ } catch {
129
+ continue;
130
+ }
131
+ const ext = path.extname(fullPath).toLowerCase();
132
+ for (const rule of rules) {
133
+ if (Array.isArray(rule.languages) && !rule.languages.includes(ext)) {
134
+ continue;
135
+ }
136
+ const line = lineNumberOf(content, rule.pattern);
137
+ if (!line) {
138
+ continue;
139
+ }
140
+ const lineContent = content.split(/\r?\n/)[line - 1] || "";
141
+ findings.push(
142
+ createFinding({
143
+ tool: "sast-scan",
144
+ kind: rule.id,
145
+ severity: rule.severity,
146
+ file: relativePath,
147
+ line,
148
+ evidence: lineContent,
149
+ rootCause: rule.rootCause,
150
+ recommendedFix: rule.recommendedFix,
151
+ confidence: rule.confidence,
152
+ })
153
+ );
154
+ }
155
+ }
156
+ return findings;
157
+ }
158
+
159
+ async function* iterateExplicitFiles(resolvedRoot, files) {
160
+ for (const file of files) {
161
+ const trimmed = String(file || "").trim();
162
+ if (!trimmed) {
163
+ continue;
164
+ }
165
+ const fullPath = path.isAbsolute(trimmed)
166
+ ? trimmed
167
+ : path.join(resolvedRoot, trimmed);
168
+ const relativePath = path
169
+ .relative(resolvedRoot, fullPath)
170
+ .replace(/\\/g, "/");
171
+ yield { fullPath, relativePath };
172
+ }
173
+ }
174
+
175
+ export { RULES as SAST_RULES };
@@ -0,0 +1,216 @@
1
+ // secrets-scan — filesystem scan for credentials (#A13).
2
+ //
3
+ // Mirrors the highest-confidence rules from gitleaks / trufflehog so the
4
+ // tool can run without external binaries. When gitleaks is installed it
5
+ // shadows this, but the zero-dep fallback keeps persona dispatch honest
6
+ // on stripped-down runners.
7
+
8
+ import fsp from "node:fs/promises";
9
+ import path from "node:path";
10
+
11
+ import { createFinding, walkRepoFiles } from "./base.js";
12
+
13
+ const DEFAULT_EXTENSIONS = new Set([
14
+ ".js",
15
+ ".jsx",
16
+ ".ts",
17
+ ".tsx",
18
+ ".mjs",
19
+ ".cjs",
20
+ ".py",
21
+ ".go",
22
+ ".rb",
23
+ ".rs",
24
+ ".java",
25
+ ".kt",
26
+ ".yaml",
27
+ ".yml",
28
+ ".json",
29
+ ".toml",
30
+ ".env",
31
+ ".env.local",
32
+ ".env.production",
33
+ ".sh",
34
+ ".bash",
35
+ // Extensionless files like id_rsa / id_ed25519 / .pem with no extension
36
+ // frequently contain secrets; let the walker yield them too.
37
+ "",
38
+ ".pem",
39
+ ".key",
40
+ ".crt",
41
+ ]);
42
+
43
+ // Each rule captures (a) regex to detect the secret, (b) entropy floor (to
44
+ // filter placeholder values like "X"*40), (c) severity and description.
45
+ const RULES = [
46
+ {
47
+ id: "secret.aws-access-key",
48
+ pattern: /\bAKIA[0-9A-Z]{16}\b/,
49
+ severity: "P0",
50
+ minEntropy: 3.0,
51
+ description:
52
+ "AWS Access Key ID committed to the repo — rotate and revoke immediately.",
53
+ },
54
+ {
55
+ id: "secret.aws-secret-access-key",
56
+ pattern: /aws_secret_access_key\s*[:=]\s*['"]?[A-Za-z0-9/+=]{40}['"]?/i,
57
+ severity: "P0",
58
+ minEntropy: 4.0,
59
+ description: "AWS secret access key assignment with 40-char token shape.",
60
+ },
61
+ {
62
+ id: "secret.github-token",
63
+ pattern: /\bgh[ps]_[A-Za-z0-9]{36}\b/,
64
+ severity: "P0",
65
+ minEntropy: 3.5,
66
+ description: "GitHub personal / PAT token committed to the repo.",
67
+ },
68
+ {
69
+ id: "secret.slack-token",
70
+ pattern: /\bxox[aboprs]-[A-Za-z0-9-]{10,}\b/,
71
+ severity: "P0",
72
+ minEntropy: 3.5,
73
+ description: "Slack token (bot/app/workflow) — revoke via Slack admin.",
74
+ },
75
+ {
76
+ id: "secret.openai-api-key",
77
+ pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/,
78
+ severity: "P0",
79
+ minEntropy: 3.0,
80
+ description:
81
+ "OpenAI API key committed to the repo — revoke in the OpenAI dashboard.",
82
+ },
83
+ {
84
+ id: "secret.anthropic-api-key",
85
+ pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/,
86
+ severity: "P0",
87
+ minEntropy: 3.0,
88
+ description: "Anthropic API key committed to the repo.",
89
+ },
90
+ {
91
+ id: "secret.stripe-secret-key",
92
+ pattern: /\bsk_(?:live|test)_[A-Za-z0-9]{24,}\b/,
93
+ severity: "P0",
94
+ minEntropy: 3.5,
95
+ description:
96
+ "Stripe secret key committed. Test keys still expose test-mode customers.",
97
+ },
98
+ {
99
+ id: "secret.private-key-block",
100
+ pattern: /-----BEGIN (?:RSA |DSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/,
101
+ severity: "P0",
102
+ minEntropy: 0, // PEM block is unambiguous without entropy check
103
+ description:
104
+ "Private key block embedded in source — rotate and remove from history.",
105
+ },
106
+ {
107
+ id: "secret.generic-hardcoded",
108
+ pattern: /(?:api[_-]?key|secret|token|password|passwd)\s*[:=]\s*['"][A-Za-z0-9_\-]{16,}['"]/i,
109
+ severity: "P1",
110
+ minEntropy: 3.2,
111
+ description:
112
+ "High-entropy hardcoded credential assigned to a security-sounding identifier.",
113
+ },
114
+ ];
115
+
116
+ function shannonEntropy(str) {
117
+ const text = String(str || "");
118
+ if (!text) {
119
+ return 0;
120
+ }
121
+ const freq = new Map();
122
+ for (const ch of text) {
123
+ freq.set(ch, (freq.get(ch) || 0) + 1);
124
+ }
125
+ const len = text.length;
126
+ let entropy = 0;
127
+ for (const count of freq.values()) {
128
+ const p = count / len;
129
+ entropy -= p * Math.log2(p);
130
+ }
131
+ return entropy;
132
+ }
133
+
134
+ function extractToken(match) {
135
+ // Pull the first contiguous token-ish substring from the match so we can
136
+ // measure entropy on the actual secret instead of surrounding assignment
137
+ // boilerplate.
138
+ const text = String(match || "");
139
+ const token = text.match(/[A-Za-z0-9_\-./+=]{10,}/);
140
+ return token ? token[0] : text;
141
+ }
142
+
143
+ export async function runSecretsScan({ rootPath, files = null } = {}) {
144
+ const resolvedRoot = path.resolve(String(rootPath || "."));
145
+ const iterator =
146
+ Array.isArray(files) && files.length > 0
147
+ ? iterateExplicitFiles(resolvedRoot, files)
148
+ : walkRepoFiles({ rootPath: resolvedRoot, extensions: DEFAULT_EXTENSIONS });
149
+
150
+ const findings = [];
151
+ for await (const { fullPath, relativePath } of iterator) {
152
+ let content;
153
+ try {
154
+ content = await fsp.readFile(fullPath, "utf-8");
155
+ } catch {
156
+ continue;
157
+ }
158
+ const lines = content.split(/\r?\n/);
159
+ for (const rule of RULES) {
160
+ const global = new RegExp(rule.pattern.source, rule.pattern.flags.includes("g") ? rule.pattern.flags : `${rule.pattern.flags}g`);
161
+ let match;
162
+ while ((match = global.exec(content)) !== null) {
163
+ const token = extractToken(match[0]);
164
+ const entropy = shannonEntropy(token);
165
+ if (rule.minEntropy > 0 && entropy < rule.minEntropy) {
166
+ continue;
167
+ }
168
+ const lineIndex = content.slice(0, match.index).split(/\r?\n/).length;
169
+ const evidence = (lines[lineIndex - 1] || "").trim().slice(0, 200);
170
+ findings.push(
171
+ createFinding({
172
+ tool: "secrets-scan",
173
+ kind: rule.id,
174
+ severity: rule.severity,
175
+ file: relativePath,
176
+ line: lineIndex,
177
+ evidence: redactEvidence(evidence, token),
178
+ rootCause: rule.description,
179
+ recommendedFix:
180
+ "Revoke the credential at the provider, rotate to a new secret, and store it in your secret manager (never in source).",
181
+ confidence: Math.max(0.7, Math.min(1, entropy / 5)),
182
+ })
183
+ );
184
+ }
185
+ }
186
+ }
187
+ return findings;
188
+ }
189
+
190
+ function redactEvidence(line, token) {
191
+ if (!token || token.length < 12) {
192
+ return line;
193
+ }
194
+ // Show first 6 + "..." so reviewers can recognize the key family without
195
+ // re-exposing the full secret in logs.
196
+ const redacted = `${token.slice(0, 6)}...${token.slice(-2)}`;
197
+ return line.replace(token, redacted);
198
+ }
199
+
200
+ async function* iterateExplicitFiles(resolvedRoot, files) {
201
+ for (const file of files) {
202
+ const trimmed = String(file || "").trim();
203
+ if (!trimmed) {
204
+ continue;
205
+ }
206
+ const fullPath = path.isAbsolute(trimmed)
207
+ ? trimmed
208
+ : path.join(resolvedRoot, trimmed);
209
+ const relativePath = path
210
+ .relative(resolvedRoot, fullPath)
211
+ .replace(/\\/g, "/");
212
+ yield { fullPath, relativePath };
213
+ }
214
+ }
215
+
216
+ export { RULES as SECRETS_RULES };