@kirrosh/zond 0.26.1 โ†’ 0.27.0

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 (261) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +22 -21
  3. package/bin/zond.mjs +23 -0
  4. package/package.json +13 -16
  5. package/scripts/npm/postinstall.mjs +76 -0
  6. package/src/CLAUDE.md +0 -112
  7. package/src/bun-types.d.ts +0 -5
  8. package/src/cli/argv.ts +0 -122
  9. package/src/cli/commands/add-api.ts +0 -146
  10. package/src/cli/commands/api/annotate/idempotency.ts +0 -59
  11. package/src/cli/commands/api/annotate/index.ts +0 -880
  12. package/src/cli/commands/api/annotate/lifecycle.ts +0 -74
  13. package/src/cli/commands/api/annotate/overlay.ts +0 -206
  14. package/src/cli/commands/api/annotate/pagination.ts +0 -64
  15. package/src/cli/commands/api/annotate/prompts.ts +0 -220
  16. package/src/cli/commands/api/annotate/readback.ts +0 -58
  17. package/src/cli/commands/api/annotate/resources.ts +0 -91
  18. package/src/cli/commands/api/annotate/seed-bodies.ts +0 -61
  19. package/src/cli/commands/audit.ts +0 -786
  20. package/src/cli/commands/catalog.ts +0 -97
  21. package/src/cli/commands/check.ts +0 -361
  22. package/src/cli/commands/checks.ts +0 -1072
  23. package/src/cli/commands/ci-init.ts +0 -223
  24. package/src/cli/commands/clean.ts +0 -212
  25. package/src/cli/commands/cleanup.ts +0 -236
  26. package/src/cli/commands/completions.ts +0 -192
  27. package/src/cli/commands/coverage.ts +0 -872
  28. package/src/cli/commands/db.ts +0 -611
  29. package/src/cli/commands/describe.ts +0 -120
  30. package/src/cli/commands/discover.ts +0 -1356
  31. package/src/cli/commands/doctor.ts +0 -661
  32. package/src/cli/commands/fixtures.ts +0 -402
  33. package/src/cli/commands/generate.ts +0 -577
  34. package/src/cli/commands/init/agents-md.ts +0 -61
  35. package/src/cli/commands/init/bootstrap.ts +0 -111
  36. package/src/cli/commands/init/index.ts +0 -244
  37. package/src/cli/commands/init/skills.ts +0 -141
  38. package/src/cli/commands/init/templates/agents.md +0 -86
  39. package/src/cli/commands/init/templates/markdown.d.ts +0 -4
  40. package/src/cli/commands/init/templates/skills/warm-up-target.md +0 -122
  41. package/src/cli/commands/init/templates/skills/zond-checks.md +0 -621
  42. package/src/cli/commands/init/templates/skills/zond-seed.md +0 -114
  43. package/src/cli/commands/init/templates/skills/zond-triage.md +0 -272
  44. package/src/cli/commands/init/templates/skills/zond.md +0 -861
  45. package/src/cli/commands/init/templates/zond-config.yml +0 -14
  46. package/src/cli/commands/prepare-fixtures.ts +0 -97
  47. package/src/cli/commands/probe/_seed-bodies.ts +0 -52
  48. package/src/cli/commands/probe/mass-assignment.ts +0 -594
  49. package/src/cli/commands/probe/security.ts +0 -537
  50. package/src/cli/commands/probe/static.ts +0 -255
  51. package/src/cli/commands/probe/webhooks.ts +0 -163
  52. package/src/cli/commands/probe.ts +0 -535
  53. package/src/cli/commands/reference.ts +0 -87
  54. package/src/cli/commands/refresh-api.ts +0 -227
  55. package/src/cli/commands/remove-api.ts +0 -150
  56. package/src/cli/commands/report-bundle.ts +0 -310
  57. package/src/cli/commands/report.ts +0 -241
  58. package/src/cli/commands/request.ts +0 -548
  59. package/src/cli/commands/run.ts +0 -1162
  60. package/src/cli/commands/schema-from-runs.ts +0 -128
  61. package/src/cli/commands/secrets.ts +0 -133
  62. package/src/cli/commands/session.ts +0 -244
  63. package/src/cli/commands/use.ts +0 -74
  64. package/src/cli/index.ts +0 -55
  65. package/src/cli/json-envelope.ts +0 -108
  66. package/src/cli/json-schemas.ts +0 -314
  67. package/src/cli/output.ts +0 -40
  68. package/src/cli/program.ts +0 -219
  69. package/src/cli/resolve.ts +0 -105
  70. package/src/cli/runtime.ts +0 -7
  71. package/src/cli/safe-live.ts +0 -24
  72. package/src/cli/status-filter.ts +0 -114
  73. package/src/cli/util/api-context.ts +0 -85
  74. package/src/cli/version.ts +0 -8
  75. package/src/core/audit/persist.ts +0 -183
  76. package/src/core/checks/budget.ts +0 -59
  77. package/src/core/checks/checks/_crud-helpers.ts +0 -133
  78. package/src/core/checks/checks/_negative_mutator.ts +0 -133
  79. package/src/core/checks/checks/_readback-helpers.ts +0 -133
  80. package/src/core/checks/checks/content_type_conformance.ts +0 -39
  81. package/src/core/checks/checks/cross_call_references.ts +0 -147
  82. package/src/core/checks/checks/cursor_boundary_fuzzing.ts +0 -219
  83. package/src/core/checks/checks/ensure_resource_availability.ts +0 -62
  84. package/src/core/checks/checks/idempotency_replay.ts +0 -242
  85. package/src/core/checks/checks/ignored_auth.ts +0 -254
  86. package/src/core/checks/checks/index.ts +0 -68
  87. package/src/core/checks/checks/lifecycle_transitions.ts +0 -416
  88. package/src/core/checks/checks/missing_required_header.ts +0 -40
  89. package/src/core/checks/checks/negative_data_rejection.ts +0 -148
  90. package/src/core/checks/checks/not_a_server_error.ts +0 -35
  91. package/src/core/checks/checks/open_cors_on_sensitive.ts +0 -160
  92. package/src/core/checks/checks/pagination_invariants.ts +0 -419
  93. package/src/core/checks/checks/positive_data_acceptance.ts +0 -33
  94. package/src/core/checks/checks/rate_limit_headers_absent.ts +0 -77
  95. package/src/core/checks/checks/response_headers_conformance.ts +0 -74
  96. package/src/core/checks/checks/response_schema_conformance.ts +0 -30
  97. package/src/core/checks/checks/status_code_conformance.ts +0 -132
  98. package/src/core/checks/checks/unsupported_method.ts +0 -63
  99. package/src/core/checks/checks/use_after_free.ts +0 -78
  100. package/src/core/checks/index.ts +0 -30
  101. package/src/core/checks/mode.ts +0 -82
  102. package/src/core/checks/recommended-action.ts +0 -68
  103. package/src/core/checks/registry.ts +0 -78
  104. package/src/core/checks/runner.ts +0 -1461
  105. package/src/core/checks/sarif.ts +0 -230
  106. package/src/core/checks/spec-findings.ts +0 -308
  107. package/src/core/checks/stateful.ts +0 -121
  108. package/src/core/checks/types.ts +0 -305
  109. package/src/core/checks/zond-extensions.ts +0 -73
  110. package/src/core/classifier/recommended-action.ts +0 -251
  111. package/src/core/context/current.ts +0 -51
  112. package/src/core/context/session.ts +0 -78
  113. package/src/core/coverage/loader.ts +0 -216
  114. package/src/core/coverage/reasons.ts +0 -300
  115. package/src/core/diagnostics/db-analysis.ts +0 -666
  116. package/src/core/diagnostics/failure-class.ts +0 -140
  117. package/src/core/diagnostics/failure-hints.ts +0 -112
  118. package/src/core/diagnostics/spec-pointer.ts +0 -99
  119. package/src/core/diagnostics/suggested-fixes.ts +0 -155
  120. package/src/core/exporter/case-study/index.ts +0 -270
  121. package/src/core/exporter/curl.ts +0 -40
  122. package/src/core/exporter/exporter.ts +0 -48
  123. package/src/core/exporter/html-report/escape.ts +0 -24
  124. package/src/core/exporter/html-report/index.ts +0 -479
  125. package/src/core/exporter/html-report/script.ts +0 -100
  126. package/src/core/exporter/html-report/styles.ts +0 -408
  127. package/src/core/generator/catalog-builder.ts +0 -179
  128. package/src/core/generator/chunker.ts +0 -78
  129. package/src/core/generator/coverage-phase.ts +0 -0
  130. package/src/core/generator/coverage-scanner.ts +0 -87
  131. package/src/core/generator/data-factory.ts +0 -759
  132. package/src/core/generator/describe.ts +0 -302
  133. package/src/core/generator/endpoint-warnings.ts +0 -52
  134. package/src/core/generator/fixtures-builder.ts +0 -332
  135. package/src/core/generator/index.ts +0 -14
  136. package/src/core/generator/openapi-reader.ts +0 -297
  137. package/src/core/generator/path-param-disambig.ts +0 -140
  138. package/src/core/generator/resources-builder.ts +0 -898
  139. package/src/core/generator/schema-utils.ts +0 -112
  140. package/src/core/generator/serializer.ts +0 -343
  141. package/src/core/generator/suite-generator.ts +0 -1301
  142. package/src/core/generator/types.ts +0 -63
  143. package/src/core/identity/identity-file.ts +0 -0
  144. package/src/core/lint/affects.ts +0 -28
  145. package/src/core/lint/config.ts +0 -96
  146. package/src/core/lint/format.ts +0 -42
  147. package/src/core/lint/index.ts +0 -94
  148. package/src/core/lint/reporter.ts +0 -128
  149. package/src/core/lint/rules/consistency.ts +0 -158
  150. package/src/core/lint/rules/heuristics.ts +0 -97
  151. package/src/core/lint/rules/strictness.ts +0 -109
  152. package/src/core/lint/types.ts +0 -96
  153. package/src/core/lint/walker.ts +0 -248
  154. package/src/core/meta/meta-store.ts +0 -11
  155. package/src/core/output/README.md +0 -73
  156. package/src/core/output/index.ts +0 -13
  157. package/src/core/output/run.ts +0 -91
  158. package/src/core/output/types.ts +0 -122
  159. package/src/core/parser/dynamic-values.ts +0 -160
  160. package/src/core/parser/env-interpolation.ts +0 -104
  161. package/src/core/parser/filter.ts +0 -97
  162. package/src/core/parser/schema.ts +0 -359
  163. package/src/core/parser/types.ts +0 -117
  164. package/src/core/parser/variables.ts +0 -0
  165. package/src/core/parser/yaml-parser.ts +0 -181
  166. package/src/core/probe/bootstrap.ts +0 -34
  167. package/src/core/probe/dry-run-envelope.ts +0 -61
  168. package/src/core/probe/mass-assignment/classify.ts +0 -175
  169. package/src/core/probe/mass-assignment/cleanup.ts +0 -52
  170. package/src/core/probe/mass-assignment/digest.ts +0 -114
  171. package/src/core/probe/mass-assignment/orchestrator.ts +0 -459
  172. package/src/core/probe/mass-assignment/regression.ts +0 -141
  173. package/src/core/probe/mass-assignment/suspects.ts +0 -92
  174. package/src/core/probe/mass-assignment/types.ts +0 -135
  175. package/src/core/probe/mass-assignment-probe-class.ts +0 -198
  176. package/src/core/probe/mass-assignment-probe.ts +0 -27
  177. package/src/core/probe/mass-assignment-template.ts +0 -240
  178. package/src/core/probe/method-probe.ts +0 -164
  179. package/src/core/probe/method-shared.ts +0 -69
  180. package/src/core/probe/negative-probe.ts +0 -691
  181. package/src/core/probe/orphan-tracker.ts +0 -188
  182. package/src/core/probe/path-discovery.ts +0 -439
  183. package/src/core/probe/probe-harness.ts +0 -119
  184. package/src/core/probe/registry.ts +0 -89
  185. package/src/core/probe/runner.ts +0 -136
  186. package/src/core/probe/security/baseline.ts +0 -174
  187. package/src/core/probe/security/classify.ts +0 -341
  188. package/src/core/probe/security/cleanup.ts +0 -125
  189. package/src/core/probe/security/detectors.ts +0 -71
  190. package/src/core/probe/security/digest.ts +0 -104
  191. package/src/core/probe/security/orchestrator.ts +0 -398
  192. package/src/core/probe/security/regression.ts +0 -103
  193. package/src/core/probe/security/types.ts +0 -151
  194. package/src/core/probe/security-probe-class.ts +0 -207
  195. package/src/core/probe/security-probe.ts +0 -32
  196. package/src/core/probe/shared.ts +0 -531
  197. package/src/core/probe/static-probe-class.ts +0 -125
  198. package/src/core/probe/types.ts +0 -165
  199. package/src/core/probe/verdict-aggregator.ts +0 -33
  200. package/src/core/probe/webhooks-probe.ts +0 -282
  201. package/src/core/reporter/console.ts +0 -240
  202. package/src/core/reporter/index.ts +0 -22
  203. package/src/core/reporter/json.ts +0 -22
  204. package/src/core/reporter/junit.ts +0 -93
  205. package/src/core/reporter/ndjson.ts +0 -37
  206. package/src/core/reporter/types.ts +0 -15
  207. package/src/core/runner/assertions.ts +0 -383
  208. package/src/core/runner/async-pool.ts +0 -108
  209. package/src/core/runner/auth-path.ts +0 -8
  210. package/src/core/runner/ci-context.ts +0 -72
  211. package/src/core/runner/executor.ts +0 -652
  212. package/src/core/runner/expr-eval.ts +0 -41
  213. package/src/core/runner/form-encode.ts +0 -41
  214. package/src/core/runner/http-client.ts +0 -224
  215. package/src/core/runner/learn-drift.ts +0 -293
  216. package/src/core/runner/preflight-vars.ts +0 -153
  217. package/src/core/runner/progress-tracker.ts +0 -73
  218. package/src/core/runner/rate-limiter.ts +0 -185
  219. package/src/core/runner/run-kind.ts +0 -45
  220. package/src/core/runner/schema-validator.ts +0 -308
  221. package/src/core/runner/send-request.ts +0 -232
  222. package/src/core/runner/transforms.ts +0 -65
  223. package/src/core/runner/types.ts +0 -94
  224. package/src/core/secrets/registry.ts +0 -164
  225. package/src/core/secrets/secrets-file.ts +0 -115
  226. package/src/core/selectors/operation-filter.ts +0 -144
  227. package/src/core/setup-api.ts +0 -590
  228. package/src/core/severity/category.ts +0 -94
  229. package/src/core/severity/index.ts +0 -58
  230. package/src/core/spec/infer-schema.ts +0 -102
  231. package/src/core/spec/layers.ts +0 -154
  232. package/src/core/spec/merge-specs.ts +0 -156
  233. package/src/core/spec/schema-from-runs.ts +0 -117
  234. package/src/core/spec/schema-overlay.ts +0 -130
  235. package/src/core/util/ajv.ts +0 -13
  236. package/src/core/util/format-eta.ts +0 -21
  237. package/src/core/util/headers.ts +0 -9
  238. package/src/core/util/url.ts +0 -24
  239. package/src/core/utils.ts +0 -13
  240. package/src/core/workspace/config.ts +0 -129
  241. package/src/core/workspace/fixture-gap-report.ts +0 -84
  242. package/src/core/workspace/fixture-gaps.ts +0 -71
  243. package/src/core/workspace/manifest.ts +0 -283
  244. package/src/core/workspace/output-rotation.ts +0 -62
  245. package/src/core/workspace/root.ts +0 -96
  246. package/src/core/workspace/triage-path.ts +0 -87
  247. package/src/db/lint-runs.ts +0 -47
  248. package/src/db/migrate.ts +0 -128
  249. package/src/db/migrations/0001_run_kind.sql +0 -25
  250. package/src/db/migrations/0002_run_kind_request.sql +0 -59
  251. package/src/db/migrations/sql.d.ts +0 -4
  252. package/src/db/queries/collections.ts +0 -133
  253. package/src/db/queries/coverage.ts +0 -9
  254. package/src/db/queries/dashboard.ts +0 -59
  255. package/src/db/queries/results.ts +0 -216
  256. package/src/db/queries/runs.ts +0 -289
  257. package/src/db/queries/sessions.ts +0 -42
  258. package/src/db/queries/settings.ts +0 -28
  259. package/src/db/queries/types.ts +0 -172
  260. package/src/db/queries.ts +0 -75
  261. package/src/db/schema.ts +0 -298
@@ -1,61 +0,0 @@
1
- /**
2
- * Dry-run envelope helpers (m-17 / ARV-50).
3
- *
4
- * `--dry-run` answers "what would I attack" โ€” severity is undefined
5
- * because nothing was classified. Both probe-security and
6
- * probe-mass-assignment write this shape into `data` instead of the
7
- * legacy severity-bucket structure that conflated planned attacks
8
- * with skipped endpoints (F1-15).
9
- */
10
- import type { EndpointPlan } from "./types.ts";
11
-
12
- export interface DryRunSummary {
13
- totalEndpoints: number;
14
- planned: number;
15
- skipped: number;
16
- }
17
-
18
- export interface DryRunEnvelopeData {
19
- endpoints: EndpointPlan[];
20
- summary: DryRunSummary;
21
- }
22
-
23
- export function summarizeDryRun(plans: EndpointPlan[]): DryRunEnvelopeData {
24
- let planned = 0;
25
- let skipped = 0;
26
- for (const p of plans) {
27
- if (p.planned) planned++;
28
- else skipped++;
29
- }
30
- return {
31
- endpoints: plans,
32
- summary: {
33
- totalEndpoints: plans.length,
34
- planned,
35
- skipped,
36
- },
37
- };
38
- }
39
-
40
- /** Render a short human digest for the dry-run plan (used by non-json
41
- * output). One line per endpoint, planned/skipped counts at the end. */
42
- export function formatDryRunDigest(plans: EndpointPlan[]): string {
43
- const lines: string[] = [];
44
- for (const p of plans) {
45
- if (p.planned) {
46
- const fields = p.fields_planned.length > 0 ? ` fields=${p.fields_planned.join(",")}` : "";
47
- const cls = p.classes_planned.length > 0 ? ` classes=${p.classes_planned.join(",")}` : "";
48
- lines.push(` + ${p.method} ${p.path}${cls}${fields}`);
49
- } else {
50
- lines.push(` - ${p.method} ${p.path} (skipped: ${p.skip_reason ?? "unknown"})`);
51
- }
52
- }
53
- const summary = summarizeDryRun(plans).summary;
54
- lines.push("");
55
- lines.push(`Plan: ${summary.planned} planned ยท ${summary.skipped} skipped ยท ${summary.totalEndpoints} total`);
56
- // ARV-309: the plan above lists what *would* be attacked โ€” no traffic was
57
- // sent. Without this line a reader can't tell "ran, found nothing" from
58
- // "never fired" (the plan reads like a findings list). State it explicitly.
59
- lines.push("Dry-run: mutation probes NOT executed โ€” no requests sent. Re-run without --dry-run to attack live.");
60
- return lines.join("\n");
61
- }
@@ -1,175 +0,0 @@
1
- import { classify as classifyRecommendedAction } from "../../classifier/recommended-action.ts";
2
- import type { EndpointInfo } from "../../generator/types.ts";
3
- import type { EndpointVerdict } from "./types.ts";
4
-
5
- /** Lower-cased anchored fragments for the SaaS-flavoured 403 wordings we
6
- * encounter in the wild (paid plan / role-scope / feature-flag gates).
7
- * Each entry is one independent signal โ€” a body matching any one of them
8
- * is treated as subscription-gated. Formerly the anti-FP registry rule
9
- * `subscription-gated/paid-plan-403` (ARV-125); now inlined as a plain
10
- * evidence signal on the baseline summary โ€” NOT a suppressor. */
11
- const SUBSCRIPTION_GATED_PATTERNS: RegExp[] = [
12
- /\bpaid plan\b/i,
13
- /\bsubscription (?:required|needed)\b/i,
14
- /\bnot (?:available|enabled) (?:on|for) your\b/i,
15
- /\bplan (?:does not include|doesn['']?t include)\b/i,
16
- /\brequires? (?:the )?[\w:-]+ scope\b/i,
17
- /\bmissing (?:the )?[\w:-]+ scope\b/i,
18
- /\bfeature (?:is )?(?:not enabled|disabled|not available)\b/i,
19
- /\binsufficient (?:permissions?|scope)\b/i,
20
- ];
21
-
22
- /** Reason text surfaced on the baseline summary when a 403 body names a
23
- * subscription/scope gate โ€” signals to the triage agent that fixture
24
- * edits won't help. */
25
- const SUBSCRIPTION_GATED_REASON =
26
- "endpoint is env/subscription-gated (paid plan, role/scope, feature flag); " +
27
- "not a fixture issue โ€” wontfix unless scope changes";
28
-
29
- function matchesSubscriptionGated(message: string): boolean {
30
- for (const re of SUBSCRIPTION_GATED_PATTERNS) {
31
- if (re.test(message)) return true;
32
- }
33
- return false;
34
- }
35
-
36
- /** ARV-56: route through the single classifier instead of carrying the
37
- * severityโ†’action switch inline. */
38
- export function stampRecommendedAction(verdict: EndpointVerdict): void {
39
- const action = classifyRecommendedAction({
40
- finding_class: "probe:mass_assignment",
41
- severity: verdict.severity as Parameters<typeof classifyRecommendedAction>[0]["severity"],
42
- });
43
- if (action) verdict.recommended_action = action;
44
- }
45
-
46
- /**
47
- * Build a one-line summary for INCONCLUSIVE-baseline verdicts. We surface
48
- * the server's error code/name when present so the user can immediately
49
- * see *which* FK / scope / fixture failed and fix it before re-probing.
50
- */
51
- export function inconclusiveBaselineSummary(
52
- status: number,
53
- body: unknown,
54
- bodyFkMisses?: Array<{ field: string; reason: string }>,
55
- ): string {
56
- const hint = extractBaselineHint(body);
57
- const base = `baseline body invalid โ€” server returned ${status}`;
58
- // ARV-104 (F9): when status is 403 and the response body names a
59
- // subscription/scope gate (paid plan, feature flag, role/scope
60
- // insufficient), the right answer isn't "fix fixture" โ€” there's
61
- // nothing to fix. Surface a wontfix reason on the summary as raw
62
- // evidence for the triage agent (this is a hint, NOT a suppressor).
63
- const gated = status === 403 && hint !== undefined && matchesSubscriptionGated(hint);
64
- const tail = gated
65
- ? ` โ€” ${SUBSCRIPTION_GATED_REASON}`
66
- : " โ€” fix fixture / FK value / path-params and re-probe";
67
- // TASK-137: if body-FK auto-discovery couldn't fill required FK fields, name
68
- // them in the summary so the user knows exactly what to add to env (or
69
- // why discover-fk missed โ€” e.g. nested list endpoint, 403 from scope).
70
- let fkClause = "";
71
- if (bodyFkMisses && bodyFkMisses.length > 0) {
72
- const names = bodyFkMisses.map(m => m.field).join(", ");
73
- fkClause = ` โ€” unresolved body FKs: ${names}`;
74
- }
75
- return hint
76
- ? `${base} (${hint})${fkClause}${tail}`
77
- : `${base}${fkClause}${tail}`;
78
- }
79
-
80
- /** ARV-104 (F9): quick yes/no on whether a 403 body names a
81
- * subscription/scope gate. Public predicate for callers (and tests)
82
- * that want the signal without composing a summary string. */
83
- export const isSubscriptionGated = matchesSubscriptionGated;
84
-
85
- function extractBaselineHint(body: unknown): string | undefined {
86
- if (typeof body === "string") {
87
- const trimmed = body.trim();
88
- if (trimmed.length === 0) return undefined;
89
- return trimmed.length > 120 ? `${trimmed.slice(0, 120)}โ€ฆ` : trimmed;
90
- }
91
- if (typeof body !== "object" || body === null) return undefined;
92
- const obj = body as Record<string, unknown>;
93
- // Common error-envelope fields across SaaS APIs.
94
- const candidates = [
95
- obj.message,
96
- obj.error,
97
- (obj.error as Record<string, unknown> | undefined)?.message,
98
- obj.detail,
99
- obj.title,
100
- obj.name,
101
- obj.code,
102
- ];
103
- for (const c of candidates) {
104
- if (typeof c === "string" && c.length > 0) {
105
- return c.length > 120 ? `${c.slice(0, 120)}โ€ฆ` : c;
106
- }
107
- }
108
- return undefined;
109
- }
110
-
111
- export function needsFollowUp(verdict: EndpointVerdict): boolean {
112
- return verdict.fields.some(f => f.outcome === "absent" || f.outcome === "unknown");
113
- }
114
-
115
- export function classifyFromBody(
116
- verdict: EndpointVerdict,
117
- body: Record<string, unknown> | undefined,
118
- fromGet = false,
119
- ): void {
120
- if (!body) return;
121
- for (const field of verdict.fields) {
122
- // Once a field is decisively classified (applied/echoed-overwritten),
123
- // don't downgrade. But "absent" on POST may still flip to applied/ignored
124
- // after GET โ€” so only re-check those.
125
- if (field.outcome === "applied" || field.outcome === "echoed-overwritten") continue;
126
- if (!(field.field in body)) {
127
- // GET also missing โ†’ ignored. POST missing โ†’ keep "absent" so we GET later.
128
- field.outcome = fromGet ? "ignored" : "absent";
129
- continue;
130
- }
131
- const observed = body[field.field];
132
- field.observed = observed;
133
- if (Bun.deepEquals(observed, field.injected)) {
134
- field.outcome = "applied";
135
- } else if (fromGet) {
136
- field.outcome = "ignored";
137
- } else {
138
- field.outcome = "echoed-overwritten";
139
- }
140
- }
141
- }
142
-
143
- export function findIdParam(ep: EndpointInfo): string {
144
- const m = ep.path.match(/\{([^}]+)\}/);
145
- return m ? m[1]! : "id";
146
- }
147
-
148
- export function finaliseSeverity(v: EndpointVerdict, strict: boolean): void {
149
- const applied = v.fields.filter(f => f.outcome === "applied");
150
- const absent = v.fields.filter(f => f.outcome === "absent");
151
-
152
- if (applied.length > 0) {
153
- v.severity = "high";
154
- v.summary = `accepted-and-applied: ${applied.map(f => f.field).join(", ")}`;
155
- return;
156
- }
157
- if (absent.length > 0) {
158
- // ARV-252: absent-but-unverifiable carries single_signal proof.
159
- // Surfaced as INFO and only shown under --verbose so the report
160
- // stays clean; the verdict still travels through the JSON envelope
161
- // for agents that want to triage it explicitly.
162
- v.severity = "info";
163
- v.summary = `inconclusive โ€” could not verify via follow-up GET (${absent.map(f => f.field).join(", ")})`;
164
- return;
165
- }
166
- // ARV-252: silently-ignored = correct framework behaviour (Rails
167
- // strong params / FastAPI extra=ignore). Severity stays INFO so it
168
- // never gates CI, AND the CLI display layer suppresses it entirely
169
- // (even under --verbose). Reports must not be noise-floored by
170
- // correct behaviour. Verdicts still travel through the JSON envelope
171
- // for agents that explicitly want to inspect them.
172
- v.severity = "info";
173
- const status = v.response?.status ?? 0;
174
- v.summary = `accepted ${status} but extras silently ignored${strict ? " (despite additionalProperties:false โ€” server should reject)" : ""}`;
175
- }
@@ -1,52 +0,0 @@
1
- import type { EndpointInfo, SecuritySchemeInfo } from "../../generator/types.ts";
2
- import { executeRequest } from "../../runner/http-client.ts";
3
- import {
4
- captureFieldFor,
5
- findDeleteCounterpart,
6
- liveAuthHeaders,
7
- } from "../shared.ts";
8
- import { buildProbeUrl } from "../probe-harness.ts";
9
- import { findIdParam } from "./classify.ts";
10
-
11
- /**
12
- * Best-effort DELETE on the baseline (no-extras) probe so the injected
13
- * POST doesn't trip a unique-constraint and we don't leak resources.
14
- */
15
- export async function tryCleanupBaseline(
16
- ep: EndpointInfo,
17
- allEndpoints: EndpointInfo[],
18
- schemes: SecuritySchemeInfo[],
19
- vars: Record<string, string>,
20
- baselineBody: unknown,
21
- opts: { timeoutMs?: number },
22
- ): Promise<void> {
23
- const body =
24
- typeof baselineBody === "object" && baselineBody !== null
25
- ? (baselineBody as Record<string, unknown>)
26
- : undefined;
27
- if (!body) return;
28
- const idField = captureFieldFor(ep);
29
- const id = body[idField];
30
- if (id === undefined) return;
31
- const delEp = findDeleteCounterpart(ep, allEndpoints);
32
- if (!delEp) return;
33
- const delVars = { ...vars, [findIdParam(delEp)]: String(id), id: String(id) };
34
- const delUrl = buildProbeUrl(delEp, delVars);
35
- if (delUrl.unresolved.length > 0) return;
36
- try {
37
- await executeRequest(
38
- {
39
- method: "DELETE",
40
- url: delUrl.url,
41
- headers: {
42
- accept: "application/json",
43
- ...liveAuthHeaders(delEp, schemes, vars),
44
- },
45
- },
46
- { timeout: opts.timeoutMs ?? 30000, retries: 0 },
47
- );
48
- } catch {
49
- // best-effort โ€” if cleanup fails we'll leak a baseline resource, but
50
- // that's a deployment problem, not a probe bug.
51
- }
52
- }
@@ -1,114 +0,0 @@
1
- import { SUSPECTED_FIELDS } from "./suspects.ts";
2
- import type {
3
- EndpointVerdict,
4
- MassAssignmentResult,
5
- Severity,
6
- } from "./types.ts";
7
-
8
- const SEVERITY_ORDER: Severity[] = [
9
- "high",
10
- "inconclusive-baseline",
11
- "inconclusive-5xx",
12
- "medium",
13
- "low",
14
- "info",
15
- "ok",
16
- "skipped",
17
- ];
18
-
19
- const SEVERITY_HEADER: Record<Severity, string> = {
20
- high: "๐Ÿšจ HIGH โ€” privilege escalation candidates",
21
- "inconclusive-baseline": "โš ๏ธ INCONCLUSIVE โ€” baseline body invalid (fix fixture / FK / scope and re-probe)",
22
- "inconclusive-5xx": "โš ๏ธ INCONCLUSIVE โ€” baseline 5xx (endpoint crashes โ€” likely duplicate of validation-probe)",
23
- medium: "โš ๏ธ MEDIUM โ€” inconclusive (no follow-up GET available)",
24
- low: "โ„น๏ธ LOW โ€” inconclusive (single-signal, follow-up GET unavailable)",
25
- info: "ยท INFO โ€” accepted-and-ignored (correct framework behaviour, often ineligible to report)",
26
- ok: "โœ… OK โ€” rejected 4xx (best behaviour)",
27
- skipped: "โญ๏ธ SKIPPED",
28
- };
29
-
30
- export function formatDigestMarkdown(
31
- result: MassAssignmentResult,
32
- specPath: string,
33
- ): string {
34
- const lines: string[] = [];
35
- lines.push(`# Mass-assignment probe digest`);
36
- lines.push("");
37
- lines.push(`**Spec:** \`${specPath}\``);
38
- lines.push(`**Endpoints probed:** ${result.specProbed} of ${result.totalEndpoints} mutating endpoints`);
39
- lines.push("");
40
- lines.push(`**Suspected fields tested:** ${Object.keys(SUSPECTED_FIELDS).join(", ")}`);
41
- lines.push("");
42
-
43
- const buckets = groupBySeverity(result.verdicts);
44
- for (const sev of SEVERITY_ORDER) {
45
- const items = buckets[sev];
46
- if (!items || items.length === 0) continue;
47
- lines.push(`## ${SEVERITY_HEADER[sev]} (${items.length})`);
48
- lines.push("");
49
- for (const v of items) {
50
- lines.push(`### ${v.method} ${v.path}`);
51
- lines.push("");
52
- if (v.severity === "skipped") {
53
- lines.push(`- Skipped: ${v.skipReason ?? v.summary}`);
54
- lines.push("");
55
- continue;
56
- }
57
- lines.push(`- ${v.summary}`);
58
- lines.push(`- Injected: ${v.request.injectedFields.map(n => `\`${n}\``).join(", ")}`);
59
- if (v.baseline) {
60
- lines.push(`- Baseline (no extras): ${v.baseline.status}`);
61
- }
62
- if (v.response) {
63
- lines.push(`- With extras: ${v.response.status}`);
64
- }
65
- if (v.followUpGet) {
66
- lines.push(`- Follow-up GET โ†’ ${v.followUpGet.status}`);
67
- }
68
- const interesting = v.fields.filter(f => f.outcome !== "ignored" && f.outcome !== "absent");
69
- if (interesting.length > 0) {
70
- lines.push(`- Per-field outcomes:`);
71
- for (const f of interesting) {
72
- const obs = f.observed === undefined ? "n/a" : JSON.stringify(f.observed);
73
- lines.push(` - \`${f.field}\` โ†’ **${f.outcome}** (injected ${JSON.stringify(f.injected)}, observed ${obs})`);
74
- }
75
- }
76
- if (v.cleanup) {
77
- if (v.cleanup.attempted) {
78
- lines.push(`- Cleanup DELETE: ${v.cleanup.status ?? "errored"}${v.cleanup.error ? ` โ€” ${v.cleanup.error}` : ""}`);
79
- } else {
80
- lines.push(`- Cleanup skipped: ${v.cleanup.error ?? "unknown"}`);
81
- }
82
- }
83
- if (v.notes && v.notes.length > 0) {
84
- for (const n of v.notes) lines.push(`- Note: ${n}`);
85
- }
86
- if (v.severity === "high") {
87
- lines.push(`- **Action:** treat as P0 โ€” server should reject or strip these fields.`);
88
- }
89
- if (v.severity === "inconclusive-baseline") {
90
- lines.push(
91
- `- **Action:** the baseline POST itself failed โ€” set the right fixture / FK / path-params in your env (e.g. \`domain_id\`, \`account_id\`) and re-run.`,
92
- );
93
- }
94
- if (v.severity === "inconclusive-5xx") {
95
- lines.push(
96
- `- **Action:** baseline crashed with 5xx โ€” fix the underlying server bug (validation-probe likely reported it for the same endpoint) before mass-assignment can be observed here.`,
97
- );
98
- }
99
- lines.push("");
100
- }
101
- }
102
-
103
- if (result.warnings.length > 0) {
104
- lines.push(`## Warnings`);
105
- lines.push("");
106
- for (const w of result.warnings) lines.push(`- ${w}`);
107
- lines.push("");
108
- }
109
- return lines.join("\n");
110
- }
111
-
112
- function groupBySeverity(verdicts: EndpointVerdict[]): Partial<Record<Severity, EndpointVerdict[]>> {
113
- return Object.groupBy(verdicts, (v) => v.severity);
114
- }