@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,74 +0,0 @@
1
- /**
2
- * `response_headers_conformance` — every response header declared in
3
- * the OpenAPI `responses[<status>].headers` map must be present, and
4
- * (when a schema is given) its value must validate. Schemathesis-style.
5
- *
6
- * For the check to mean anything, the spec has to declare some
7
- * headers; if it declares none, the check skips. Validation is shallow
8
- * here (string presence + simple type/format match) — full ajv
9
- * validation lands alongside ARV-5/ARV-6 once we cache validators per
10
- * (status, header) pair without paying compile cost per response.
11
- */
12
- import type { OpenAPIV3 } from "openapi-types";
13
- import type { Check } from "../types.ts";
14
-
15
- function getDeclaredHeaders(
16
- doc: OpenAPIV3.Document,
17
- path: string,
18
- method: string,
19
- status: number,
20
- ): Record<string, OpenAPIV3.HeaderObject> {
21
- const op = (doc.paths?.[path] as OpenAPIV3.PathItemObject | undefined)
22
- ?.[method.toLowerCase() as OpenAPIV3.HttpMethods] as OpenAPIV3.OperationObject | undefined;
23
- if (!op?.responses) return {};
24
- const exact = op.responses[String(status)] as OpenAPIV3.ResponseObject | undefined;
25
- const wildcard = op.responses[`${Math.floor(status / 100)}XX`] as OpenAPIV3.ResponseObject | undefined;
26
- const fallback = op.responses.default as OpenAPIV3.ResponseObject | undefined;
27
- const branch = exact ?? wildcard ?? fallback;
28
- return (branch?.headers ?? {}) as Record<string, OpenAPIV3.HeaderObject>;
29
- }
30
-
31
- function valueOk(value: string | undefined, header: OpenAPIV3.HeaderObject): boolean {
32
- if (value === undefined) return false;
33
- const schema = header.schema as OpenAPIV3.SchemaObject | undefined;
34
- if (!schema) return true;
35
- if (schema.type === "integer") return /^-?\d+$/.test(value);
36
- if (schema.type === "number") return /^-?\d+(\.\d+)?$/.test(value);
37
- if (schema.type === "boolean") return value === "true" || value === "false";
38
- return true;
39
- }
40
-
41
- export const responseHeadersConformance: Check = {
42
- id: "response_headers_conformance",
43
- severity: "low",
44
- defaultExpected: "All headers declared on the response must be present and shape-valid",
45
- references: [{ id: "OAS3-headerObject" }],
46
- applies: () => true,
47
- run({ case: c, response, doc }) {
48
- if (!doc) return { kind: "skip", reason: "spec doc not available" };
49
- // ARV-183: use originalPath (pre-ARV-40 rename) for spec lookup.
50
- const specPath = c.operation.originalPath ?? c.operation.path;
51
- const declared = getDeclaredHeaders(doc, specPath, c.operation.method, response.status);
52
- const names = Object.keys(declared);
53
- if (names.length === 0) return { kind: "skip", reason: "no declared response headers" };
54
- const issues: string[] = [];
55
- for (const name of names) {
56
- const header = declared[name]!;
57
- const required = header.required === true;
58
- const got = response.headers[name] ?? response.headers[name.toLowerCase()];
59
- if (got === undefined) {
60
- if (required) issues.push(`missing required "${name}"`);
61
- continue;
62
- }
63
- if (!valueOk(got, header)) {
64
- issues.push(`"${name}" value "${got}" doesn't match declared schema`);
65
- }
66
- }
67
- if (issues.length === 0) return { kind: "pass" };
68
- return {
69
- kind: "fail",
70
- message: `Response headers don't conform: ${issues.join("; ")}`,
71
- evidence: { issues, declared: names },
72
- };
73
- },
74
- };
@@ -1,30 +0,0 @@
1
- /**
2
- * `response_schema_conformance` — body must validate against the JSON
3
- * Schema declared on the matched response branch. Reuses the existing
4
- * `runner/schema-validator.ts` so we don't rebuild AJV plumbing
5
- * (ARV-2 AC #3).
6
- */
7
- import type { Check } from "../types.ts";
8
-
9
- export const responseSchemaConformance: Check = {
10
- id: "response_schema_conformance",
11
- severity: "high",
12
- defaultExpected: "Response body must validate against the OpenAPI response schema",
13
- references: [{ id: "OAS3-schemaObject" }],
14
- applies: () => true,
15
- run({ case: c, response, schemaValidator }) {
16
- if (!schemaValidator) return { kind: "skip", reason: "validator unavailable" };
17
- const inspect = schemaValidator.inspect(c.operation.method, c.operation.path, response.status);
18
- if (!inspect.matchedEndpoint) return { kind: "skip", reason: "no spec endpoint matched" };
19
- if (!inspect.hasJsonSchema) return { kind: "skip", reason: "no JSON Schema on this response branch" };
20
- const results = schemaValidator.validate(c.operation.method, c.operation.path, response.status, response.body);
21
- const failed = results.filter((r) => !r.passed);
22
- if (failed.length === 0) return { kind: "pass" };
23
- const messages = failed.slice(0, 5).map((r) => `${r.field}: expected ${JSON.stringify(r.expected)}`);
24
- return {
25
- kind: "fail",
26
- message: `Response body fails schema validation (${failed.length} issue${failed.length === 1 ? "" : "s"})`,
27
- evidence: { issues: messages },
28
- };
29
- },
30
- };
@@ -1,132 +0,0 @@
1
- /**
2
- * `status_code_conformance` — schemathesis-equivalent. Fails when the
3
- * server returns a status code that's not declared in the OpenAPI
4
- * `responses` for this operation (and no `default` is declared either).
5
- *
6
- * Edge: `default` in OpenAPI means "any status not enumerated above" —
7
- * the presence of `default` makes every status code conforming. ARV-2
8
- * AC #6 explicitly tests this case.
9
- *
10
- * Severity matrix (ARV-285, dispatched per finding via outcome.severity
11
- * — see `severityFor` below):
12
- *
13
- * - HIGH: 5xx undeclared (no 5XX wildcard, no default). A server-error
14
- * on an undocumented branch is concrete evidence of an unhandled
15
- * code path — likely a real bug.
16
- * - MEDIUM: 4xx undeclared but at least one other 4xx is declared.
17
- * Partial contract — the API documents *some* client errors but
18
- * missed this one. Concrete gap, actionable for the team.
19
- * - LOW: 4xx undeclared + no declared 4xx at all (minimal spec). The
20
- * spec only lists 2xx; the 4xx is a real response but the spec
21
- * gap could simply be spec-under-documentation, not a real bug.
22
- * - LOW: 2xx/3xx undeclared on negative_data / missing_required_header
23
- * / unsupported_method probes. The negative probe already surfaces
24
- * the acceptance as a finding in its own check — the status gap
25
- * is a secondary signal here (spec hygiene, not a fresh breach).
26
- * - MEDIUM: 2xx/3xx undeclared on positive probes. A conforming happy-path
27
- * response with an undocumented status is a concrete spec gap.
28
- *
29
- * Note: `wildcard.get(5)` / `wildcard.get(4)` already gate at the top —
30
- * those return pass before we reach this dispatch. The 5xx-undeclared case
31
- * only fires when there is no 5XX wildcard and no default.
32
- *
33
- * Per ARV-250's proof-cap principle (no evidence → no high severity):
34
- * the declared `severity: "low"` is the natural fallback / proof-cap
35
- * baseline; evidence strength escalates individual findings via
36
- * `outcome.severity` to override it. The agent re-severitizes from the
37
- * raw evidence.
38
- */
39
- import type { OpenAPIV3 } from "openapi-types";
40
-
41
- import type { Check, CheckOutcome, CaseKind } from "../types.ts";
42
- import type { Severity } from "../../severity/index.ts";
43
-
44
- function declaredStatuses(doc: OpenAPIV3.Document, path: string, method: string):
45
- { codes: Set<number>; hasDefault: boolean; hasWildcard: Map<number, boolean> } {
46
- const codes = new Set<number>();
47
- const hasWildcard = new Map<number, boolean>(); // 2,3,4,5 -> declared as 2XX etc.
48
- let hasDefault = false;
49
- const op = (doc.paths?.[path] as OpenAPIV3.PathItemObject | undefined)
50
- ?.[method.toLowerCase() as OpenAPIV3.HttpMethods] as OpenAPIV3.OperationObject | undefined;
51
- if (!op?.responses) return { codes, hasDefault, hasWildcard };
52
- for (const key of Object.keys(op.responses)) {
53
- if (key === "default") { hasDefault = true; continue; }
54
- // 2XX / 3XX / 4XX / 5XX wildcard keys are valid OpenAPI 3.0 forms.
55
- // Must check before parseInt — parseInt("4XX") === 4 which is finite
56
- // and would otherwise be treated as a literal status code 4.
57
- const m = /^([1-5])XX$/i.exec(key);
58
- if (m) { hasWildcard.set(Number.parseInt(m[1]!, 10), true); continue; }
59
- const n = Number.parseInt(key, 10);
60
- if (Number.isFinite(n)) codes.add(n);
61
- }
62
- return { codes, hasDefault, hasWildcard };
63
- }
64
-
65
- /** Negative case kinds — the probe itself already surfaces acceptance; the
66
- * status-code gap here is a secondary spec-hygiene signal (LOW). */
67
- const NEGATIVE_KINDS: ReadonlySet<CaseKind> = new Set([
68
- "negative_data",
69
- "missing_required_header",
70
- "unsupported_method",
71
- ]);
72
-
73
- /**
74
- * Per-finding severity dispatch (ARV-285).
75
- *
76
- * @param status - actual response status
77
- * @param codes - set of explicitly declared numeric status codes in spec
78
- * @param kind - CheckCase.kind for the current probe
79
- */
80
- function severityFor(status: number, codes: Set<number>, kind: CaseKind): Severity {
81
- if (status >= 500 && status < 600) return "high";
82
- if (status >= 400 && status < 500) {
83
- const hasDeclared4xx = [...codes].some((c) => c >= 400 && c < 500);
84
- return hasDeclared4xx ? "medium" : "low";
85
- }
86
- // 2xx / 3xx
87
- if (NEGATIVE_KINDS.has(kind)) return "low";
88
- return "medium"; // positive case: undocumented success status is a real spec gap
89
- }
90
-
91
- export const statusCodeConformance: Check = {
92
- id: "status_code_conformance",
93
- // ARV-285: declared severity is the *natural* tier (proof-cap baseline
94
- // per ARV-250 — single-signal caps at LOW). Per-finding severity is
95
- // dispatched via `outcome.severity` in `run()` below, so summary
96
- // tables can show HIGH for 5xx and MEDIUM for partial-4xx-contract
97
- // without globally setting the check to HIGH (which masks calibration).
98
- severity: "low",
99
- defaultExpected: "Response status must be declared in the OpenAPI `responses` (or `default`)",
100
- references: [{ id: "OAS3-responsesObject", url: "https://spec.openapis.org/oas/v3.0.3#responses-object" }],
101
- // ARV-180: status-code conformance is a property of the response, not
102
- // of the input. The check must fire on every case kind — including
103
- // negative-data, dropped-header, and unsupported-method probes — so
104
- // an undocumented 5xx/404/422 on bad input surfaces as a finding
105
- // (matches schemathesis V4 default: the check has no input-kind filter).
106
- caseKinds: ["positive", "negative_data", "missing_required_header", "unsupported_method"],
107
- applies: () => true,
108
- run({ case: c, response, doc }): CheckOutcome {
109
- if (!doc) return { kind: "skip", reason: "spec doc not available" };
110
- // ARV-183: ARV-40 path-disambiguation renames {id} → {<resource>_id}
111
- // in EndpointInfo.path. doc.paths keeps the original — use
112
- // originalPath for spec lookup when present.
113
- const specPath = c.operation.originalPath ?? c.operation.path;
114
- const { codes, hasDefault, hasWildcard } = declaredStatuses(doc, specPath, c.operation.method);
115
- if (hasDefault) return { kind: "pass" };
116
- if (codes.has(response.status)) return { kind: "pass" };
117
- if (hasWildcard.get(Math.floor(response.status / 100))) return { kind: "pass" };
118
- // ARV-224: use the actual request method (c.request.method) instead
119
- // of the operation's declared method. For unsupported_method probes
120
- // the request fires POST/PUT/PATCH against a GET-only endpoint and the
121
- // legacy `c.operation.method` would print "for GET" while the
122
- // request_signature (downstream SARIF / triage) shows POST — sending
123
- // operators in the wrong direction.
124
- const reqMethod = c.request.method.toUpperCase();
125
- return {
126
- kind: "fail",
127
- message: `Status ${response.status} not declared in OpenAPI responses for ${reqMethod} ${c.operation.path}`,
128
- evidence: { actual: response.status, declared: [...codes].sort((a, b) => a - b) },
129
- severity: severityFor(response.status, codes, c.kind),
130
- };
131
- },
132
- };
@@ -1,63 +0,0 @@
1
- /**
2
- * `unsupported_method` — the live-running counterpart of the offline
3
- * `method-probe`. Sends a method that isn't declared on the path; the
4
- * server must reject with a 405 (or 401/403/404 fallback). Shares the
5
- * acceptable-status list with the offline probe via `method-shared`
6
- * (ARV-2 AC #4).
7
- */
8
- import type { Check } from "../types.ts";
9
- import {
10
- ACCEPTABLE_UNSUPPORTED_STATUSES,
11
- isPermissibleOptionsResponse,
12
- } from "../../probe/method-shared.ts";
13
-
14
- const ACCEPTABLE = new Set<number>(ACCEPTABLE_UNSUPPORTED_STATUSES);
15
-
16
- export const unsupportedMethod: Check = {
17
- id: "unsupported_method",
18
- severity: "medium",
19
- defaultExpected: "Server must reject undeclared HTTP methods with 405 (or 401/403/404)",
20
- references: [{ id: "RFC-9110-15.5.6", url: "https://www.rfc-editor.org/rfc/rfc9110#name-405-method-not-allowed" }],
21
- caseKinds: ["unsupported_method"],
22
- applies: () => true,
23
- run({ case: c, response, options }) {
24
- const status = response.status;
25
- const undeclared = String(c.meta?.undeclared_method ?? c.request.method);
26
- const strict = options?.strict405 === true;
27
-
28
- // ARV-179: OPTIONS 2xx is legitimate CORS preflight behaviour on
29
- // almost every framework. Treat as pass even in strict mode — the
30
- // m-18 parity replication explicitly ignores OPTIONS responses too.
31
- if (isPermissibleOptionsResponse(undeclared, status)) return { kind: "pass" };
32
-
33
- if (strict) {
34
- if (status === 405) return { kind: "pass" };
35
- return {
36
- kind: "fail",
37
- message: `Undeclared method ${undeclared} returned ${status} — strict mode requires 405`,
38
- evidence: { undeclared_method: undeclared, status, strict_405: true },
39
- };
40
- }
41
-
42
- if (ACCEPTABLE.has(status)) return { kind: "pass" };
43
- if (status >= 200 && status < 300) {
44
- return {
45
- kind: "fail",
46
- message: `Server accepted undeclared method ${undeclared} on ${c.operation.path} (status ${status})`,
47
- evidence: { undeclared_method: undeclared, status },
48
- };
49
- }
50
- if (status >= 500) {
51
- return {
52
- kind: "fail",
53
- message: `Server 5xx'd on undeclared method ${undeclared} for ${c.operation.path} — should be 405`,
54
- evidence: { undeclared_method: undeclared, status },
55
- };
56
- }
57
- return {
58
- kind: "fail",
59
- message: `Undeclared method ${undeclared} returned ${status} — expected 405 (or 401/403/404)`,
60
- evidence: { undeclared_method: undeclared, status },
61
- };
62
- },
63
- };
@@ -1,78 +0,0 @@
1
- /**
2
- * `use_after_free` (m-15 ARV-3) — given a CRUD group with create+read+
3
- * delete, create a resource, delete it, then GET by id. The server
4
- * must respond 404/410. Any 2xx means the resource is still readable
5
- * after a successful DELETE (a classic data-leak / soft-delete bug).
6
- */
7
- import type { CrudStatefulCheck } from "../stateful.ts";
8
- import { extractIdFromCreateResponse, fillPathWithId, fillPathParams, serializeCheckBody, resolveCreateBody } from "./_crud-helpers.ts";
9
-
10
- export const useAfterFree: CrudStatefulCheck = {
11
- id: "use_after_free",
12
- severity: "high",
13
- defaultExpected: "GET on a deleted resource must return 404 or 410",
14
- references: [{ id: "CWE-672" }],
15
- phase: "crud",
16
- applies(g) {
17
- return Boolean(g.create && g.read && g.delete);
18
- },
19
- async run(g, h) {
20
- if (h.bootstrapCleanupFailed) {
21
- return { kind: "skip", reason: "bootstrap-cleanup failed — security checks paused (ARV-3 AC #6)" };
22
- }
23
- const create = g.create!;
24
- const read = g.read!;
25
- const del = g.delete!;
26
- const baseHeaders = { Accept: "application/json", ...h.authHeaders };
27
-
28
- // 1. create — ARV-191: respect form-urlencoded so Stripe-style APIs
29
- // don't silently broken-baseline-skip every CRUD chain.
30
- // ARV-187: prefer LLM-authored seed_body when available.
31
- const seedBody = h.resourceConfigs?.get(g.resource)?.seedBody;
32
- const generated = resolveCreateBody(create, seedBody) ?? {};
33
- const { body: createBody, contentType } = serializeCheckBody(
34
- create,
35
- generated,
36
- h.pathVars,
37
- seedBody?.contentType,
38
- );
39
- const createUrl = `${h.baseUrl.replace(/\/+$/, "")}${fillPathParams(create.path, h.pathVars)}`;
40
- const createResp = await h.send({
41
- method: "POST",
42
- url: createUrl,
43
- headers: { ...baseHeaders, "Content-Type": contentType },
44
- body: createBody,
45
- });
46
- if (createResp.status < 200 || createResp.status >= 300) {
47
- return { kind: "skip", reason: `create returned ${createResp.status} — broken-baseline guard` };
48
- }
49
- const id = extractIdFromCreateResponse(createResp.body_parsed ?? createResp.body, g.idParam);
50
- if (id == null) return { kind: "skip", reason: "could not extract id from create response" };
51
-
52
- // 2. delete
53
- const delResp = await h.send({
54
- method: "DELETE",
55
- url: `${h.baseUrl.replace(/\/+$/, "")}${fillPathWithId(fillPathParams(del.path, h.pathVars), g.idParam, id)}`,
56
- headers: baseHeaders,
57
- });
58
- if (delResp.status < 200 || delResp.status >= 300) {
59
- return { kind: "skip", reason: `delete returned ${delResp.status} — broken-baseline guard` };
60
- }
61
-
62
- // 3. read after delete
63
- const readResp = await h.send({
64
- method: "GET",
65
- url: `${h.baseUrl.replace(/\/+$/, "")}${fillPathWithId(fillPathParams(read.path, h.pathVars), g.idParam, id)}`,
66
- headers: baseHeaders,
67
- });
68
- if (readResp.status === 404 || readResp.status === 410) return { kind: "pass" };
69
- if (readResp.status >= 200 && readResp.status < 300) {
70
- return {
71
- kind: "fail",
72
- message: `GET on resource ${id} after DELETE returned ${readResp.status} — resource still readable`,
73
- evidence: { resource: g.resource, id, get_status_after_delete: readResp.status },
74
- };
75
- }
76
- return { kind: "skip", reason: `read after delete returned ${readResp.status} — neither 404/410 nor 2xx` };
77
- },
78
- };
@@ -1,30 +0,0 @@
1
- /**
2
- * Public entry point for the `core/checks` framework.
3
- *
4
- * Importing this module also triggers `checks/index.ts` which registers
5
- * the built-in checks exactly once.
6
- */
7
- export type {
8
- Check,
9
- CheckCase,
10
- CheckContext,
11
- CheckFinding,
12
- CheckOutcome,
13
- CheckResponse,
14
- CheckRunData,
15
- CheckRunSummary,
16
- Phase,
17
- Severity,
18
- } from "./types.ts";
19
- export { emptySummary } from "./types.ts";
20
- export {
21
- __resetRegistryForTests,
22
- getCheck,
23
- listChecks,
24
- registerCheck,
25
- selectChecks,
26
- type SelectOptions,
27
- type SelectionResult,
28
- } from "./registry.ts";
29
- export { runChecks, type RunChecksOptions, type RunChecksResult } from "./runner.ts";
30
- export { registerBuiltinChecks } from "./checks/index.ts";
@@ -1,82 +0,0 @@
1
- /**
2
- * `--mode positive|negative|all` filter (m-15 ARV-7).
3
- *
4
- * Splits the registered check catalog along the positive-vs-negative
5
- * axis so an agent can run *just* contract verification (positive) or
6
- * *just* malicious-input probes (negative) on the same spec without
7
- * juggling a long `--check` list.
8
- *
9
- * Centralized table — both response-phase `Check`s and stateful
10
- * security checks declare their mode here. Tests snapshot the table so
11
- * adding a new check forces an explicit decision rather than letting it
12
- * silently land in the "all" bucket and surface in both modes.
13
- */
14
- import type { Check } from "./types.ts";
15
- import type { StatefulCheck } from "./stateful.ts";
16
-
17
- export type Mode = "positive" | "negative" | "all";
18
-
19
- export const MODE_BY_CHECK: Record<string, Mode> = {
20
- // Conformance checks fire on every response — both modes.
21
- not_a_server_error: "all",
22
- status_code_conformance: "all",
23
- content_type_conformance: "all",
24
- response_headers_conformance: "all",
25
- response_schema_conformance: "all",
26
- // Probes that *only* make sense as malicious input.
27
- missing_required_header: "negative",
28
- unsupported_method: "negative",
29
- negative_data_rejection: "negative",
30
- // The "happy-path didn't break" sanity probe.
31
- positive_data_acceptance: "positive",
32
- // Security checks (stateful) — they verify a *bad* outcome (auth
33
- // bypass, dangling reads). Not part of the positive contract.
34
- ignored_auth: "negative",
35
- use_after_free: "negative",
36
- // Availability is positive-flavored — it asserts the server *can*
37
- // serve the listed resource. Useful in `--mode positive` runs.
38
- ensure_resource_availability: "all",
39
- // ARV-169 (m-20): cross-call drift is a contract-verification check
40
- // (does GET reflect POST?), not a malicious-input probe. Positive.
41
- cross_call_references: "positive",
42
- // ARV-170 (m-20): idempotency replay verifies a *contract* the server
43
- // advertises (Idempotency-Key honored). Positive.
44
- idempotency_replay: "positive",
45
- // ARV-171 (m-20): pagination invariants verify the cursor contract.
46
- pagination_invariants: "positive",
47
- // ARV-172 (m-20): lifecycle verifies the declared state machine.
48
- lifecycle_transitions: "positive",
49
- // ARV-256 (m-21): open-CORS check sends an attacker Origin and
50
- // inspects response — categorically negative-mode probe.
51
- open_cors_on_sensitive: "negative",
52
- // ARV-256 (m-21): rate-limit headers check inspects 2xx responses
53
- // for advertised rate-limit metadata — runs on the positive path.
54
- rate_limit_headers_absent: "positive",
55
- // ARV-273 (m-22): cursor-fuzzing sends malformed cursor values and
56
- // expects 4xx — classic negative-mode probe.
57
- cursor_boundary_fuzzing: "negative",
58
- };
59
-
60
- export function modeFor(checkId: string): Mode {
61
- return MODE_BY_CHECK[checkId] ?? "all";
62
- }
63
-
64
- export function filterChecksByMode<T extends Check | StatefulCheck>(
65
- checks: T[],
66
- mode: Mode,
67
- ): T[] {
68
- if (mode === "all") return checks;
69
- return checks.filter((c) => {
70
- const cm = modeFor(c.id);
71
- if (cm === "all") return true;
72
- return cm === mode;
73
- });
74
- }
75
-
76
- /** True if a built case (with `mode: "positive" | "negative"`) should be
77
- * sent in the requested run-mode. The runner uses this to short-circuit
78
- * request emission for cases the active mode doesn't care about. */
79
- export function caseMatchesMode(caseMode: "positive" | "negative", runMode: Mode): boolean {
80
- if (runMode === "all") return true;
81
- return runMode === caseMode;
82
- }
@@ -1,68 +0,0 @@
1
- /**
2
- * ARV-11 (m-15): per-check `recommended_action` table.
3
- *
4
- * Each `CheckFinding` carries one closed-enum action so an agent can
5
- * route on it without parsing the free-form `message`. The mapping is
6
- * deterministic — given the check id and (for a couple of fan-out cases)
7
- * the response status, exactly one action comes out.
8
- *
9
- * Keep this file thin and table-driven. The shared enum lives in
10
- * `core/diagnostics/failure-hints.ts` so `db diagnose` (TASK-294) and
11
- * `zond checks run` (ARV-11) can never drift.
12
- */
13
- import type { RecommendedAction } from "../diagnostics/failure-hints.ts";
14
- import { classify, type FindingClass } from "../classifier/recommended-action.ts";
15
-
16
- /** Check IDs the classifier explicitly recognises. Keeping this typed
17
- * guarantees that adding a new check forces a branch in the classifier
18
- * switch (tsc errors out at compile time on a stale `STATIC_TABLE` lookup). */
19
- const CHECK_ID_TO_CLASS: Record<string, FindingClass> = {
20
- status_code_conformance: "check:status_code_conformance",
21
- content_type_conformance: "check:content_type_conformance",
22
- response_headers_conformance: "check:response_headers_conformance",
23
- response_schema_conformance: "check:response_schema_conformance",
24
- not_a_server_error: "check:not_a_server_error",
25
- unsupported_method: "check:unsupported_method",
26
- positive_data_acceptance: "check:positive_data_acceptance",
27
- use_after_free: "check:use_after_free",
28
- ensure_resource_availability: "check:ensure_resource_availability",
29
- negative_data_rejection: "check:negative_data_rejection",
30
- missing_required_header: "check:missing_required_header",
31
- ignored_auth: "check:ignored_auth",
32
- cross_call_references: "check:cross_call_references",
33
- idempotency_replay: "check:idempotency_replay",
34
- pagination_invariants: "check:pagination_invariants",
35
- lifecycle_transitions: "check:lifecycle_transitions",
36
- open_cors_on_sensitive: "check:open_cors_on_sensitive",
37
- rate_limit_headers_absent: "check:rate_limit_headers_absent",
38
- cursor_boundary_fuzzing: "check:cursor_boundary_fuzzing",
39
- network_error: "check:network_error",
40
- };
41
-
42
- /**
43
- * ARV-56: thin wrapper that maps a check id + status to the unified
44
- * classifier. Returns `undefined` when the check id isn't in the table
45
- * — unknown ids are a bug in this map, not a runtime input to coerce.
46
- */
47
- export function recommendForCheck(
48
- checkId: string,
49
- status?: number,
50
- /** ARV-324: true when `.fixture-gaps.yaml` already confirmed this
51
- * operation as a known-empty/inaccessible resource. */
52
- unresolvedFixture?: boolean,
53
- ): RecommendedAction | undefined {
54
- const findingClass = CHECK_ID_TO_CLASS[checkId];
55
- if (!findingClass) return undefined;
56
- return classify({ finding_class: findingClass, status: status ?? null, unresolved_fixture: unresolvedFixture });
57
- }
58
-
59
- /** Test-only export — keeps the unit table authoritative without
60
- * re-listing entries inside the test file. Derived from classifier
61
- * output so the table never drifts from the classifier's switch. */
62
- export const RECOMMENDED_ACTION_TABLE: Readonly<Record<string, RecommendedAction>> = Object.freeze(
63
- Object.fromEntries(
64
- Object.entries(CHECK_ID_TO_CLASS)
65
- .filter(([id]) => id !== "network_error") // dynamic — depends on status
66
- .map(([id, fc]) => [id, classify({ finding_class: fc })!]),
67
- ),
68
- );
@@ -1,78 +0,0 @@
1
- /**
2
- * Global registry of `zond checks`. Built-in checks register themselves
3
- * on first import (see `checks/index.ts`); `selectChecks` resolves the
4
- * `--check` / `--exclude-check` filters from the CLI into the active
5
- * subset for a single run.
6
- */
7
- import type { Check } from "./types.ts";
8
-
9
- const REGISTRY = new Map<string, Check>();
10
-
11
- export function registerCheck(check: Check): void {
12
- if (REGISTRY.has(check.id)) {
13
- throw new Error(`Check "${check.id}" is already registered`);
14
- }
15
- REGISTRY.set(check.id, check);
16
- }
17
-
18
- export function getCheck(id: string): Check | undefined {
19
- return REGISTRY.get(id);
20
- }
21
-
22
- export function listChecks(): Check[] {
23
- return [...REGISTRY.values()].sort((a, b) => a.id.localeCompare(b.id));
24
- }
25
-
26
- export interface SelectOptions {
27
- include?: string[];
28
- exclude?: string[];
29
- }
30
-
31
- export interface SelectionResult {
32
- selected: Check[];
33
- unknown: string[];
34
- }
35
-
36
- /**
37
- * Resolve include/exclude id lists into the active set of checks.
38
- * Unknown ids are returned separately so the caller can surface them as
39
- * envelope warnings (and decide whether to fail or continue).
40
- */
41
- export function selectChecks(opts: SelectOptions = {}): SelectionResult {
42
- const all = listChecks();
43
- const knownIds = new Set(all.map(c => c.id));
44
- const unknown: string[] = [];
45
-
46
- for (const id of [...(opts.include ?? []), ...(opts.exclude ?? [])]) {
47
- if (!knownIds.has(id)) unknown.push(id);
48
- }
49
-
50
- let pool = all;
51
- if (opts.include && opts.include.length > 0) {
52
- const includeSet = new Set(opts.include);
53
- pool = pool.filter(c => includeSet.has(c.id));
54
- }
55
- if (opts.exclude && opts.exclude.length > 0) {
56
- const excludeSet = new Set(opts.exclude);
57
- pool = pool.filter(c => !excludeSet.has(c.id));
58
- }
59
- return { selected: pool, unknown };
60
- }
61
-
62
- /** Test-only escape hatch — wipes the registry between unit tests. */
63
- export function __resetRegistryForTests(): void {
64
- REGISTRY.clear();
65
- }
66
-
67
- /** Test-only snapshot/restore. Use when a test wants a clean slate but the
68
- * rest of the suite still needs the side-effect-registered built-ins
69
- * (otherwise wiping the registry leaks across files because the modules
70
- * that registered them are already cached and won't re-fire on re-import).
71
- */
72
- export function __snapshotRegistryForTests(): () => void {
73
- const saved = new Map(REGISTRY);
74
- return () => {
75
- REGISTRY.clear();
76
- for (const [k, v] of saved) REGISTRY.set(k, v);
77
- };
78
- }