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.
- package/README.md +1009 -996
- package/bin/create-sentinelayer.js +5 -5
- package/bin/sentinelayer-cli.js +4 -4
- package/bin/sl.js +5 -5
- package/package.json +64 -63
- package/src/agents/ai-governance/index.js +12 -0
- package/src/agents/ai-governance/tools/base.js +171 -0
- package/src/agents/ai-governance/tools/eval-regression.js +47 -0
- package/src/agents/ai-governance/tools/hitl-audit.js +81 -0
- package/src/agents/ai-governance/tools/index.js +52 -0
- package/src/agents/ai-governance/tools/prompt-drift.js +42 -0
- package/src/agents/ai-governance/tools/provenance-check.js +69 -0
- package/src/agents/backend/index.js +12 -0
- package/src/agents/backend/tools/base.js +189 -0
- package/src/agents/backend/tools/circuit-breaker-check.js +123 -0
- package/src/agents/backend/tools/idempotency-audit.js +105 -0
- package/src/agents/backend/tools/index.js +87 -0
- package/src/agents/backend/tools/retry-audit.js +132 -0
- package/src/agents/backend/tools/timeout-audit.js +144 -0
- package/src/agents/code-quality/index.js +12 -0
- package/src/agents/code-quality/tools/base.js +159 -0
- package/src/agents/code-quality/tools/complexity-measure.js +197 -0
- package/src/agents/code-quality/tools/coupling-analysis.js +81 -0
- package/src/agents/code-quality/tools/cycle-detect.js +49 -0
- package/src/agents/code-quality/tools/dep-graph.js +196 -0
- package/src/agents/code-quality/tools/index.js +89 -0
- package/src/agents/data-layer/index.js +12 -0
- package/src/agents/data-layer/tools/base.js +181 -0
- package/src/agents/data-layer/tools/index-audit.js +165 -0
- package/src/agents/data-layer/tools/index.js +83 -0
- package/src/agents/data-layer/tools/migration-scan.js +135 -0
- package/src/agents/data-layer/tools/query-explain.js +120 -0
- package/src/agents/data-layer/tools/tenancy-scan.js +166 -0
- package/src/agents/documentation/index.js +12 -0
- package/src/agents/documentation/tools/api-diff.js +91 -0
- package/src/agents/documentation/tools/base.js +151 -0
- package/src/agents/documentation/tools/dead-link-check.js +58 -0
- package/src/agents/documentation/tools/docstring-coverage.js +78 -0
- package/src/agents/documentation/tools/index.js +52 -0
- package/src/agents/documentation/tools/readme-freshness.js +61 -0
- package/src/agents/envelope/fix-cycle.js +45 -0
- package/src/agents/envelope/index.js +31 -0
- package/src/agents/envelope/loop.js +150 -0
- package/src/agents/envelope/pulse.js +18 -0
- package/src/agents/envelope/stream.js +40 -0
- package/src/agents/infrastructure/index.js +12 -0
- package/src/agents/infrastructure/tools/base.js +171 -0
- package/src/agents/infrastructure/tools/checkov-run.js +32 -0
- package/src/agents/infrastructure/tools/drift-detect.js +59 -0
- package/src/agents/infrastructure/tools/iam-least-priv-check.js +78 -0
- package/src/agents/infrastructure/tools/index.js +52 -0
- package/src/agents/infrastructure/tools/tflint-run.js +31 -0
- package/src/agents/jules/config/definition.js +160 -160
- package/src/agents/jules/config/system-prompt.js +182 -182
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +17 -17
- package/src/agents/jules/loop.js +460 -450
- package/src/agents/jules/pulse.js +10 -10
- package/src/agents/jules/stream.js +187 -186
- package/src/agents/jules/swarm/file-scanner.js +74 -74
- package/src/agents/jules/swarm/index.js +11 -11
- package/src/agents/jules/swarm/orchestrator.js +362 -362
- package/src/agents/jules/swarm/pattern-hunter.js +123 -123
- package/src/agents/jules/swarm/sub-agent.js +315 -309
- package/src/agents/jules/tools/aidenid-email.js +189 -189
- package/src/agents/jules/tools/auth-audit.js +1708 -1691
- package/src/agents/jules/tools/dispatch.js +340 -335
- package/src/agents/jules/tools/file-edit.js +2 -2
- package/src/agents/jules/tools/file-read.js +2 -2
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +2 -2
- package/src/agents/jules/tools/grep.js +2 -2
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +2 -2
- package/src/agents/jules/tools/runtime-audit.js +507 -507
- package/src/agents/jules/tools/shell.js +2 -2
- package/src/agents/jules/tools/url-policy.js +100 -100
- package/src/agents/mode.js +113 -0
- package/src/agents/observability/index.js +12 -0
- package/src/agents/observability/tools/alert-audit.js +39 -0
- package/src/agents/observability/tools/base.js +181 -0
- package/src/agents/observability/tools/dashboard-gap.js +42 -0
- package/src/agents/observability/tools/index.js +54 -0
- package/src/agents/observability/tools/log-schema-check.js +74 -0
- package/src/agents/observability/tools/span-coverage.js +74 -0
- package/src/agents/persona-visuals.js +102 -61
- package/src/agents/release/index.js +12 -0
- package/src/agents/release/tools/base.js +181 -0
- package/src/agents/release/tools/changelog-diff.js +86 -0
- package/src/agents/release/tools/feature-flag-audit.js +126 -0
- package/src/agents/release/tools/index.js +61 -0
- package/src/agents/release/tools/rollback-verify.js +129 -0
- package/src/agents/release/tools/semver-check.js +109 -0
- package/src/agents/reliability/index.js +12 -0
- package/src/agents/reliability/tools/backpressure-check.js +129 -0
- package/src/agents/reliability/tools/base.js +181 -0
- package/src/agents/reliability/tools/chaos-probe.js +109 -0
- package/src/agents/reliability/tools/graceful-degradation-check.js +114 -0
- package/src/agents/reliability/tools/health-check-audit.js +111 -0
- package/src/agents/reliability/tools/index.js +87 -0
- package/src/agents/run-persona.js +109 -0
- package/src/agents/security/index.js +12 -0
- package/src/agents/security/tools/authz-audit.js +134 -0
- package/src/agents/security/tools/base.js +190 -0
- package/src/agents/security/tools/crypto-review.js +175 -0
- package/src/agents/security/tools/index.js +97 -0
- package/src/agents/security/tools/sast-scan.js +175 -0
- package/src/agents/security/tools/secrets-scan.js +216 -0
- package/src/agents/shared-tools/dispatch-core.js +320 -315
- package/src/agents/shared-tools/file-edit.js +180 -180
- package/src/agents/shared-tools/file-read.js +100 -100
- package/src/agents/shared-tools/glob.js +168 -168
- package/src/agents/shared-tools/grep.js +228 -228
- package/src/agents/shared-tools/index.js +46 -46
- package/src/agents/shared-tools/path-guards.js +161 -161
- package/src/agents/shared-tools/shell.js +383 -383
- package/src/agents/supply-chain/index.js +12 -0
- package/src/agents/supply-chain/tools/attestation-check.js +42 -0
- package/src/agents/supply-chain/tools/base.js +151 -0
- package/src/agents/supply-chain/tools/index.js +52 -0
- package/src/agents/supply-chain/tools/lockfile-integrity.js +73 -0
- package/src/agents/supply-chain/tools/package-verify.js +56 -0
- package/src/agents/supply-chain/tools/sbom-diff.js +34 -0
- package/src/agents/testing/index.js +12 -0
- package/src/agents/testing/tools/base.js +202 -0
- package/src/agents/testing/tools/coverage-gap.js +144 -0
- package/src/agents/testing/tools/flake-detect.js +125 -0
- package/src/agents/testing/tools/index.js +85 -0
- package/src/agents/testing/tools/mutation-test.js +143 -0
- package/src/agents/testing/tools/snapshot-diff.js +103 -0
- package/src/ai/aidenid.js +1021 -1009
- package/src/ai/client.js +553 -553
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- package/src/ai/proxy.js +137 -137
- package/src/ai/site-store.js +145 -145
- package/src/audit/agents/architecture.js +180 -180
- package/src/audit/agents/compliance.js +179 -179
- package/src/audit/agents/documentation.js +165 -165
- package/src/audit/agents/performance.js +145 -145
- package/src/audit/agents/security.js +215 -215
- package/src/audit/agents/testing.js +172 -172
- package/src/audit/orchestrator.js +557 -557
- package/src/audit/package.js +204 -204
- package/src/audit/registry.js +284 -284
- package/src/audit/replay.js +103 -103
- package/src/auth/gate.js +428 -371
- package/src/auth/http.js +681 -611
- package/src/auth/service.js +1106 -1106
- package/src/auth/session-store.js +813 -813
- package/src/cli.js +257 -252
- package/src/commands/ai/identity-lifecycle.js +1338 -1338
- package/src/commands/ai/provision-governance.js +1272 -1272
- package/src/commands/ai/shared.js +147 -147
- package/src/commands/ai.js +11 -11
- package/src/commands/apply.js +12 -12
- package/src/commands/audit.js +1171 -1166
- package/src/commands/auth.js +419 -419
- package/src/commands/chat.js +184 -191
- package/src/commands/config.js +184 -184
- package/src/commands/cost.js +311 -311
- package/src/commands/daemon/core.js +850 -850
- package/src/commands/daemon/extended.js +1048 -1048
- package/src/commands/daemon/shared.js +213 -213
- package/src/commands/daemon.js +11 -11
- package/src/commands/guide.js +174 -174
- package/src/commands/ingest.js +58 -58
- package/src/commands/init.js +55 -55
- package/src/commands/legacy-args.js +20 -10
- package/src/commands/mcp.js +461 -461
- package/src/commands/omargate.js +63 -29
- package/src/commands/persona.js +65 -20
- package/src/commands/plugin.js +260 -260
- package/src/commands/policy.js +132 -132
- package/src/commands/prompt.js +238 -238
- package/src/commands/review.js +704 -704
- package/src/commands/scan.js +865 -872
- package/src/commands/session.js +1238 -0
- package/src/commands/spec.js +771 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +511 -511
- package/src/config/agent-dictionary.js +182 -182
- package/src/config/io.js +56 -56
- package/src/config/paths.js +18 -18
- package/src/config/schema.js +55 -55
- package/src/config/service.js +184 -184
- package/src/coord/events-log.js +141 -0
- package/src/coord/handshake.js +719 -0
- package/src/coord/index.js +35 -0
- package/src/coord/paths.js +84 -0
- package/src/coord/priority.js +62 -0
- package/src/coord/tarjan.js +157 -0
- package/src/cost/budget.js +235 -235
- package/src/cost/history.js +188 -188
- package/src/cost/tokenizer.js +160 -0
- package/src/cost/tracker.js +232 -171
- package/src/daemon/artifact-lineage.js +896 -534
- package/src/daemon/assignment-ledger.js +1083 -770
- package/src/daemon/ast-drift.js +496 -0
- package/src/daemon/ast-parser-layer.js +258 -258
- package/src/daemon/budget-governor.js +633 -633
- package/src/daemon/callgraph-overlay.js +646 -646
- package/src/daemon/error-worker.js +1209 -626
- package/src/daemon/fix-cycle.js +384 -377
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/ingest-refresh.js +79 -11
- package/src/daemon/jira-lifecycle.js +767 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/pulse.js +327 -327
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/scope-engine.js +1068 -0
- package/src/daemon/watchdog.js +971 -971
- package/src/events/schema.js +190 -0
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +933 -918
- package/src/ingest/ownership.js +380 -0
- package/src/interactive/index.js +97 -97
- package/src/legacy-cli.js +3228 -2994
- package/src/mcp/registry.js +695 -695
- package/src/memory/blackboard.js +301 -301
- package/src/memory/retrieval.js +581 -581
- package/src/orchestrator/kai-chen.js +126 -0
- package/src/plugin/manifest.js +553 -553
- package/src/policy/packs.js +144 -144
- package/src/prompt/generator.js +136 -118
- package/src/review/ai-review.js +672 -679
- package/src/review/compliance-pack.js +389 -0
- package/src/review/investor-dd-config.js +54 -0
- package/src/review/investor-dd-file-loop.js +303 -0
- package/src/review/investor-dd-file-router.js +406 -0
- package/src/review/investor-dd-html-report.js +233 -0
- package/src/review/investor-dd-notification.js +120 -0
- package/src/review/investor-dd-orchestrator.js +405 -0
- package/src/review/investor-dd-persona-runner.js +275 -0
- package/src/review/live-validator.js +253 -0
- package/src/review/local-review.js +1351 -1305
- package/src/review/omargate-interactive.js +68 -68
- package/src/review/omargate-orchestrator.js +492 -300
- package/src/review/persona-prompts.js +484 -296
- package/src/review/reconciliation-rules.js +329 -0
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/reproducibility-chain.js +136 -0
- package/src/review/scan-modes.js +147 -42
- package/src/review/spec-binding.js +487 -487
- package/src/scaffold/generator.js +67 -67
- package/src/scaffold/templates.js +150 -150
- package/src/scan/generator.js +418 -418
- package/src/scan/gh-secrets.js +107 -107
- package/src/session/agent-registry.js +359 -0
- package/src/session/analytics.js +479 -0
- package/src/session/daemon.js +1396 -0
- package/src/session/file-locks.js +666 -0
- package/src/session/paths.js +37 -0
- package/src/session/recap.js +567 -0
- package/src/session/redact.js +82 -0
- package/src/session/runtime-bridge.js +762 -0
- package/src/session/scoring.js +406 -0
- package/src/session/setup-guides.js +304 -0
- package/src/session/store.js +704 -0
- package/src/session/stream.js +333 -0
- package/src/session/sync.js +753 -0
- package/src/session/tasks.js +1054 -0
- package/src/session/templates.js +188 -0
- package/src/spec/generator.js +619 -519
- package/src/spec/regenerate.js +237 -237
- package/src/spec/templates.js +91 -91
- package/src/swarm/dashboard.js +247 -247
- package/src/swarm/factory.js +363 -363
- package/src/swarm/pentest.js +934 -934
- package/src/swarm/registry.js +419 -419
- package/src/swarm/report.js +158 -158
- package/src/swarm/runtime.js +569 -576
- package/src/swarm/scenario-dsl.js +272 -272
- package/src/telemetry/ledger.js +302 -302
- package/src/telemetry/session-tracker.js +234 -234
- package/src/telemetry/sync.js +203 -203
- package/src/ui/command-hints.js +13 -13
- package/src/ui/markdown.js +220 -220
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconciliation ruleset (#investor-dd-29).
|
|
3
|
+
*
|
|
4
|
+
* Cross-references source-level findings emitted by the persona runner
|
|
5
|
+
* with live-web observations captured by the live-validator (devTestBot
|
|
6
|
+
* via AIdenID). Each rule matches a (finding-pattern, live-observation-
|
|
7
|
+
* pattern) pair and assigns a verdict.
|
|
8
|
+
*
|
|
9
|
+
* Verdicts:
|
|
10
|
+
* CONFIRMED — source claim validated by live behavior (or code
|
|
11
|
+
* claim paired with consistent live observation).
|
|
12
|
+
* FALSE_POSITIVE — source flagged an issue, live behavior proves it
|
|
13
|
+
* is not actually broken in production.
|
|
14
|
+
* CONTRADICTORY — source claim and live observation actively
|
|
15
|
+
* disagree (rare; warrants human review).
|
|
16
|
+
* UNVERIFIABLE — no live observation covered this finding, or no
|
|
17
|
+
* rule matched the pair; reported as-is.
|
|
18
|
+
*
|
|
19
|
+
* Each rule is a pure function pair:
|
|
20
|
+
* matchFinding(finding) → bool (source side)
|
|
21
|
+
* matchObservation(observation, finding) → bool (live side, may use
|
|
22
|
+
* the finding context to match to a
|
|
23
|
+
* specific interaction)
|
|
24
|
+
*
|
|
25
|
+
* Rules are intentionally declarative and small: the ruleset grows as
|
|
26
|
+
* new finding classes appear. The engine (PR-28) walks rules in order
|
|
27
|
+
* and picks the first verdict.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} Finding
|
|
32
|
+
* @property {string} [kind] - e.g., "sast.eval", "authz.missing-guard".
|
|
33
|
+
* @property {string} [severity]
|
|
34
|
+
* @property {string} [file]
|
|
35
|
+
* @property {number} [line]
|
|
36
|
+
* @property {string} [personaId]
|
|
37
|
+
* @property {string} [evidence]
|
|
38
|
+
* @property {string} [recommendedFix]
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {object} LiveObservation
|
|
43
|
+
* @property {string} interactionId - UUID for the clicked button/form/nav.
|
|
44
|
+
* @property {string} [elementLabel]
|
|
45
|
+
* @property {string} [sourceFile] - File that declared the element (if static extract worked).
|
|
46
|
+
* @property {number} [statusCodeObserved]
|
|
47
|
+
* @property {string[]} [consoleErrors]
|
|
48
|
+
* @property {Array<object>} [networkErrors]
|
|
49
|
+
* @property {boolean} [navigated]
|
|
50
|
+
* @property {string} [observedBehavior] - Free-form summary from the recorder.
|
|
51
|
+
* @property {object} [payload] - Request/response summary when relevant.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {object} ReconciliationVerdict
|
|
56
|
+
* @property {"CONFIRMED" | "FALSE_POSITIVE" | "CONTRADICTORY" | "UNVERIFIABLE"} verdict
|
|
57
|
+
* @property {string} ruleId
|
|
58
|
+
* @property {number} confidence - 0.0-1.0
|
|
59
|
+
* @property {string} [reason]
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
const RULES = Object.freeze([
|
|
63
|
+
// CONTRADICTORY rules MUST come before CONFIRMED/FALSE_POSITIVE rules
|
|
64
|
+
// for the same finding kind — they capture the ambiguity that the
|
|
65
|
+
// simpler rules would otherwise collapse in the wrong direction.
|
|
66
|
+
{
|
|
67
|
+
id: "R11",
|
|
68
|
+
description:
|
|
69
|
+
"Source says broken, live shows 2xx, but observed text claims failure — human review",
|
|
70
|
+
matchFinding: (f) =>
|
|
71
|
+
f.kind === "frontend.broken-handler" ||
|
|
72
|
+
/broken (click|button|onclick|handler)/i.test(String(f.evidence || "")),
|
|
73
|
+
matchObservation: (obs) =>
|
|
74
|
+
obs &&
|
|
75
|
+
(obs.statusCodeObserved || 0) >= 200 &&
|
|
76
|
+
(obs.statusCodeObserved || 0) < 400 &&
|
|
77
|
+
/(error|failed|denied)/i.test(String(obs.observedBehavior || "")),
|
|
78
|
+
verdict: "CONTRADICTORY",
|
|
79
|
+
confidence: 0.7,
|
|
80
|
+
reason: "Live 2xx response but observed text suggests failure — human review",
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
id: "R01",
|
|
85
|
+
description:
|
|
86
|
+
"UI click finding marked as broken in source, live click succeeded + no console error + expected network call ≥ 200",
|
|
87
|
+
matchFinding: (f) =>
|
|
88
|
+
f.kind === "frontend.broken-handler" ||
|
|
89
|
+
/broken (click|button|onclick|handler)/i.test(String(f.evidence || "")),
|
|
90
|
+
matchObservation: (obs) =>
|
|
91
|
+
obs &&
|
|
92
|
+
(obs.statusCodeObserved || 0) >= 200 &&
|
|
93
|
+
(obs.statusCodeObserved || 0) < 400 &&
|
|
94
|
+
(obs.consoleErrors || []).length === 0,
|
|
95
|
+
verdict: "FALSE_POSITIVE",
|
|
96
|
+
confidence: 0.9,
|
|
97
|
+
reason: "Live interaction succeeded with no console error",
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
{
|
|
101
|
+
id: "R02",
|
|
102
|
+
description:
|
|
103
|
+
"UI click finding and live click actually threw a console error matching the claimed stack",
|
|
104
|
+
matchFinding: (f) =>
|
|
105
|
+
f.kind === "frontend.broken-handler" ||
|
|
106
|
+
/broken (click|button|onclick|handler)/i.test(String(f.evidence || "")),
|
|
107
|
+
matchObservation: (obs, finding) => {
|
|
108
|
+
if (!obs || !(obs.consoleErrors || []).length) return false;
|
|
109
|
+
const evidence = String(finding?.evidence || "").toLowerCase();
|
|
110
|
+
if (!evidence) return false;
|
|
111
|
+
// Bidirectional substring: either finding evidence contains the
|
|
112
|
+
// live console msg, or the live msg contains a chunk of the
|
|
113
|
+
// evidence. Either direction proves the live failure matches the
|
|
114
|
+
// static claim.
|
|
115
|
+
return (obs.consoleErrors || []).some((e) => {
|
|
116
|
+
const msg = String(e.msg || e).toLowerCase();
|
|
117
|
+
if (!msg) return false;
|
|
118
|
+
if (evidence.includes(msg)) return true;
|
|
119
|
+
if (msg.includes(evidence.slice(0, 40))) return true;
|
|
120
|
+
// Token overlap fallback: if 3+ contiguous tokens appear in both
|
|
121
|
+
// strings, call it a match.
|
|
122
|
+
const evidenceTokens = evidence.split(/\s+/).filter((t) => t.length >= 4);
|
|
123
|
+
for (let i = 0; i + 3 <= evidenceTokens.length; i += 1) {
|
|
124
|
+
const window = evidenceTokens.slice(i, i + 3).join(" ");
|
|
125
|
+
if (msg.includes(window)) return true;
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
verdict: "CONFIRMED",
|
|
131
|
+
confidence: 0.85,
|
|
132
|
+
reason: "Live console error matches source evidence",
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
{
|
|
136
|
+
id: "R03",
|
|
137
|
+
description: "Source flagged auth bypass; live unauth request returned 200 with sensitive data",
|
|
138
|
+
matchFinding: (f) =>
|
|
139
|
+
f.kind === "authz.missing-guard" ||
|
|
140
|
+
/auth (bypass|missing)/i.test(String(f.evidence || "")),
|
|
141
|
+
matchObservation: (obs) =>
|
|
142
|
+
obs &&
|
|
143
|
+
obs.statusCodeObserved === 200 &&
|
|
144
|
+
/token|secret|user\s*id|email|ssn|credit/i.test(String(obs.observedBehavior || "")),
|
|
145
|
+
verdict: "CONFIRMED",
|
|
146
|
+
confidence: 0.95,
|
|
147
|
+
reason: "Unauthenticated 200 with sensitive data surfaced",
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
id: "R04",
|
|
152
|
+
description: "Source flagged auth bypass; live unauth request returned 401/403",
|
|
153
|
+
matchFinding: (f) =>
|
|
154
|
+
f.kind === "authz.missing-guard" ||
|
|
155
|
+
/auth (bypass|missing)/i.test(String(f.evidence || "")),
|
|
156
|
+
matchObservation: (obs) =>
|
|
157
|
+
obs && (obs.statusCodeObserved === 401 || obs.statusCodeObserved === 403),
|
|
158
|
+
verdict: "FALSE_POSITIVE",
|
|
159
|
+
confidence: 0.9,
|
|
160
|
+
reason: "Unauthenticated request was properly rejected",
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
{
|
|
164
|
+
id: "R05",
|
|
165
|
+
description: "Source flagged XSS vector; live payload injected and executed",
|
|
166
|
+
matchFinding: (f) => /xss/i.test(String(f.kind || f.evidence || "")),
|
|
167
|
+
matchObservation: (obs) =>
|
|
168
|
+
obs &&
|
|
169
|
+
(obs.observedBehavior || "").toLowerCase().includes("payload executed"),
|
|
170
|
+
verdict: "CONFIRMED",
|
|
171
|
+
confidence: 0.95,
|
|
172
|
+
reason: "Live probe confirmed XSS payload executed",
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
{
|
|
176
|
+
id: "R06",
|
|
177
|
+
description: "Source flagged unbounded fetch; live request returned > 10MB or > 10k rows",
|
|
178
|
+
matchFinding: (f) =>
|
|
179
|
+
/unbounded[\s.-](fetch|query|list)/i.test(String(f.kind || f.evidence || "")),
|
|
180
|
+
matchObservation: (obs) => {
|
|
181
|
+
if (!obs || !obs.payload) return false;
|
|
182
|
+
const bytes = obs.payload.bytes || 0;
|
|
183
|
+
const rows = obs.payload.rows || 0;
|
|
184
|
+
return bytes > 10 * 1024 * 1024 || rows > 10_000;
|
|
185
|
+
},
|
|
186
|
+
verdict: "CONFIRMED",
|
|
187
|
+
confidence: 0.9,
|
|
188
|
+
reason: "Payload size/row count exceeded thresholds",
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
{
|
|
192
|
+
id: "R07",
|
|
193
|
+
description: "Source flagged missing idempotency; double-submit created N > 1 rows",
|
|
194
|
+
matchFinding: (f) =>
|
|
195
|
+
/idempoten|double(-|\s)?submit/i.test(String(f.kind || f.evidence || "")),
|
|
196
|
+
matchObservation: (obs) =>
|
|
197
|
+
obs &&
|
|
198
|
+
typeof obs.payload?.rowsCreatedOnDoubleSubmit === "number" &&
|
|
199
|
+
obs.payload.rowsCreatedOnDoubleSubmit > 1,
|
|
200
|
+
verdict: "CONFIRMED",
|
|
201
|
+
confidence: 0.95,
|
|
202
|
+
reason: "Double-submit resulted in multiple created rows",
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
{
|
|
206
|
+
id: "R08",
|
|
207
|
+
description: "Source flagged CORS too permissive; live response sent permissive Access-Control-Allow-Origin",
|
|
208
|
+
matchFinding: (f) => /cors/i.test(String(f.kind || f.evidence || "")),
|
|
209
|
+
matchObservation: (obs) => {
|
|
210
|
+
const header = obs?.payload?.headers?.["access-control-allow-origin"];
|
|
211
|
+
return header === "*" || header === "null";
|
|
212
|
+
},
|
|
213
|
+
verdict: "CONFIRMED",
|
|
214
|
+
confidence: 0.9,
|
|
215
|
+
reason: "CORS header permissive in live response",
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
{
|
|
219
|
+
id: "R09",
|
|
220
|
+
description:
|
|
221
|
+
"Source flagged element not rendered; live DOM snapshot shows the element and it is interactive",
|
|
222
|
+
matchFinding: (f) =>
|
|
223
|
+
/not rendered|missing element/i.test(String(f.kind || f.evidence || "")),
|
|
224
|
+
matchObservation: (obs) => obs && obs.elementLabel && !obs.navigated === false,
|
|
225
|
+
verdict: "FALSE_POSITIVE",
|
|
226
|
+
confidence: 0.8,
|
|
227
|
+
reason: "Element present and interactive in live run",
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
{
|
|
231
|
+
id: "R10",
|
|
232
|
+
description:
|
|
233
|
+
"Source flagged rate-limit bypass; live 10x-burst returned ≥ 1 non-429 success",
|
|
234
|
+
matchFinding: (f) => /rate[\s-]?limit/i.test(String(f.kind || f.evidence || "")),
|
|
235
|
+
matchObservation: (obs) => {
|
|
236
|
+
const burst = obs?.payload?.burstResults || [];
|
|
237
|
+
return burst.length >= 10 && burst.some((s) => s >= 200 && s < 400);
|
|
238
|
+
},
|
|
239
|
+
verdict: "CONFIRMED",
|
|
240
|
+
confidence: 0.9,
|
|
241
|
+
reason: "Rate-limit was bypassed under burst",
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Evaluate a single (finding, observation) pair against the ruleset.
|
|
248
|
+
* Returns the first matching verdict, or UNVERIFIABLE if no rule fires.
|
|
249
|
+
*
|
|
250
|
+
* @param {Finding} finding
|
|
251
|
+
* @param {LiveObservation | null} observation
|
|
252
|
+
* @returns {ReconciliationVerdict}
|
|
253
|
+
*/
|
|
254
|
+
export function reconcileFindingWithObservation(finding, observation) {
|
|
255
|
+
if (!finding) {
|
|
256
|
+
return {
|
|
257
|
+
verdict: "UNVERIFIABLE",
|
|
258
|
+
ruleId: "R-PREFLIGHT",
|
|
259
|
+
confidence: 0.0,
|
|
260
|
+
reason: "No finding supplied",
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
for (const rule of RULES) {
|
|
264
|
+
try {
|
|
265
|
+
if (!rule.matchFinding(finding)) continue;
|
|
266
|
+
if (!rule.matchObservation(observation, finding)) continue;
|
|
267
|
+
return {
|
|
268
|
+
verdict: rule.verdict,
|
|
269
|
+
ruleId: rule.id,
|
|
270
|
+
confidence: rule.confidence,
|
|
271
|
+
reason: rule.reason,
|
|
272
|
+
};
|
|
273
|
+
} catch {
|
|
274
|
+
// defensive: if a rule throws, continue walking — a misconfigured
|
|
275
|
+
// rule must never block the whole reconciliation pass.
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
verdict: "UNVERIFIABLE",
|
|
281
|
+
ruleId: "R-NO-MATCH",
|
|
282
|
+
confidence: 0.0,
|
|
283
|
+
reason: observation
|
|
284
|
+
? "No ruleset match for this (finding, observation) pair"
|
|
285
|
+
: "No live observation covered this finding",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Apply reconciliation to a batch of findings + observations. Observations
|
|
291
|
+
* are keyed by `finding.file`+`finding.line`+`observation.sourceFile` via
|
|
292
|
+
* the pair function; simplest form is to pair each finding with 0 or 1
|
|
293
|
+
* observation from a lookup the caller provides.
|
|
294
|
+
*
|
|
295
|
+
* @param {Array<Finding>} findings
|
|
296
|
+
* @param {(finding: Finding) => LiveObservation | null} pair
|
|
297
|
+
* @returns {Array<Finding & { reconciliation: ReconciliationVerdict }>}
|
|
298
|
+
*/
|
|
299
|
+
export function reconcileFindings(findings, pair) {
|
|
300
|
+
if (!Array.isArray(findings)) return [];
|
|
301
|
+
const pairFn = typeof pair === "function" ? pair : () => null;
|
|
302
|
+
return findings.map((f) => ({
|
|
303
|
+
...f,
|
|
304
|
+
reconciliation: reconcileFindingWithObservation(f, pairFn(f)),
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Policy helper: decide whether a finding should ship in the final
|
|
310
|
+
* report. FALSE_POSITIVE is suppressed unless the caller forces HITL
|
|
311
|
+
* review; CONTRADICTORY ships with a banner; CONFIRMED and UNVERIFIABLE
|
|
312
|
+
* ship as-is.
|
|
313
|
+
*
|
|
314
|
+
* @param {Finding & { reconciliation: ReconciliationVerdict }} finding
|
|
315
|
+
* @param {object} [options]
|
|
316
|
+
* @param {boolean} [options.keepFalsePositivesForHitl=false]
|
|
317
|
+
* @returns {"include" | "include-with-banner" | "suppress"}
|
|
318
|
+
*/
|
|
319
|
+
export function applyReportPolicy(finding, { keepFalsePositivesForHitl = false } = {}) {
|
|
320
|
+
const verdict = finding?.reconciliation?.verdict;
|
|
321
|
+
if (verdict === "FALSE_POSITIVE") {
|
|
322
|
+
return keepFalsePositivesForHitl ? "include-with-banner" : "suppress";
|
|
323
|
+
}
|
|
324
|
+
if (verdict === "CONTRADICTORY") return "include-with-banner";
|
|
325
|
+
return "include";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export const RECONCILIATION_RULESET_VERSION = "1.0.0";
|
|
329
|
+
export const RECONCILIATION_RULES = RULES;
|