@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.
- package/CHANGELOG.md +47 -0
- package/README.md +22 -21
- package/bin/zond.mjs +23 -0
- package/package.json +13 -16
- package/scripts/npm/postinstall.mjs +76 -0
- package/src/CLAUDE.md +0 -112
- package/src/bun-types.d.ts +0 -5
- package/src/cli/argv.ts +0 -122
- package/src/cli/commands/add-api.ts +0 -146
- package/src/cli/commands/api/annotate/idempotency.ts +0 -59
- package/src/cli/commands/api/annotate/index.ts +0 -880
- package/src/cli/commands/api/annotate/lifecycle.ts +0 -74
- package/src/cli/commands/api/annotate/overlay.ts +0 -206
- package/src/cli/commands/api/annotate/pagination.ts +0 -64
- package/src/cli/commands/api/annotate/prompts.ts +0 -220
- package/src/cli/commands/api/annotate/readback.ts +0 -58
- package/src/cli/commands/api/annotate/resources.ts +0 -91
- package/src/cli/commands/api/annotate/seed-bodies.ts +0 -61
- package/src/cli/commands/audit.ts +0 -786
- package/src/cli/commands/catalog.ts +0 -97
- package/src/cli/commands/check.ts +0 -361
- package/src/cli/commands/checks.ts +0 -1072
- package/src/cli/commands/ci-init.ts +0 -223
- package/src/cli/commands/clean.ts +0 -212
- package/src/cli/commands/cleanup.ts +0 -236
- package/src/cli/commands/completions.ts +0 -192
- package/src/cli/commands/coverage.ts +0 -872
- package/src/cli/commands/db.ts +0 -611
- package/src/cli/commands/describe.ts +0 -120
- package/src/cli/commands/discover.ts +0 -1356
- package/src/cli/commands/doctor.ts +0 -661
- package/src/cli/commands/fixtures.ts +0 -402
- package/src/cli/commands/generate.ts +0 -577
- package/src/cli/commands/init/agents-md.ts +0 -61
- package/src/cli/commands/init/bootstrap.ts +0 -111
- package/src/cli/commands/init/index.ts +0 -244
- package/src/cli/commands/init/skills.ts +0 -141
- package/src/cli/commands/init/templates/agents.md +0 -86
- package/src/cli/commands/init/templates/markdown.d.ts +0 -4
- package/src/cli/commands/init/templates/skills/warm-up-target.md +0 -122
- package/src/cli/commands/init/templates/skills/zond-checks.md +0 -621
- package/src/cli/commands/init/templates/skills/zond-seed.md +0 -114
- package/src/cli/commands/init/templates/skills/zond-triage.md +0 -272
- package/src/cli/commands/init/templates/skills/zond.md +0 -861
- package/src/cli/commands/init/templates/zond-config.yml +0 -14
- package/src/cli/commands/prepare-fixtures.ts +0 -97
- package/src/cli/commands/probe/_seed-bodies.ts +0 -52
- package/src/cli/commands/probe/mass-assignment.ts +0 -594
- package/src/cli/commands/probe/security.ts +0 -537
- package/src/cli/commands/probe/static.ts +0 -255
- package/src/cli/commands/probe/webhooks.ts +0 -163
- package/src/cli/commands/probe.ts +0 -535
- package/src/cli/commands/reference.ts +0 -87
- package/src/cli/commands/refresh-api.ts +0 -227
- package/src/cli/commands/remove-api.ts +0 -150
- package/src/cli/commands/report-bundle.ts +0 -310
- package/src/cli/commands/report.ts +0 -241
- package/src/cli/commands/request.ts +0 -548
- package/src/cli/commands/run.ts +0 -1162
- package/src/cli/commands/schema-from-runs.ts +0 -128
- package/src/cli/commands/secrets.ts +0 -133
- package/src/cli/commands/session.ts +0 -244
- package/src/cli/commands/use.ts +0 -74
- package/src/cli/index.ts +0 -55
- package/src/cli/json-envelope.ts +0 -108
- package/src/cli/json-schemas.ts +0 -314
- package/src/cli/output.ts +0 -40
- package/src/cli/program.ts +0 -219
- package/src/cli/resolve.ts +0 -105
- package/src/cli/runtime.ts +0 -7
- package/src/cli/safe-live.ts +0 -24
- package/src/cli/status-filter.ts +0 -114
- package/src/cli/util/api-context.ts +0 -85
- package/src/cli/version.ts +0 -8
- package/src/core/audit/persist.ts +0 -183
- package/src/core/checks/budget.ts +0 -59
- package/src/core/checks/checks/_crud-helpers.ts +0 -133
- package/src/core/checks/checks/_negative_mutator.ts +0 -133
- package/src/core/checks/checks/_readback-helpers.ts +0 -133
- package/src/core/checks/checks/content_type_conformance.ts +0 -39
- package/src/core/checks/checks/cross_call_references.ts +0 -147
- package/src/core/checks/checks/cursor_boundary_fuzzing.ts +0 -219
- package/src/core/checks/checks/ensure_resource_availability.ts +0 -62
- package/src/core/checks/checks/idempotency_replay.ts +0 -242
- package/src/core/checks/checks/ignored_auth.ts +0 -254
- package/src/core/checks/checks/index.ts +0 -68
- package/src/core/checks/checks/lifecycle_transitions.ts +0 -416
- package/src/core/checks/checks/missing_required_header.ts +0 -40
- package/src/core/checks/checks/negative_data_rejection.ts +0 -148
- package/src/core/checks/checks/not_a_server_error.ts +0 -35
- package/src/core/checks/checks/open_cors_on_sensitive.ts +0 -160
- package/src/core/checks/checks/pagination_invariants.ts +0 -419
- package/src/core/checks/checks/positive_data_acceptance.ts +0 -33
- package/src/core/checks/checks/rate_limit_headers_absent.ts +0 -77
- package/src/core/checks/checks/response_headers_conformance.ts +0 -74
- package/src/core/checks/checks/response_schema_conformance.ts +0 -30
- package/src/core/checks/checks/status_code_conformance.ts +0 -132
- package/src/core/checks/checks/unsupported_method.ts +0 -63
- package/src/core/checks/checks/use_after_free.ts +0 -78
- package/src/core/checks/index.ts +0 -30
- package/src/core/checks/mode.ts +0 -82
- package/src/core/checks/recommended-action.ts +0 -68
- package/src/core/checks/registry.ts +0 -78
- package/src/core/checks/runner.ts +0 -1461
- package/src/core/checks/sarif.ts +0 -230
- package/src/core/checks/spec-findings.ts +0 -308
- package/src/core/checks/stateful.ts +0 -121
- package/src/core/checks/types.ts +0 -305
- package/src/core/checks/zond-extensions.ts +0 -73
- package/src/core/classifier/recommended-action.ts +0 -251
- package/src/core/context/current.ts +0 -51
- package/src/core/context/session.ts +0 -78
- package/src/core/coverage/loader.ts +0 -216
- package/src/core/coverage/reasons.ts +0 -300
- package/src/core/diagnostics/db-analysis.ts +0 -666
- package/src/core/diagnostics/failure-class.ts +0 -140
- package/src/core/diagnostics/failure-hints.ts +0 -112
- package/src/core/diagnostics/spec-pointer.ts +0 -99
- package/src/core/diagnostics/suggested-fixes.ts +0 -155
- package/src/core/exporter/case-study/index.ts +0 -270
- package/src/core/exporter/curl.ts +0 -40
- package/src/core/exporter/exporter.ts +0 -48
- package/src/core/exporter/html-report/escape.ts +0 -24
- package/src/core/exporter/html-report/index.ts +0 -479
- package/src/core/exporter/html-report/script.ts +0 -100
- package/src/core/exporter/html-report/styles.ts +0 -408
- package/src/core/generator/catalog-builder.ts +0 -179
- package/src/core/generator/chunker.ts +0 -78
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/coverage-scanner.ts +0 -87
- package/src/core/generator/data-factory.ts +0 -759
- package/src/core/generator/describe.ts +0 -302
- package/src/core/generator/endpoint-warnings.ts +0 -52
- package/src/core/generator/fixtures-builder.ts +0 -332
- package/src/core/generator/index.ts +0 -14
- package/src/core/generator/openapi-reader.ts +0 -297
- package/src/core/generator/path-param-disambig.ts +0 -140
- package/src/core/generator/resources-builder.ts +0 -898
- package/src/core/generator/schema-utils.ts +0 -112
- package/src/core/generator/serializer.ts +0 -343
- package/src/core/generator/suite-generator.ts +0 -1301
- package/src/core/generator/types.ts +0 -63
- package/src/core/identity/identity-file.ts +0 -0
- package/src/core/lint/affects.ts +0 -28
- package/src/core/lint/config.ts +0 -96
- package/src/core/lint/format.ts +0 -42
- package/src/core/lint/index.ts +0 -94
- package/src/core/lint/reporter.ts +0 -128
- package/src/core/lint/rules/consistency.ts +0 -158
- package/src/core/lint/rules/heuristics.ts +0 -97
- package/src/core/lint/rules/strictness.ts +0 -109
- package/src/core/lint/types.ts +0 -96
- package/src/core/lint/walker.ts +0 -248
- package/src/core/meta/meta-store.ts +0 -11
- package/src/core/output/README.md +0 -73
- package/src/core/output/index.ts +0 -13
- package/src/core/output/run.ts +0 -91
- package/src/core/output/types.ts +0 -122
- package/src/core/parser/dynamic-values.ts +0 -160
- package/src/core/parser/env-interpolation.ts +0 -104
- package/src/core/parser/filter.ts +0 -97
- package/src/core/parser/schema.ts +0 -359
- package/src/core/parser/types.ts +0 -117
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +0 -181
- package/src/core/probe/bootstrap.ts +0 -34
- package/src/core/probe/dry-run-envelope.ts +0 -61
- package/src/core/probe/mass-assignment/classify.ts +0 -175
- package/src/core/probe/mass-assignment/cleanup.ts +0 -52
- package/src/core/probe/mass-assignment/digest.ts +0 -114
- package/src/core/probe/mass-assignment/orchestrator.ts +0 -459
- package/src/core/probe/mass-assignment/regression.ts +0 -141
- package/src/core/probe/mass-assignment/suspects.ts +0 -92
- package/src/core/probe/mass-assignment/types.ts +0 -135
- package/src/core/probe/mass-assignment-probe-class.ts +0 -198
- package/src/core/probe/mass-assignment-probe.ts +0 -27
- package/src/core/probe/mass-assignment-template.ts +0 -240
- package/src/core/probe/method-probe.ts +0 -164
- package/src/core/probe/method-shared.ts +0 -69
- package/src/core/probe/negative-probe.ts +0 -691
- package/src/core/probe/orphan-tracker.ts +0 -188
- package/src/core/probe/path-discovery.ts +0 -439
- package/src/core/probe/probe-harness.ts +0 -119
- package/src/core/probe/registry.ts +0 -89
- package/src/core/probe/runner.ts +0 -136
- package/src/core/probe/security/baseline.ts +0 -174
- package/src/core/probe/security/classify.ts +0 -341
- package/src/core/probe/security/cleanup.ts +0 -125
- package/src/core/probe/security/detectors.ts +0 -71
- package/src/core/probe/security/digest.ts +0 -104
- package/src/core/probe/security/orchestrator.ts +0 -398
- package/src/core/probe/security/regression.ts +0 -103
- package/src/core/probe/security/types.ts +0 -151
- package/src/core/probe/security-probe-class.ts +0 -207
- package/src/core/probe/security-probe.ts +0 -32
- package/src/core/probe/shared.ts +0 -531
- package/src/core/probe/static-probe-class.ts +0 -125
- package/src/core/probe/types.ts +0 -165
- package/src/core/probe/verdict-aggregator.ts +0 -33
- package/src/core/probe/webhooks-probe.ts +0 -282
- package/src/core/reporter/console.ts +0 -240
- package/src/core/reporter/index.ts +0 -22
- package/src/core/reporter/json.ts +0 -22
- package/src/core/reporter/junit.ts +0 -93
- package/src/core/reporter/ndjson.ts +0 -37
- package/src/core/reporter/types.ts +0 -15
- package/src/core/runner/assertions.ts +0 -383
- package/src/core/runner/async-pool.ts +0 -108
- package/src/core/runner/auth-path.ts +0 -8
- package/src/core/runner/ci-context.ts +0 -72
- package/src/core/runner/executor.ts +0 -652
- package/src/core/runner/expr-eval.ts +0 -41
- package/src/core/runner/form-encode.ts +0 -41
- package/src/core/runner/http-client.ts +0 -224
- package/src/core/runner/learn-drift.ts +0 -293
- package/src/core/runner/preflight-vars.ts +0 -153
- package/src/core/runner/progress-tracker.ts +0 -73
- package/src/core/runner/rate-limiter.ts +0 -185
- package/src/core/runner/run-kind.ts +0 -45
- package/src/core/runner/schema-validator.ts +0 -308
- package/src/core/runner/send-request.ts +0 -232
- package/src/core/runner/transforms.ts +0 -65
- package/src/core/runner/types.ts +0 -94
- package/src/core/secrets/registry.ts +0 -164
- package/src/core/secrets/secrets-file.ts +0 -115
- package/src/core/selectors/operation-filter.ts +0 -144
- package/src/core/setup-api.ts +0 -590
- package/src/core/severity/category.ts +0 -94
- package/src/core/severity/index.ts +0 -58
- package/src/core/spec/infer-schema.ts +0 -102
- package/src/core/spec/layers.ts +0 -154
- package/src/core/spec/merge-specs.ts +0 -156
- package/src/core/spec/schema-from-runs.ts +0 -117
- package/src/core/spec/schema-overlay.ts +0 -130
- package/src/core/util/ajv.ts +0 -13
- package/src/core/util/format-eta.ts +0 -21
- package/src/core/util/headers.ts +0 -9
- package/src/core/util/url.ts +0 -24
- package/src/core/utils.ts +0 -13
- package/src/core/workspace/config.ts +0 -129
- package/src/core/workspace/fixture-gap-report.ts +0 -84
- package/src/core/workspace/fixture-gaps.ts +0 -71
- package/src/core/workspace/manifest.ts +0 -283
- package/src/core/workspace/output-rotation.ts +0 -62
- package/src/core/workspace/root.ts +0 -96
- package/src/core/workspace/triage-path.ts +0 -87
- package/src/db/lint-runs.ts +0 -47
- package/src/db/migrate.ts +0 -128
- package/src/db/migrations/0001_run_kind.sql +0 -25
- package/src/db/migrations/0002_run_kind_request.sql +0 -59
- package/src/db/migrations/sql.d.ts +0 -4
- package/src/db/queries/collections.ts +0 -133
- package/src/db/queries/coverage.ts +0 -9
- package/src/db/queries/dashboard.ts +0 -59
- package/src/db/queries/results.ts +0 -216
- package/src/db/queries/runs.ts +0 -289
- package/src/db/queries/sessions.ts +0 -42
- package/src/db/queries/settings.ts +0 -28
- package/src/db/queries/types.ts +0 -172
- package/src/db/queries.ts +0 -75
- 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
|
-
};
|
package/src/core/checks/index.ts
DELETED
|
@@ -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";
|
package/src/core/checks/mode.ts
DELETED
|
@@ -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
|
-
}
|