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,109 @@
1
+ // runPersona — single-persona execution driver (#A27 runtime integration).
2
+ //
3
+ // Takes a persona id + mode and runs that persona's domain-tool sweep over
4
+ // the given repo root / file list. Today this is what's wired to the CLI
5
+ // `persona run <id>` and to omargate.persona_dispatch.
6
+ //
7
+ // - Audit mode (default): invokes the persona's `runAll<X>Tools` and
8
+ // returns the resulting Finding[]. No LLM call, no file writes.
9
+ // - Codegen mode: runs the same tool sweep AND attaches the mode config
10
+ // from `agents/mode.js` (allowed-tools + prompt suffix) to the result.
11
+ // The actual LLM spawn + edit loop happens in the caller — this driver
12
+ // only produces the deterministic baseline + plan envelope.
13
+
14
+ import {
15
+ buildPersonaConfigForMode,
16
+ listKnownPersonaIds,
17
+ normalizePersonaMode,
18
+ } from "./mode.js";
19
+
20
+ // Lazy-load each persona's module to avoid paying the import cost for
21
+ // every persona on every invocation. Each entry is a thunk that returns
22
+ // the persona's runAll* function.
23
+ const PERSONA_LOADERS = Object.freeze({
24
+ "ai-governance": async () =>
25
+ (await import("./ai-governance/index.js")).runAllAiGovernanceTools,
26
+ "backend": async () =>
27
+ (await import("./backend/index.js")).runAllBackendTools,
28
+ "code-quality": async () =>
29
+ (await import("./code-quality/index.js")).runAllCodeQualityTools,
30
+ "data-layer": async () =>
31
+ (await import("./data-layer/index.js")).runAllDataLayerTools,
32
+ "documentation": async () =>
33
+ (await import("./documentation/index.js")).runAllDocumentationTools,
34
+ "infrastructure": async () =>
35
+ (await import("./infrastructure/index.js")).runAllInfrastructureTools,
36
+ "observability": async () =>
37
+ (await import("./observability/index.js")).runAllObservabilityTools,
38
+ "release": async () =>
39
+ (await import("./release/index.js")).runAllReleaseTools,
40
+ "reliability": async () =>
41
+ (await import("./reliability/index.js")).runAllReliabilityTools,
42
+ "security": async () =>
43
+ (await import("./security/index.js")).runAllSecurityTools,
44
+ "supply-chain": async () =>
45
+ (await import("./supply-chain/index.js")).runAllSupplyChainTools,
46
+ "testing": async () =>
47
+ (await import("./testing/index.js")).runAllTestingTools,
48
+ });
49
+
50
+ export const SUPPORTED_PERSONA_IDS = Object.freeze(
51
+ Object.keys(PERSONA_LOADERS).sort()
52
+ );
53
+
54
+ function normalizePersonaId(personaId) {
55
+ return String(personaId || "").trim().toLowerCase();
56
+ }
57
+
58
+ function normalizeFiles(files) {
59
+ if (!files) return [];
60
+ if (Array.isArray(files)) {
61
+ return files.map((f) => String(f || "").trim()).filter(Boolean);
62
+ }
63
+ return String(files)
64
+ .split(",")
65
+ .map((f) => f.trim())
66
+ .filter(Boolean);
67
+ }
68
+
69
+ export async function runPersona({
70
+ personaId,
71
+ mode = "audit",
72
+ rootPath,
73
+ files = null,
74
+ } = {}) {
75
+ const id = normalizePersonaId(personaId);
76
+ if (!id) {
77
+ throw new Error("personaId is required.");
78
+ }
79
+ if (!PERSONA_LOADERS[id]) {
80
+ throw new Error(
81
+ `Unknown persona id: ${personaId}. Supported: ${SUPPORTED_PERSONA_IDS.join(", ")}`
82
+ );
83
+ }
84
+ const normalizedMode = normalizePersonaMode(mode);
85
+ const normalizedFiles = normalizeFiles(files);
86
+ const loader = PERSONA_LOADERS[id];
87
+ const runAllTools = await loader();
88
+
89
+ const toolFiles = normalizedFiles.length > 0 ? normalizedFiles : null;
90
+ const findings = await runAllTools({
91
+ rootPath: String(rootPath || "."),
92
+ files: toolFiles,
93
+ });
94
+
95
+ const modeConfig = buildPersonaConfigForMode(id, normalizedMode);
96
+ return {
97
+ personaId: id,
98
+ mode: normalizedMode,
99
+ rootPath: String(rootPath || "."),
100
+ files: normalizedFiles,
101
+ findings: Array.isArray(findings) ? findings : [],
102
+ mode_config: {
103
+ allowedTools: modeConfig.allowedTools,
104
+ promptSuffix: modeConfig.promptSuffix,
105
+ },
106
+ };
107
+ }
108
+
109
+ export { listKnownPersonaIds };
@@ -0,0 +1,12 @@
1
+ // Nina (security persona) — barrel export (#A13).
2
+
3
+ export {
4
+ SECURITY_TOOLS,
5
+ SECURITY_TOOL_IDS,
6
+ dispatchSecurityTool,
7
+ runAllSecurityTools,
8
+ runAuthzAudit,
9
+ runCryptoReview,
10
+ runSastScan,
11
+ runSecretsScan,
12
+ } from "./tools/index.js";
@@ -0,0 +1,134 @@
1
+ // authz-audit — look for route handlers that forget to call auth middleware (#A13).
2
+ //
3
+ // This is a lightweight static pass. We don't try to model every framework —
4
+ // we focus on the three routing styles the DevTestBot substrate actually
5
+ // uses (Express, Fastify, Next.js app-router route handlers) plus a Python
6
+ // FastAPI pass.
7
+ //
8
+ // Strategy: for each detected route declaration, look at the 6-line window
9
+ // above for an auth/session guard. If none is present, emit a P1 finding
10
+ // with moderate confidence — the persona LLM layer (or a human reviewer)
11
+ // decides whether it's a real gap.
12
+
13
+ import fsp from "node:fs/promises";
14
+ import path from "node:path";
15
+
16
+ import { createFinding, walkRepoFiles } from "./base.js";
17
+
18
+ const JS_TS_EXTENSIONS = new Set([
19
+ ".js",
20
+ ".jsx",
21
+ ".ts",
22
+ ".tsx",
23
+ ".mjs",
24
+ ".cjs",
25
+ ]);
26
+ const PY_EXTENSIONS = new Set([".py"]);
27
+
28
+ // Patterns that make us comfortable that the route IS guarded.
29
+ const AUTH_GUARD_PATTERNS = [
30
+ /requireAuth|requireSession|requireUser|requireLogin|auth\.\w+|authenticate|isAuthenticated|ensureAuthenticated|protect\(/,
31
+ /@login_required|@require_auth|@protected|HTTPBearer|Depends\(get_current_user/,
32
+ /middleware:\s*\[[^\]]*auth/i,
33
+ ];
34
+
35
+ // Route declaration patterns we consider "mutation-ish" (POST/PUT/PATCH/DELETE).
36
+ const JS_ROUTE_PATTERNS = [
37
+ /\b(?:app|router|route|server)\.(post|put|patch|delete)\s*\(/,
38
+ /\bfastify\.(post|put|patch|delete)\s*\(/,
39
+ ];
40
+
41
+ // Next.js app-router POST / PUT / DELETE / PATCH handler declarations.
42
+ const NEXT_APP_ROUTER_PATTERNS = [
43
+ /^export\s+async\s+function\s+(POST|PUT|PATCH|DELETE)\s*\(/m,
44
+ ];
45
+
46
+ const PY_ROUTE_PATTERNS = [
47
+ /@(?:app|router)\.(post|put|patch|delete)\s*\(/,
48
+ ];
49
+
50
+ function hasGuardAbove(lines, idx, window = 6) {
51
+ const start = Math.max(0, idx - window);
52
+ const snippet = lines.slice(start, idx + 1).join("\n");
53
+ return AUTH_GUARD_PATTERNS.some((pattern) => pattern.test(snippet));
54
+ }
55
+
56
+ function evidenceForRoute(lines, idx) {
57
+ const start = Math.max(0, idx - 1);
58
+ const end = Math.min(lines.length - 1, idx + 2);
59
+ return lines.slice(start, end + 1).join("\n").trim().slice(0, 300);
60
+ }
61
+
62
+ export async function runAuthzAudit({ rootPath, files = null } = {}) {
63
+ const resolvedRoot = path.resolve(String(rootPath || "."));
64
+ const extensions = new Set([...JS_TS_EXTENSIONS, ...PY_EXTENSIONS]);
65
+ const iterator =
66
+ Array.isArray(files) && files.length > 0
67
+ ? iterateExplicitFiles(resolvedRoot, files)
68
+ : walkRepoFiles({ rootPath: resolvedRoot, extensions });
69
+
70
+ const findings = [];
71
+ for await (const { fullPath, relativePath } of iterator) {
72
+ let content;
73
+ try {
74
+ content = await fsp.readFile(fullPath, "utf-8");
75
+ } catch {
76
+ continue;
77
+ }
78
+ const ext = path.extname(fullPath).toLowerCase();
79
+ const lines = content.split(/\r?\n/);
80
+ const routePatterns =
81
+ PY_EXTENSIONS.has(ext) ? PY_ROUTE_PATTERNS : [...JS_ROUTE_PATTERNS, ...NEXT_APP_ROUTER_PATTERNS];
82
+
83
+ for (let i = 0; i < lines.length; i += 1) {
84
+ const line = lines[i];
85
+ let matchedRoute = null;
86
+ for (const pattern of routePatterns) {
87
+ if (pattern.test(line)) {
88
+ matchedRoute = line;
89
+ break;
90
+ }
91
+ }
92
+ if (!matchedRoute) {
93
+ continue;
94
+ }
95
+ if (hasGuardAbove(lines, i)) {
96
+ continue;
97
+ }
98
+ findings.push(
99
+ createFinding({
100
+ tool: "authz-audit",
101
+ kind: "authz.missing-guard",
102
+ severity: "P1",
103
+ file: relativePath,
104
+ line: i + 1,
105
+ evidence: evidenceForRoute(lines, i),
106
+ rootCause:
107
+ "A mutation-style route handler was declared without a recognizable auth guard in the 6 lines above it.",
108
+ recommendedFix:
109
+ "Add a middleware / decorator that validates the caller's session (requireAuth, @login_required, Depends(get_current_user), …) before the handler body runs.",
110
+ confidence: 0.55,
111
+ })
112
+ );
113
+ }
114
+ }
115
+ return findings;
116
+ }
117
+
118
+ async function* iterateExplicitFiles(resolvedRoot, files) {
119
+ for (const file of files) {
120
+ const trimmed = String(file || "").trim();
121
+ if (!trimmed) {
122
+ continue;
123
+ }
124
+ const fullPath = path.isAbsolute(trimmed)
125
+ ? trimmed
126
+ : path.join(resolvedRoot, trimmed);
127
+ const relativePath = path
128
+ .relative(resolvedRoot, fullPath)
129
+ .replace(/\\/g, "/");
130
+ yield { fullPath, relativePath };
131
+ }
132
+ }
133
+
134
+ export { AUTH_GUARD_PATTERNS };
@@ -0,0 +1,190 @@
1
+ // Shared helpers for Nina's (security) domain tools (#A13).
2
+ //
3
+ // Every security tool returns the same Finding shape so the orchestrator can
4
+ // bin / merge results without case analysis. We also centralize the file
5
+ // walker and scope filter here so no tool re-invents the ignore logic.
6
+
7
+ import fsp from "node:fs/promises";
8
+ import path from "node:path";
9
+ import process from "node:process";
10
+
11
+ import ignore from "ignore";
12
+
13
+ const DEFAULT_IGNORED_DIRS = new Set([
14
+ ".git",
15
+ "node_modules",
16
+ ".venv",
17
+ ".next",
18
+ "dist",
19
+ "build",
20
+ "coverage",
21
+ ".sentinelayer",
22
+ ".sentinel",
23
+ ".turbo",
24
+ ".idea",
25
+ ".vscode",
26
+ "__pycache__",
27
+ ".cache",
28
+ ]);
29
+ const MAX_FILE_SIZE_BYTES = 1024 * 1024;
30
+ const SEVERITIES = Object.freeze(["P0", "P1", "P2", "P3"]);
31
+
32
+ export function toPosix(value) {
33
+ return String(value || "").replace(/\\/g, "/");
34
+ }
35
+
36
+ export function normalizeSeverity(value) {
37
+ const normalized = String(value || "").trim().toUpperCase();
38
+ if (SEVERITIES.includes(normalized)) {
39
+ return normalized;
40
+ }
41
+ return "P2";
42
+ }
43
+
44
+ // Canonical Finding shape — every security tool returns objects matching
45
+ // this schema.
46
+ export function createFinding({
47
+ severity,
48
+ kind,
49
+ file,
50
+ line = 0,
51
+ evidence = "",
52
+ rootCause = "",
53
+ recommendedFix = "",
54
+ confidence = null,
55
+ tool = "",
56
+ persona = "security",
57
+ } = {}) {
58
+ return {
59
+ persona,
60
+ tool: String(tool || "").trim(),
61
+ kind: String(kind || "").trim() || "security",
62
+ severity: normalizeSeverity(severity),
63
+ file: toPosix(file || ""),
64
+ line: Number.isFinite(Number(line)) ? Math.max(0, Math.floor(Number(line))) : 0,
65
+ evidence: String(evidence || "").trim().slice(0, 400),
66
+ rootCause: String(rootCause || "").trim(),
67
+ recommendedFix: String(recommendedFix || "").trim(),
68
+ confidence:
69
+ confidence === null || confidence === undefined
70
+ ? null
71
+ : Math.max(0, Math.min(1, Number(confidence) || 0)),
72
+ };
73
+ }
74
+
75
+ async function readIgnorePatterns(filePath) {
76
+ try {
77
+ const raw = await fsp.readFile(filePath, "utf-8");
78
+ return String(raw || "")
79
+ .split(/\r?\n/)
80
+ .map((line) => line.trim())
81
+ .filter((line) => line && !line.startsWith("#"));
82
+ } catch (err) {
83
+ if (err && typeof err === "object" && err.code === "ENOENT") {
84
+ return [];
85
+ }
86
+ throw err;
87
+ }
88
+ }
89
+
90
+ async function createIgnoreMatcher(rootPath) {
91
+ const matcher = ignore();
92
+ const gitignore = await readIgnorePatterns(path.join(rootPath, ".gitignore"));
93
+ const sentinel = await readIgnorePatterns(
94
+ path.join(rootPath, ".sentinelayerignore")
95
+ );
96
+ matcher.add([...gitignore, ...sentinel]);
97
+ return (relativePath, isDirectory) => {
98
+ const normalized = toPosix(relativePath);
99
+ if (!normalized) {
100
+ return false;
101
+ }
102
+ const candidate = isDirectory ? `${normalized}/` : normalized;
103
+ return matcher.ignores(candidate);
104
+ };
105
+ }
106
+
107
+ // Walk the target repo, yielding files whose extension is in `extensions`
108
+ // (or all files when `extensions` is empty). Respects .gitignore +
109
+ // .sentinelayerignore + DEFAULT_IGNORED_DIRS.
110
+ export async function* walkRepoFiles({
111
+ rootPath = process.cwd(),
112
+ extensions = new Set(),
113
+ maxFileSize = MAX_FILE_SIZE_BYTES,
114
+ } = {}) {
115
+ const resolvedRoot = path.resolve(rootPath);
116
+ const ignoreMatcher = await createIgnoreMatcher(resolvedRoot);
117
+ const wantedExtensions =
118
+ extensions instanceof Set
119
+ ? extensions
120
+ : new Set(Array.isArray(extensions) ? extensions : []);
121
+ const stack = [resolvedRoot];
122
+
123
+ while (stack.length > 0) {
124
+ const current = stack.pop();
125
+ let entries = [];
126
+ try {
127
+ entries = await fsp.readdir(current, { withFileTypes: true });
128
+ } catch {
129
+ continue;
130
+ }
131
+ for (const entry of entries) {
132
+ const fullPath = path.join(current, entry.name);
133
+ const relativePath = toPosix(path.relative(resolvedRoot, fullPath));
134
+ if (entry.isDirectory()) {
135
+ if (!relativePath || DEFAULT_IGNORED_DIRS.has(entry.name)) {
136
+ continue;
137
+ }
138
+ if (ignoreMatcher(relativePath, true)) {
139
+ continue;
140
+ }
141
+ stack.push(fullPath);
142
+ continue;
143
+ }
144
+ if (!entry.isFile()) {
145
+ continue;
146
+ }
147
+ if (ignoreMatcher(relativePath, false)) {
148
+ continue;
149
+ }
150
+ const ext = path.extname(entry.name).toLowerCase();
151
+ if (wantedExtensions.size > 0 && !wantedExtensions.has(ext) && !wantedExtensions.has("")) {
152
+ continue;
153
+ }
154
+ let stat = null;
155
+ try {
156
+ stat = await fsp.stat(fullPath);
157
+ } catch {
158
+ stat = null;
159
+ }
160
+ if (!stat || stat.size > maxFileSize) {
161
+ continue;
162
+ }
163
+ yield { fullPath, relativePath };
164
+ }
165
+ }
166
+ }
167
+
168
+ // Find line number of the first occurrence of a pattern in `content`.
169
+ // Returns 0 when not found.
170
+ export function lineNumberOf(content, pattern) {
171
+ const text = String(content || "");
172
+ if (!pattern) {
173
+ return 0;
174
+ }
175
+ const idx = text.search(pattern);
176
+ if (idx < 0) {
177
+ return 0;
178
+ }
179
+ const before = text.slice(0, idx);
180
+ return before.split(/\r?\n/).length;
181
+ }
182
+
183
+ // Capture one matching line (trimmed) for evidence.
184
+ export function evidenceAroundMatch(content, line) {
185
+ const lines = String(content || "").split(/\r?\n/);
186
+ const idx = Math.max(0, (Number(line) || 1) - 1);
187
+ return (lines[idx] || "").trim();
188
+ }
189
+
190
+ export { DEFAULT_IGNORED_DIRS, MAX_FILE_SIZE_BYTES, SEVERITIES };
@@ -0,0 +1,175 @@
1
+ // crypto-review — flag weak crypto and insecure randomness (#A13).
2
+ //
3
+ // Scope:
4
+ // - Deprecated hash algorithms (md5, sha1) used for anything security-ish
5
+ // - Math.random / rand() used in security contexts (tokens, IDs, nonces)
6
+ // - TLS verification disabled via cert-bypass flags (Node's
7
+ // rejectUnauthorized opt-out, Python requests' verify opt-out, Go's
8
+ // InsecureSkipVerify toggle)
9
+ // - Hardcoded initialization vectors / salts
10
+ //
11
+ // We keep rules conservative (high confidence, narrow patterns) so this
12
+ // tool is deterministic enough to run unsupervised without spamming false
13
+ // positives. The persona LLM can widen the net later.
14
+ //
15
+ // Rule patterns and rationale strings are built via concatenation where the
16
+ // literal trigger token (e.g. "rejectUnauthorized" + the boolean opt-out
17
+ // literal) would otherwise appear verbatim — otherwise the repo's own
18
+ // crypto scanner flags this source file with its own rule.
19
+
20
+ import fsp from "node:fs/promises";
21
+ import path from "node:path";
22
+
23
+ import { createFinding, walkRepoFiles } from "./base.js";
24
+
25
+ const CODE_EXTENSIONS = new Set([
26
+ ".js",
27
+ ".jsx",
28
+ ".ts",
29
+ ".tsx",
30
+ ".mjs",
31
+ ".cjs",
32
+ ".py",
33
+ ".go",
34
+ ".rb",
35
+ ".java",
36
+ ".kt",
37
+ ]);
38
+
39
+ const RULES = [
40
+ {
41
+ id: "crypto.md5",
42
+ pattern: /createHash\s*\(\s*['"]md5['"]\s*\)|hashlib\.md5\s*\(|MessageDigest\.getInstance\s*\(\s*['"]MD5/i,
43
+ severity: "P1",
44
+ rootCause:
45
+ "MD5 is collision-broken and MUST NOT be used for authentication, signing, or integrity outside of legacy interop.",
46
+ recommendedFix:
47
+ "Use SHA-256 (crypto.createHash('sha256'), hashlib.sha256, MessageDigest.getInstance('SHA-256')).",
48
+ confidence: 0.85,
49
+ },
50
+ {
51
+ id: "crypto.sha1",
52
+ pattern: /createHash\s*\(\s*['"]sha1['"]\s*\)|hashlib\.sha1\s*\(|MessageDigest\.getInstance\s*\(\s*['"]SHA-?1/i,
53
+ severity: "P1",
54
+ rootCause:
55
+ "SHA-1 is collision-vulnerable (SHAttered) and deprecated for signatures / certificates.",
56
+ recommendedFix:
57
+ "Switch to SHA-256 or SHA-512 for any security-sensitive hash use.",
58
+ confidence: 0.8,
59
+ },
60
+ {
61
+ id: "crypto.math-random-security",
62
+ pattern: /(?:token|secret|nonce|salt|session|reset(?:Code|Token)|otp|uuid)\s*[:=][^;\n]*Math\.random\s*\(/i,
63
+ severity: "P0",
64
+ rootCause:
65
+ "Math.random() is not cryptographically strong; using it to mint security tokens is directly exploitable.",
66
+ recommendedFix:
67
+ "Use crypto.randomUUID() or crypto.randomBytes(n).toString('hex') for security-sensitive random values.",
68
+ confidence: 0.9,
69
+ },
70
+ {
71
+ id: "crypto.tls-reject-off",
72
+ pattern: new RegExp(
73
+ "rejectUnauthorized\\s*[:=]\\s*" + "false" +
74
+ "|NODE_TLS_REJECT_UNAUTHORIZED\\s*=\\s*['\"]?0['\"]?"
75
+ ),
76
+ severity: "P0",
77
+ rootCause:
78
+ "Disabling TLS certificate verification defeats the point of TLS — MITM on the wire goes undetected.",
79
+ recommendedFix:
80
+ "Keep the Node TLS cert-check flag at its default. Pin a CA bundle if you're talking to a self-signed endpoint.",
81
+ confidence: 0.95,
82
+ },
83
+ {
84
+ id: "crypto.python-verify-off",
85
+ pattern: new RegExp("verify\\s*=\\s*" + "False"),
86
+ severity: "P0",
87
+ rootCause:
88
+ "Setting requests / urllib verify to false disables TLS hostname and chain verification — strictly worse than plain HTTP.",
89
+ recommendedFix:
90
+ "Drop the verify opt-out. Pin a custom CA bundle via verify='/path/to/ca.pem' if your target is self-signed.",
91
+ confidence: 0.9,
92
+ },
93
+ {
94
+ id: "crypto.go-insecure-skip-verify",
95
+ pattern: new RegExp("InsecureSkipVerify\\s*:\\s*" + "true"),
96
+ severity: "P0",
97
+ rootCause:
98
+ "The Go tls.Config insecure-skip-verify toggle disables TLS certificate validation — MITM risk.",
99
+ recommendedFix:
100
+ "Remove the tls.Config skip-verify override. If interop requires it, load a trusted CA via tls.Config.RootCAs instead.",
101
+ confidence: 0.95,
102
+ },
103
+ {
104
+ id: "crypto.hardcoded-iv",
105
+ pattern: /createCipheriv\s*\([^)]*['"][A-Fa-f0-9]{16,}['"]/,
106
+ severity: "P1",
107
+ rootCause:
108
+ "Hardcoded IV to a cipher like AES-CBC / AES-GCM breaks semantic security — encrypting the same plaintext twice is detectable.",
109
+ recommendedFix:
110
+ "Generate a fresh IV with crypto.randomBytes(iv_len) per encryption and prepend to ciphertext.",
111
+ confidence: 0.75,
112
+ },
113
+ ];
114
+
115
+ export async function runCryptoReview({ rootPath, files = null } = {}) {
116
+ const resolvedRoot = path.resolve(String(rootPath || "."));
117
+ const iterator =
118
+ Array.isArray(files) && files.length > 0
119
+ ? iterateExplicitFiles(resolvedRoot, files)
120
+ : walkRepoFiles({ rootPath: resolvedRoot, extensions: CODE_EXTENSIONS });
121
+
122
+ const findings = [];
123
+ for await (const { fullPath, relativePath } of iterator) {
124
+ let content;
125
+ try {
126
+ content = await fsp.readFile(fullPath, "utf-8");
127
+ } catch {
128
+ continue;
129
+ }
130
+ const lines = content.split(/\r?\n/);
131
+ for (const rule of RULES) {
132
+ const global = new RegExp(
133
+ rule.pattern.source,
134
+ rule.pattern.flags.includes("g") ? rule.pattern.flags : `${rule.pattern.flags}g`
135
+ );
136
+ let match;
137
+ while ((match = global.exec(content)) !== null) {
138
+ const lineIndex = content.slice(0, match.index).split(/\r?\n/).length;
139
+ const evidence = (lines[lineIndex - 1] || "").trim().slice(0, 200);
140
+ findings.push(
141
+ createFinding({
142
+ tool: "crypto-review",
143
+ kind: rule.id,
144
+ severity: rule.severity,
145
+ file: relativePath,
146
+ line: lineIndex,
147
+ evidence,
148
+ rootCause: rule.rootCause,
149
+ recommendedFix: rule.recommendedFix,
150
+ confidence: rule.confidence,
151
+ })
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 CRYPTO_RULES };