@kirrosh/zond 0.22.0 → 0.23.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 (256) hide show
  1. package/CHANGELOG.md +648 -0
  2. package/README.md +58 -6
  3. package/package.json +9 -6
  4. package/src/cli/argv.ts +122 -0
  5. package/src/cli/commands/add-api.ts +134 -0
  6. package/src/cli/commands/api/annotate/idempotency.ts +59 -0
  7. package/src/cli/commands/api/annotate/index.ts +525 -0
  8. package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
  9. package/src/cli/commands/api/annotate/overlay.ts +206 -0
  10. package/src/cli/commands/api/annotate/pagination.ts +60 -0
  11. package/src/cli/commands/api/annotate/prompts.ts +183 -0
  12. package/src/cli/commands/api/annotate/readback.ts +58 -0
  13. package/src/cli/commands/api/annotate/resources.ts +91 -0
  14. package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
  15. package/src/cli/commands/audit.ts +480 -0
  16. package/src/cli/commands/bootstrap.ts +710 -0
  17. package/src/cli/commands/catalog.ts +35 -0
  18. package/src/cli/commands/check.ts +348 -0
  19. package/src/cli/commands/checks.ts +756 -0
  20. package/src/cli/commands/ci-init.ts +43 -0
  21. package/src/cli/commands/clean.ts +212 -0
  22. package/src/cli/commands/cleanup.ts +262 -0
  23. package/src/cli/commands/completions.ts +16 -0
  24. package/src/cli/commands/coverage.ts +605 -132
  25. package/src/cli/commands/db.ts +178 -7
  26. package/src/cli/commands/describe.ts +37 -2
  27. package/src/cli/commands/discover.ts +1236 -0
  28. package/src/cli/commands/doctor.ts +607 -0
  29. package/src/cli/commands/fixtures.ts +402 -0
  30. package/src/cli/commands/generate.ts +420 -46
  31. package/src/cli/commands/init/bootstrap.ts +30 -1
  32. package/src/cli/commands/{init.ts → init/index.ts} +99 -5
  33. package/src/cli/commands/init/skills.ts +56 -3
  34. package/src/cli/commands/init/templates/agents.md +65 -61
  35. package/src/cli/commands/init/templates/skills/zond-checks.md +397 -0
  36. package/src/cli/commands/init/templates/skills/zond-triage.md +210 -0
  37. package/src/cli/commands/init/templates/skills/zond.md +592 -125
  38. package/src/cli/commands/init/templates/zond-config.yml +8 -9
  39. package/src/cli/commands/prepare-fixtures.ts +135 -0
  40. package/src/cli/commands/probe/mass-assignment.ts +503 -0
  41. package/src/cli/commands/probe/security.ts +454 -0
  42. package/src/cli/commands/probe/static.ts +255 -0
  43. package/src/cli/commands/probe/webhooks.ts +161 -0
  44. package/src/cli/commands/probe.ts +459 -0
  45. package/src/cli/commands/reference.ts +87 -0
  46. package/src/cli/commands/refresh-api.ts +169 -0
  47. package/src/cli/commands/remove-api.ts +150 -0
  48. package/src/cli/commands/report-bundle.ts +318 -0
  49. package/src/cli/commands/report.ts +241 -0
  50. package/src/cli/commands/request.ts +379 -4
  51. package/src/cli/commands/run.ts +842 -53
  52. package/src/cli/commands/session.ts +244 -0
  53. package/src/cli/commands/use.ts +18 -1
  54. package/src/cli/index.ts +20 -3
  55. package/src/cli/json-envelope.ts +112 -3
  56. package/src/cli/json-schemas.ts +263 -0
  57. package/src/cli/program.ts +198 -635
  58. package/src/cli/resolve.ts +105 -0
  59. package/src/cli/status-filter.ts +124 -0
  60. package/src/cli/util/api-context.ts +85 -0
  61. package/src/cli/version.ts +5 -0
  62. package/src/core/anti-fp/bootstrap.ts +34 -0
  63. package/src/core/anti-fp/index.ts +33 -0
  64. package/src/core/anti-fp/registry.ts +44 -0
  65. package/src/core/anti-fp/rules/baseline-echo.ts +74 -0
  66. package/src/core/anti-fp/rules/schemathesis/body_negation_becomes_valid.ts +52 -0
  67. package/src/core/anti-fp/rules/schemathesis/coverage_phase_boundary_positive.ts +38 -0
  68. package/src/core/anti-fp/rules/schemathesis/has_unverifiable_mutations.ts +35 -0
  69. package/src/core/anti-fp/rules/schemathesis/index.ts +24 -0
  70. package/src/core/anti-fp/rules/schemathesis/string_type_mutation_becomes_valid.ts +53 -0
  71. package/src/core/anti-fp/rules/subscription-gated/index.ts +11 -0
  72. package/src/core/anti-fp/rules/subscription-gated/paid-plan-403.ts +75 -0
  73. package/src/core/anti-fp/types.ts +68 -0
  74. package/src/core/checks/checks/_crud-helpers.ts +133 -0
  75. package/src/core/checks/checks/_negative_mutator.ts +133 -0
  76. package/src/core/checks/checks/_readback-helpers.ts +133 -0
  77. package/src/core/checks/checks/content_type_conformance.ts +39 -0
  78. package/src/core/checks/checks/cross_call_references.ts +134 -0
  79. package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
  80. package/src/core/checks/checks/idempotency_replay.ts +246 -0
  81. package/src/core/checks/checks/ignored_auth.ts +211 -0
  82. package/src/core/checks/checks/index.ts +65 -0
  83. package/src/core/checks/checks/lifecycle_transitions.ts +273 -0
  84. package/src/core/checks/checks/missing_required_header.ts +40 -0
  85. package/src/core/checks/checks/negative_data_rejection.ts +45 -0
  86. package/src/core/checks/checks/not_a_server_error.ts +27 -0
  87. package/src/core/checks/checks/open_cors_on_sensitive.ts +131 -0
  88. package/src/core/checks/checks/pagination_invariants.ts +238 -0
  89. package/src/core/checks/checks/positive_data_acceptance.ts +36 -0
  90. package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
  91. package/src/core/checks/checks/response_headers_conformance.ts +74 -0
  92. package/src/core/checks/checks/response_schema_conformance.ts +30 -0
  93. package/src/core/checks/checks/status_code_conformance.ts +61 -0
  94. package/src/core/checks/checks/unsupported_method.ts +63 -0
  95. package/src/core/checks/checks/use_after_free.ts +78 -0
  96. package/src/core/checks/index.ts +30 -0
  97. package/src/core/checks/mode.ts +79 -0
  98. package/src/core/checks/recommended-action.ts +64 -0
  99. package/src/core/checks/registry.ts +78 -0
  100. package/src/core/checks/runner.ts +874 -0
  101. package/src/core/checks/sarif.ts +230 -0
  102. package/src/core/checks/stateful.ts +121 -0
  103. package/src/core/checks/types.ts +189 -0
  104. package/src/core/classifier/recommended-action.ts +222 -0
  105. package/src/core/context/current.ts +22 -6
  106. package/src/core/context/session.ts +78 -0
  107. package/src/core/coverage/loader.ts +185 -0
  108. package/src/core/coverage/reasons.ts +300 -0
  109. package/src/core/diagnostics/db-analysis.ts +151 -11
  110. package/src/core/diagnostics/failure-class.ts +120 -0
  111. package/src/core/diagnostics/failure-hints.ts +212 -9
  112. package/src/core/diagnostics/spec-pointer.ts +99 -0
  113. package/src/core/diagnostics/suggested-fixes.ts +156 -0
  114. package/src/core/exporter/case-study/index.ts +270 -0
  115. package/src/core/exporter/curl.ts +40 -0
  116. package/src/core/exporter/exporter.ts +48 -0
  117. package/src/core/exporter/html-report/escape.ts +24 -0
  118. package/src/core/exporter/html-report/index.ts +479 -0
  119. package/src/core/exporter/html-report/script.ts +100 -0
  120. package/src/core/exporter/html-report/styles.ts +408 -0
  121. package/src/core/generator/chunker.ts +42 -16
  122. package/src/core/generator/coverage-phase.ts +0 -0
  123. package/src/core/generator/create-body.ts +89 -0
  124. package/src/core/generator/data-factory.ts +445 -19
  125. package/src/core/generator/describe.ts +1 -1
  126. package/src/core/generator/fixtures-builder.ts +325 -0
  127. package/src/core/generator/index.ts +7 -5
  128. package/src/core/generator/openapi-reader.ts +37 -3
  129. package/src/core/generator/path-param-disambig.ts +114 -0
  130. package/src/core/generator/resources-builder.ts +648 -0
  131. package/src/core/generator/schema-utils.ts +11 -3
  132. package/src/core/generator/serializer.ts +103 -13
  133. package/src/core/generator/suite-generator.ts +419 -111
  134. package/src/core/generator/types.ts +8 -0
  135. package/src/core/identity/identity-file.ts +129 -0
  136. package/src/core/lint/affects.ts +28 -0
  137. package/src/core/lint/config.ts +96 -0
  138. package/src/core/lint/format.ts +42 -0
  139. package/src/core/lint/index.ts +94 -0
  140. package/src/core/lint/reporter.ts +128 -0
  141. package/src/core/lint/rules/consistency.ts +158 -0
  142. package/src/core/lint/rules/heuristics.ts +97 -0
  143. package/src/core/lint/rules/strictness.ts +109 -0
  144. package/src/core/lint/types.ts +96 -0
  145. package/src/core/lint/walker.ts +248 -0
  146. package/src/core/meta/meta-store.ts +6 -73
  147. package/src/core/output/README.md +91 -0
  148. package/src/core/output/index.ts +13 -0
  149. package/src/core/output/run.ts +126 -0
  150. package/src/core/output/types.ts +129 -0
  151. package/src/core/parser/env-interpolation.ts +104 -0
  152. package/src/core/parser/filter.ts +57 -0
  153. package/src/core/parser/schema.ts +129 -4
  154. package/src/core/parser/types.ts +19 -1
  155. package/src/core/parser/variables.ts +0 -0
  156. package/src/core/parser/yaml-parser.ts +58 -12
  157. package/src/core/probe/bootstrap.ts +34 -0
  158. package/src/core/probe/dry-run-envelope.ts +57 -0
  159. package/src/core/probe/mass-assignment-probe-class.ts +198 -0
  160. package/src/core/probe/mass-assignment-probe.ts +1122 -0
  161. package/src/core/probe/mass-assignment-template.ts +212 -0
  162. package/src/core/probe/method-probe.ts +43 -76
  163. package/src/core/probe/method-shared.ts +69 -0
  164. package/src/core/probe/negative-probe.ts +183 -149
  165. package/src/core/probe/orphan-tracker.ts +188 -0
  166. package/src/core/probe/path-discovery.ts +440 -0
  167. package/src/core/probe/probe-harness.ts +120 -0
  168. package/src/core/probe/registry.ts +89 -0
  169. package/src/core/probe/runner.ts +136 -0
  170. package/src/core/probe/security-probe-class.ts +201 -0
  171. package/src/core/probe/security-probe.ts +1453 -0
  172. package/src/core/probe/shared.ts +505 -0
  173. package/src/core/probe/static-probe-class.ts +125 -0
  174. package/src/core/probe/types.ts +165 -0
  175. package/src/core/probe/verdict-aggregator.ts +33 -0
  176. package/src/core/probe/webhooks-probe.ts +284 -0
  177. package/src/core/reporter/console.ts +41 -2
  178. package/src/core/reporter/index.ts +2 -3
  179. package/src/core/reporter/json.ts +11 -1
  180. package/src/core/reporter/junit.ts +27 -12
  181. package/src/core/reporter/ndjson.ts +37 -0
  182. package/src/core/reporter/types.ts +3 -0
  183. package/src/core/runner/assertions.ts +58 -1
  184. package/src/core/runner/async-pool.ts +108 -0
  185. package/src/core/runner/auth-path.ts +8 -0
  186. package/src/core/runner/ci-context.ts +72 -0
  187. package/src/core/runner/executor.ts +264 -20
  188. package/src/core/runner/form-encode.ts +51 -0
  189. package/src/core/runner/http-client.ts +75 -2
  190. package/src/core/runner/learn-drift.ts +293 -0
  191. package/src/core/runner/preflight-vars.ts +149 -0
  192. package/src/core/runner/progress-tracker.ts +73 -0
  193. package/src/core/runner/rate-limiter.ts +89 -17
  194. package/src/core/runner/run-kind.ts +39 -0
  195. package/src/core/runner/schema-validator.ts +312 -0
  196. package/src/core/runner/send-request.ts +153 -20
  197. package/src/core/runner/types.ts +38 -0
  198. package/src/core/secrets/registry.ts +164 -0
  199. package/src/core/secrets/secrets-file.ts +115 -0
  200. package/src/core/selectors/operation-filter.ts +144 -0
  201. package/src/core/setup-api.ts +415 -16
  202. package/src/core/severity/category.ts +94 -0
  203. package/src/core/severity/index.ts +121 -0
  204. package/src/core/spec/layers.ts +154 -0
  205. package/src/core/util/format-eta.ts +21 -0
  206. package/src/core/utils.ts +5 -1
  207. package/src/core/workspace/config.ts +129 -0
  208. package/src/core/workspace/manifest.ts +283 -0
  209. package/src/core/workspace/output-rotation.ts +62 -0
  210. package/src/core/workspace/triage-path.ts +87 -0
  211. package/src/db/lint-runs.ts +47 -0
  212. package/src/db/migrate.ts +126 -0
  213. package/src/db/migrations/0001_run_kind.sql +25 -0
  214. package/src/db/migrations/sql.d.ts +4 -0
  215. package/src/db/queries/collections.ts +133 -0
  216. package/src/db/queries/coverage.ts +9 -0
  217. package/src/db/queries/dashboard.ts +59 -0
  218. package/src/db/queries/results.ts +128 -0
  219. package/src/db/queries/runs.ts +235 -0
  220. package/src/db/queries/sessions.ts +42 -0
  221. package/src/db/queries/settings.ts +28 -0
  222. package/src/db/queries/types.ts +172 -0
  223. package/src/db/queries.ts +72 -802
  224. package/src/db/schema.ts +178 -50
  225. package/src/cli/commands/export.ts +0 -144
  226. package/src/cli/commands/guide.ts +0 -127
  227. package/src/cli/commands/init/templates/skills/scenarios.md +0 -97
  228. package/src/cli/commands/probe-methods.ts +0 -108
  229. package/src/cli/commands/probe-validation.ts +0 -124
  230. package/src/cli/commands/serve.ts +0 -114
  231. package/src/cli/commands/sync.ts +0 -268
  232. package/src/cli/commands/update.ts +0 -189
  233. package/src/cli/commands/validate.ts +0 -34
  234. package/src/core/diagnostics/render-md.ts +0 -112
  235. package/src/core/exporter/postman.ts +0 -963
  236. package/src/core/generator/guide-builder.ts +0 -253
  237. package/src/core/meta/types.ts +0 -19
  238. package/src/core/parser/index.ts +0 -21
  239. package/src/core/runner/execute-run.ts +0 -132
  240. package/src/core/runner/index.ts +0 -12
  241. package/src/core/sync/spec-differ.ts +0 -38
  242. package/src/web/data/collection-state.ts +0 -362
  243. package/src/web/routes/api.ts +0 -314
  244. package/src/web/routes/dashboard.ts +0 -350
  245. package/src/web/routes/runs.ts +0 -64
  246. package/src/web/schemas.ts +0 -121
  247. package/src/web/server.ts +0 -134
  248. package/src/web/static/htmx.min.cjs +0 -1
  249. package/src/web/static/style.css +0 -1148
  250. package/src/web/views/endpoints-tab.ts +0 -174
  251. package/src/web/views/explorer-tab.ts +0 -402
  252. package/src/web/views/health-strip.ts +0 -92
  253. package/src/web/views/layout.ts +0 -48
  254. package/src/web/views/results.ts +0 -210
  255. package/src/web/views/runs-tab.ts +0 -126
  256. package/src/web/views/suites-tab.ts +0 -181
@@ -0,0 +1,230 @@
1
+ /**
2
+ * SARIF v2.1.0 reporter for `zond checks` (m-15 ARV-5).
3
+ *
4
+ * Maps `CheckFinding[]` into a SARIF log that GitHub Code Scanning can
5
+ * ingest via `github/codeql-action/upload-sarif@v3`. Two invariants the
6
+ * format relies on:
7
+ *
8
+ * - `tool.driver.rules` — descriptors for every registered check, so
9
+ * even a finding-less run carries the catalog. ruleId follows the
10
+ * `<category>-<check_id>` form that oasdiff uses.
11
+ * - `partialFingerprints.primary` — sha1(ruleId + jsonPointer +
12
+ * spec_hash). Stable across re-runs of the same spec, so GitHub
13
+ * dedupes rather than re-opening alerts every push (42Crunch-style).
14
+ *
15
+ * The reporter is deliberately schema-only — it builds the JSON
16
+ * document, the CLI handles writing it to disk.
17
+ */
18
+ import { createHash } from "node:crypto";
19
+
20
+ import { listChecks } from "./registry.ts";
21
+ import { listStatefulChecks } from "./stateful.ts";
22
+ import type { CheckFinding, Severity } from "./types.ts";
23
+ import { categoryFor, type Category } from "../severity/category.ts";
24
+ import { severityToSarifLevel } from "../severity/index.ts";
25
+
26
+ export { categoryFor };
27
+
28
+ export function ruleIdFor(checkId: string): string {
29
+ return `${categoryFor(checkId)}-${checkId}`;
30
+ }
31
+
32
+ const severityToLevel = severityToSarifLevel;
33
+
34
+ /** RFC 6901 JSON Pointer for the operation: `/paths/<escaped>/<method>`.
35
+ * Escapes `~` → `~0` and `/` → `~1` so paths like `/users/{id}` survive
36
+ * serialization. */
37
+ export function jsonPointerForOperation(path: string, method: string): string {
38
+ const escaped = path.replace(/~/g, "~0").replace(/\//g, "~1");
39
+ return `/paths/${escaped}/${method.toLowerCase()}`;
40
+ }
41
+
42
+ function sha1(s: string): string {
43
+ return createHash("sha1").update(s).digest("hex");
44
+ }
45
+
46
+ export function specHashOf(specContent: string): string {
47
+ return sha1(specContent);
48
+ }
49
+
50
+ export function partialFingerprintFor(ruleId: string, jsonPointer: string, specHash: string): string {
51
+ return sha1(`${ruleId}\n${jsonPointer}\n${specHash}`);
52
+ }
53
+
54
+ interface SarifReportingDescriptor {
55
+ id: string;
56
+ name: string;
57
+ shortDescription: { text: string };
58
+ defaultConfiguration: { level: "error" | "warning" | "note" };
59
+ helpUri?: string;
60
+ properties: {
61
+ category: Category;
62
+ severity: Severity;
63
+ references: string[];
64
+ tags: string[];
65
+ };
66
+ }
67
+
68
+ interface SarifResult {
69
+ ruleId: string;
70
+ ruleIndex: number;
71
+ level: "error" | "warning" | "note";
72
+ message: { text: string };
73
+ locations: Array<{
74
+ physicalLocation: {
75
+ artifactLocation: { uri: string; uriBaseId?: string };
76
+ // SARIF requires region to specify at least one of startLine/charOffset/
77
+ // byteOffset. We don't parse the spec into lines — startLine: 1 keeps
78
+ // GitHub Code Scanning happy and the JSON Pointer travels in the
79
+ // logicalLocations + properties below.
80
+ region: { startLine: number; snippet: { text: string } };
81
+ };
82
+ logicalLocations: Array<{ fullyQualifiedName: string; kind: string }>;
83
+ }>;
84
+ partialFingerprints: { primary: string };
85
+ properties: Record<string, unknown>;
86
+ }
87
+
88
+ export interface SarifLog {
89
+ $schema: string;
90
+ version: "2.1.0";
91
+ runs: Array<{
92
+ tool: {
93
+ driver: {
94
+ name: string;
95
+ version: string;
96
+ informationUri: string;
97
+ rules: SarifReportingDescriptor[];
98
+ };
99
+ };
100
+ results: SarifResult[];
101
+ }>;
102
+ }
103
+
104
+ export interface SarifReportOptions {
105
+ findings: CheckFinding[];
106
+ /** Raw spec text — fed into `sha1` for the partial-fingerprint salt
107
+ * so two runs against the same spec produce identical fingerprints. */
108
+ specContent: string;
109
+ /** SARIF artifactLocation.uri. Defaults to "spec.json"; CLI sets the
110
+ * spec's relative path so GitHub Code Scanning links findings to the
111
+ * spec source file. */
112
+ specUri?: string;
113
+ toolVersion: string;
114
+ toolInformationUri?: string;
115
+ }
116
+
117
+ function buildRules(): SarifReportingDescriptor[] {
118
+ const all = [
119
+ ...listChecks().map((c) => ({
120
+ id: c.id,
121
+ severity: c.severity,
122
+ defaultExpected: c.defaultExpected,
123
+ references: c.references,
124
+ })),
125
+ ...listStatefulChecks().map((c) => ({
126
+ id: c.id,
127
+ severity: c.severity,
128
+ defaultExpected: c.defaultExpected,
129
+ references: c.references,
130
+ })),
131
+ ];
132
+ const seen = new Set<string>();
133
+ const rules: SarifReportingDescriptor[] = [];
134
+ for (const c of all) {
135
+ const ruleId = ruleIdFor(c.id);
136
+ if (seen.has(ruleId)) continue;
137
+ seen.add(ruleId);
138
+ const cat = categoryFor(c.id);
139
+ const helpUri = c.references.find((r) => r.url)?.url;
140
+ const descriptor: SarifReportingDescriptor = {
141
+ id: ruleId,
142
+ name: c.id,
143
+ shortDescription: { text: c.defaultExpected },
144
+ defaultConfiguration: { level: severityToLevel(c.severity) },
145
+ properties: {
146
+ category: cat,
147
+ severity: c.severity,
148
+ references: c.references.map((r) => r.id),
149
+ tags: [cat, "openapi", "zond"],
150
+ },
151
+ };
152
+ if (helpUri) descriptor.helpUri = helpUri;
153
+ rules.push(descriptor);
154
+ }
155
+ return rules.sort((a, b) => a.id.localeCompare(b.id));
156
+ }
157
+
158
+ export function generateSarifReport(opts: SarifReportOptions): SarifLog {
159
+ const specHash = specHashOf(opts.specContent);
160
+ const specUri = opts.specUri ?? "spec.json";
161
+ const rules = buildRules();
162
+ const ruleIndex = new Map(rules.map((r, i) => [r.id, i] as const));
163
+
164
+ // Sort findings deterministically so two runs over the same spec emit
165
+ // byte-identical SARIF — GitHub diffs the file across pushes and any
166
+ // reordering churn would re-open and re-close alerts spuriously.
167
+ const sorted = [...opts.findings].sort((a, b) => {
168
+ const aId = ruleIdFor(a.check);
169
+ const bId = ruleIdFor(b.check);
170
+ if (aId !== bId) return aId.localeCompare(bId);
171
+ if (a.operation.path !== b.operation.path) return a.operation.path.localeCompare(b.operation.path);
172
+ if (a.operation.method !== b.operation.method) return a.operation.method.localeCompare(b.operation.method);
173
+ return a.message.localeCompare(b.message);
174
+ });
175
+
176
+ const results: SarifResult[] = sorted.map((f) => {
177
+ const ruleId = ruleIdFor(f.check);
178
+ const ptr = jsonPointerForOperation(f.operation.path, f.operation.method);
179
+ const idx = ruleIndex.get(ruleId);
180
+ if (idx === undefined) {
181
+ throw new Error(`SARIF: no rule descriptor registered for check "${f.check}" (ruleId "${ruleId}")`);
182
+ }
183
+ const properties: Record<string, unknown> = {
184
+ severity: f.severity,
185
+ method: f.operation.method,
186
+ path: f.operation.path,
187
+ request_signature: f.request_signature,
188
+ response_status: f.response_summary.status,
189
+ };
190
+ if (f.operation.operationId) properties.operationId = f.operation.operationId;
191
+ if (f.response_summary.content_type) properties.response_content_type = f.response_summary.content_type;
192
+ if (f.evidence) properties.evidence = f.evidence;
193
+ if (f.recommended_action) properties.recommendedAction = f.recommended_action;
194
+ return {
195
+ ruleId,
196
+ ruleIndex: idx,
197
+ level: severityToLevel(f.severity),
198
+ message: { text: f.message },
199
+ locations: [
200
+ {
201
+ physicalLocation: {
202
+ artifactLocation: { uri: specUri },
203
+ region: { startLine: 1, snippet: { text: ptr } },
204
+ },
205
+ logicalLocations: [{ fullyQualifiedName: ptr, kind: "object" }],
206
+ },
207
+ ],
208
+ partialFingerprints: { primary: partialFingerprintFor(ruleId, ptr, specHash) },
209
+ properties,
210
+ };
211
+ });
212
+
213
+ return {
214
+ $schema: "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
215
+ version: "2.1.0",
216
+ runs: [
217
+ {
218
+ tool: {
219
+ driver: {
220
+ name: "zond",
221
+ version: opts.toolVersion,
222
+ informationUri: opts.toolInformationUri ?? "https://github.com/kirrosh/zond",
223
+ rules,
224
+ },
225
+ },
226
+ results,
227
+ },
228
+ ],
229
+ };
230
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Stateful checks (m-15 ARV-3) — security-flavored checks that need
3
+ * to orchestrate multiple HTTP requests against a single operation
4
+ * (auth probes) or a CRUD chain (use-after-free / availability).
5
+ *
6
+ * Kept in a parallel registry from the per-response `Check`s so the
7
+ * single-response runner stays simple. `runChecks` calls
8
+ * `runStateful(...)` after the per-op response phase.
9
+ */
10
+ import type { OpenAPIV3 } from "openapi-types";
11
+ import type { CrudGroup, EndpointInfo } from "../generator/types.ts";
12
+ import type { HttpRequest, HttpResponse } from "../runner/types.ts";
13
+ import { executeRequest } from "../runner/http-client.ts";
14
+ import type { CheckOutcome, CheckReference, CheckRuntimeOptions, Severity } from "./types.ts";
15
+
16
+ export interface StatefulHarness {
17
+ baseUrl: string;
18
+ doc: OpenAPIV3.Document;
19
+ /** Headers that constitute "real auth" for the run. Empty when the
20
+ * caller didn't pass --auth-header / no env vars. */
21
+ authHeaders: Record<string, string>;
22
+ /** When true, security checks should skip with a warning (ARV-3 AC #6). */
23
+ bootstrapCleanupFailed: boolean;
24
+ /** ARV-181: real path-param fixtures from `.env.yaml`. Mirrors what
25
+ * the per-response runner already does via ARV-141 — without this
26
+ * the stateful harness rebuilds URLs with literal `{event_id}`
27
+ * placeholders, gets routed to 403/404, and the broken-baseline
28
+ * guard silently skips real auth checks. Optional so unit tests
29
+ * can stub without it; production callers always pass them. */
30
+ pathVars?: Record<string, string>;
31
+ /** ARV-181: per-run knobs (e.g. strict401). Mirrors CheckContext.options
32
+ * for stateful checks so they can read the same flags as per-response
33
+ * ones. */
34
+ options?: CheckRuntimeOptions;
35
+ /** ARV-169 (m-20): per-resource overrides for cross-call probes,
36
+ * keyed by `resource` name from `.api-resources.yaml`. Today only
37
+ * `cross_call_references` reads `readbackDiff`; future m-20 probes
38
+ * (idempotency, pagination, lifecycle) will append their own keys
39
+ * to the per-resource entry. Optional — when absent each probe
40
+ * falls back to its built-in defaults. */
41
+ resourceConfigs?: Map<string, {
42
+ readbackDiff?: import("../generator/resources-builder.ts").ReadbackDiffConfig;
43
+ idempotency?: import("../generator/resources-builder.ts").IdempotencyConfig;
44
+ pagination?: import("../generator/resources-builder.ts").PaginationConfig;
45
+ lifecycle?: import("../generator/resources-builder.ts").LifecycleConfig;
46
+ /** ARV-187: LLM-authored example POST body — preferred over
47
+ * generateFromSchema(create) by stateful CRUD checks. */
48
+ seedBody?: import("../generator/resources-builder.ts").SeedBodyConfig;
49
+ }>;
50
+ send(req: HttpRequest, opts?: { timeoutMs?: number }): Promise<HttpResponse>;
51
+ }
52
+
53
+ export interface BaseStatefulCheck {
54
+ id: string;
55
+ severity: Severity;
56
+ defaultExpected: string;
57
+ references: CheckReference[];
58
+ }
59
+
60
+ export interface AuthStatefulCheck extends BaseStatefulCheck {
61
+ phase: "auth";
62
+ applies(op: EndpointInfo): boolean;
63
+ run(op: EndpointInfo, h: StatefulHarness): Promise<CheckOutcome>;
64
+ }
65
+
66
+ export interface CrudStatefulCheck extends BaseStatefulCheck {
67
+ phase: "crud";
68
+ applies(group: CrudGroup): boolean;
69
+ run(group: CrudGroup, h: StatefulHarness): Promise<CheckOutcome>;
70
+ }
71
+
72
+ export type StatefulCheck = AuthStatefulCheck | CrudStatefulCheck;
73
+
74
+ const STATEFUL_REGISTRY = new Map<string, StatefulCheck>();
75
+
76
+ export function registerStatefulCheck(c: StatefulCheck): void {
77
+ if (STATEFUL_REGISTRY.has(c.id)) throw new Error(`Stateful check "${c.id}" already registered`);
78
+ STATEFUL_REGISTRY.set(c.id, c);
79
+ }
80
+
81
+ export function listStatefulChecks(): StatefulCheck[] {
82
+ return [...STATEFUL_REGISTRY.values()].sort((a, b) => a.id.localeCompare(b.id));
83
+ }
84
+
85
+ export function makeHarness(
86
+ baseUrl: string,
87
+ doc: OpenAPIV3.Document,
88
+ opts: {
89
+ authHeaders?: Record<string, string>;
90
+ bootstrapCleanupFailed?: boolean;
91
+ timeoutMs?: number;
92
+ pathVars?: Record<string, string>;
93
+ options?: CheckRuntimeOptions;
94
+ resourceConfigs?: StatefulHarness["resourceConfigs"];
95
+ /** ARV-227: shared with the per-response phase so a single
96
+ * `--max-requests` cap bounds the whole run. Throws (caught by
97
+ * the runner's per-check try/catch and converted to skip) once
98
+ * the budget is exhausted, so a stateful probe mid-chain doesn't
99
+ * silently spin past the cap. */
100
+ requestBudget?: import("../runner/executor.ts").RequestBudget;
101
+ } = {},
102
+ ): StatefulHarness {
103
+ return {
104
+ baseUrl,
105
+ doc,
106
+ authHeaders: opts.authHeaders ?? {},
107
+ bootstrapCleanupFailed: opts.bootstrapCleanupFailed ?? false,
108
+ pathVars: opts.pathVars,
109
+ options: opts.options,
110
+ resourceConfigs: opts.resourceConfigs,
111
+ send: async (req, sendOpts) => {
112
+ if (opts.requestBudget) {
113
+ const { reserveRequest, MAX_REQUESTS_SKIP_REASON } = await import("../runner/executor.ts");
114
+ if (!reserveRequest(opts.requestBudget)) {
115
+ throw new Error(MAX_REQUESTS_SKIP_REASON);
116
+ }
117
+ }
118
+ return executeRequest(req, { timeout: sendOpts?.timeoutMs ?? opts.timeoutMs ?? 30000 });
119
+ },
120
+ };
121
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Type contracts for the `zond checks` framework (m-15 ARV-1).
3
+ *
4
+ * A `Check` is a single named conformance/security probe applied to one
5
+ * operation × one HTTP response. The `runner` resolves the spec into
6
+ * operations, generates a request via `core/generator/data-factory`,
7
+ * sends it via `core/runner/send-request`, and feeds each (case,
8
+ * response) pair to every active check.
9
+ *
10
+ * Names mirror schemathesis V4 1-to-1 so benchmarks and findings carry
11
+ * across (see `backlog/milestones/m-15`). New checks register themselves
12
+ * in `checks/index.ts` via `registerCheck`.
13
+ */
14
+ import type { OpenAPIV3 } from "openapi-types";
15
+ import type { EndpointInfo } from "../generator/types.ts";
16
+ import type { SchemaValidator } from "../runner/schema-validator.ts";
17
+ import type { RecommendedAction } from "../diagnostics/failure-hints.ts";
18
+
19
+ // Severity unified in src/core/severity (ARV-250). Re-exported here for
20
+ // backwards-compatible imports across the checks subsystem; the canonical
21
+ // definition is in core/severity. Adds 'info' tier (previously absent in
22
+ // checks, now needed for hygiene-class findings per m-21 pivot).
23
+ import type { Severity } from "../severity/index.ts";
24
+ import { emptySeverityBuckets } from "../severity/index.ts";
25
+ import type { Category } from "../severity/category.ts";
26
+ import { emptyCategoryBuckets } from "../severity/category.ts";
27
+ export type { Severity, Category };
28
+
29
+ export type Phase = "examples" | "coverage" | "all";
30
+
31
+ /** Probe shapes a check may need. ARV-1 shipped only `positive`; ARV-2
32
+ * adds two more for header/method-rejection checks; ARV-4 will add
33
+ * `negative_data` for the data-rejection pair. The runner generates a
34
+ * case for each kind that at least one active check declares. */
35
+ export type CaseKind =
36
+ | "positive"
37
+ | "missing_required_header"
38
+ | "unsupported_method"
39
+ | "negative_data";
40
+
41
+ export interface CheckReference {
42
+ /** CWE / OWASP / RFC identifier — free form, agent-readable. */
43
+ id: string;
44
+ /** Optional URL for the human reader. */
45
+ url?: string;
46
+ }
47
+
48
+ export interface CheckResponse {
49
+ status: number;
50
+ headers: Record<string, string>;
51
+ body: unknown;
52
+ duration_ms: number;
53
+ }
54
+
55
+ export interface CheckCase {
56
+ /** The operation under test (path + method + parameters). */
57
+ operation: EndpointInfo;
58
+ /** Resolved request that produced the response below. */
59
+ request: {
60
+ method: string;
61
+ url: string;
62
+ headers: Record<string, string>;
63
+ body?: string;
64
+ };
65
+ /** Mode the case was generated under — positive (valid) or negative
66
+ * (intentionally invalid). Drives the ARV-7 `--mode` filter. */
67
+ mode: "positive" | "negative";
68
+ /** Probe shape — what the case is *built* to exercise. A check only
69
+ * runs against a case whose `kind` it declared (or all if it
70
+ * declared none — defaults to `positive`). */
71
+ kind: CaseKind;
72
+ /** For probe-style cases, an opaque hint the check itself produced
73
+ * (e.g. the header name that was dropped, the method that was
74
+ * swapped in). Lets a check emit a precise finding without parsing
75
+ * the request back out. */
76
+ meta?: Record<string, unknown>;
77
+ }
78
+
79
+ /** ARV-179: per-run knobs surfaced from `RunCheckOptions` into the
80
+ * check itself. Add new fields here when a check needs an opt-in
81
+ * behaviour that doesn't deserve its own check id. Keep this lean —
82
+ * most knobs belong upstream in the case-generator, not in the
83
+ * check's response-side logic. */
84
+ export interface CheckRuntimeOptions {
85
+ /** ARV-179: require strict 405 for `unsupported_method` (matches
86
+ * schemathesis V4 default). Off by default — zond's pragmatic policy
87
+ * accepts 401/403/404 as legitimate rejections of an undeclared
88
+ * method (common nginx/gateway behaviour). */
89
+ strict405?: boolean;
90
+ /** ARV-181: require strict 401 for `ignored_auth` no-auth / bogus-auth
91
+ * variants (matches schemathesis V4 default). Off by default — zond's
92
+ * pragmatic policy accepts any 4xx as a legitimate auth-reject (403,
93
+ * 404, 422 are common). With this on, only 401 passes — a 403 on
94
+ * no_auth becomes a finding "expected 401, got 403". */
95
+ strict401?: boolean;
96
+ }
97
+
98
+ export interface CheckContext {
99
+ case: CheckCase;
100
+ response: CheckResponse;
101
+ /** Pre-built once per run so checks don't pay AJV compile cost
102
+ * per-case. Optional so unit tests can stub a context without one. */
103
+ schemaValidator?: SchemaValidator;
104
+ /** Original spec doc — checks that need declared headers /
105
+ * content-types / status codes look them up here. */
106
+ doc?: OpenAPIV3.Document;
107
+ /** ARV-179: per-run knobs (see CheckRuntimeOptions). */
108
+ options?: CheckRuntimeOptions;
109
+ }
110
+
111
+ export type CheckOutcome =
112
+ | { kind: "pass" }
113
+ | { kind: "skip"; reason: string }
114
+ | { kind: "fail"; message: string; evidence?: Record<string, unknown> };
115
+
116
+ export interface Check {
117
+ /** Stable identifier — must match schemathesis name where possible. */
118
+ id: string;
119
+ severity: Severity;
120
+ /** Default expected outcome ("server should NOT 5xx", etc) — surfaced
121
+ * by `zond checks list` so an agent can read the contract. */
122
+ defaultExpected: string;
123
+ references: CheckReference[];
124
+ /** Probe shapes this check consumes. Default `["positive"]` — most
125
+ * conformance checks just inspect the standard response. ARV-2's
126
+ * `missing_required_header` / `unsupported_method` declare their
127
+ * own kinds so the runner generates the matching probe case. */
128
+ caseKinds?: CaseKind[];
129
+ /** Whether this check is meaningful for the given operation. Used by
130
+ * the runner to skip checks that don't apply (e.g. auth-related
131
+ * checks on operations with no security requirement). */
132
+ applies(operation: EndpointInfo): boolean;
133
+ run(ctx: CheckContext): CheckOutcome;
134
+ }
135
+
136
+ export interface CheckFinding {
137
+ check: string;
138
+ severity: Severity;
139
+ /** ARV-251: finding category drives per-section roll-up in reports.
140
+ * Optional for backwards compat — when absent, downstream code derives
141
+ * it via `categoryFor(check)`. Storing it on the finding keeps probe
142
+ * emitters and check emitters using the same shape. */
143
+ category?: Category;
144
+ operation: { path: string; method: string; operationId?: string };
145
+ request_signature: string;
146
+ response_summary: { status: number; content_type?: string };
147
+ message: string;
148
+ evidence?: Record<string, unknown>;
149
+ /** ARV-11 — closed enum so agents can route on it without parsing
150
+ * the message. Resolved by `recommendForCheck()` keyed on the
151
+ * check id (and response status for `network_error`). Optional
152
+ * because synthetic findings (e.g. unit-test fakes) may skip it. */
153
+ recommended_action?: RecommendedAction;
154
+ }
155
+
156
+ export interface CheckRunSummary {
157
+ operations: number;
158
+ cases: number;
159
+ checks_run: number;
160
+ findings: number;
161
+ by_severity: Record<Severity, number>;
162
+ /** ARV-251: per-category roll-up — small teams use this to triage
163
+ * "0 security, 12 reliability, 40 contract, 200 hygiene" instead of
164
+ * reading one flat severity pile. */
165
+ by_category: Record<Category, number>;
166
+ /** ARV-26: count of `kind: "skip"` outcomes returned by checks, keyed by
167
+ * `"<check_id>: <reason>"`. Surfaces the gap between probe and runtime
168
+ * validators — e.g. `response_schema_conformance: no JSON Schema on this
169
+ * response branch ×2` tells the user why "0 findings" doesn't mean "all
170
+ * green" (probe got 4xx, response schema only declared on 2xx). */
171
+ skipped_outcomes: Record<string, number>;
172
+ }
173
+
174
+ export interface CheckRunData {
175
+ findings: CheckFinding[];
176
+ summary: CheckRunSummary;
177
+ }
178
+
179
+ export function emptySummary(): CheckRunSummary {
180
+ return {
181
+ operations: 0,
182
+ cases: 0,
183
+ checks_run: 0,
184
+ findings: 0,
185
+ by_severity: emptySeverityBuckets(),
186
+ by_category: emptyCategoryBuckets(),
187
+ skipped_outcomes: {},
188
+ };
189
+ }