@kirrosh/zond 0.22.0 → 0.26.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 +811 -0
- package/README.md +59 -6
- package/package.json +9 -7
- package/src/CLAUDE.md +112 -0
- package/src/cli/argv.ts +122 -0
- package/src/cli/commands/add-api.ts +146 -0
- package/src/cli/commands/api/annotate/idempotency.ts +59 -0
- package/src/cli/commands/api/annotate/index.ts +880 -0
- package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
- package/src/cli/commands/api/annotate/overlay.ts +206 -0
- package/src/cli/commands/api/annotate/pagination.ts +64 -0
- package/src/cli/commands/api/annotate/prompts.ts +220 -0
- package/src/cli/commands/api/annotate/readback.ts +58 -0
- package/src/cli/commands/api/annotate/resources.ts +91 -0
- package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
- package/src/cli/commands/audit.ts +786 -0
- package/src/cli/commands/catalog.ts +35 -0
- package/src/cli/commands/check.ts +361 -0
- package/src/cli/commands/checks.ts +1072 -0
- package/src/cli/commands/ci-init.ts +43 -0
- package/src/cli/commands/clean.ts +212 -0
- package/src/cli/commands/cleanup.ts +236 -0
- package/src/cli/commands/completions.ts +16 -0
- package/src/cli/commands/coverage.ts +823 -132
- package/src/cli/commands/db.ts +486 -12
- package/src/cli/commands/describe.ts +37 -2
- package/src/cli/commands/discover.ts +1356 -0
- package/src/cli/commands/doctor.ts +661 -0
- package/src/cli/commands/fixtures.ts +402 -0
- package/src/cli/commands/generate.ts +438 -47
- package/src/cli/commands/init/bootstrap.ts +34 -2
- package/src/cli/commands/{init.ts → init/index.ts} +99 -5
- package/src/cli/commands/init/skills.ts +99 -3
- package/src/cli/commands/init/templates/agents.md +77 -64
- package/src/cli/commands/init/templates/skills/warm-up-target.md +122 -0
- package/src/cli/commands/init/templates/skills/zond-checks.md +621 -0
- package/src/cli/commands/init/templates/skills/zond-seed.md +114 -0
- package/src/cli/commands/init/templates/skills/zond-triage.md +272 -0
- package/src/cli/commands/init/templates/skills/zond.md +802 -125
- package/src/cli/commands/init/templates/zond-config.yml +8 -9
- package/src/cli/commands/prepare-fixtures.ts +97 -0
- package/src/cli/commands/probe/_seed-bodies.ts +52 -0
- package/src/cli/commands/probe/mass-assignment.ts +594 -0
- package/src/cli/commands/probe/security.ts +537 -0
- package/src/cli/commands/probe/static.ts +255 -0
- package/src/cli/commands/probe/webhooks.ts +163 -0
- package/src/cli/commands/probe.ts +535 -0
- package/src/cli/commands/reference.ts +87 -0
- package/src/cli/commands/refresh-api.ts +227 -0
- package/src/cli/commands/remove-api.ts +150 -0
- package/src/cli/commands/report-bundle.ts +310 -0
- package/src/cli/commands/report.ts +241 -0
- package/src/cli/commands/request.ts +495 -4
- package/src/cli/commands/run.ts +870 -53
- package/src/cli/commands/schema-from-runs.ts +128 -0
- package/src/cli/commands/secrets.ts +133 -0
- package/src/cli/commands/session.ts +244 -0
- package/src/cli/commands/use.ts +18 -1
- package/src/cli/index.ts +20 -3
- package/src/cli/json-envelope.ts +92 -3
- package/src/cli/json-schemas.ts +314 -0
- package/src/cli/output.ts +17 -1
- package/src/cli/program.ts +199 -635
- package/src/cli/resolve.ts +105 -0
- package/src/cli/safe-live.ts +24 -0
- package/src/cli/status-filter.ts +114 -0
- package/src/cli/util/api-context.ts +85 -0
- package/src/cli/version.ts +5 -0
- package/src/core/audit/persist.ts +183 -0
- package/src/core/checks/budget.ts +59 -0
- package/src/core/checks/checks/_crud-helpers.ts +133 -0
- package/src/core/checks/checks/_negative_mutator.ts +133 -0
- package/src/core/checks/checks/_readback-helpers.ts +133 -0
- package/src/core/checks/checks/content_type_conformance.ts +39 -0
- package/src/core/checks/checks/cross_call_references.ts +147 -0
- package/src/core/checks/checks/cursor_boundary_fuzzing.ts +219 -0
- package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
- package/src/core/checks/checks/idempotency_replay.ts +242 -0
- package/src/core/checks/checks/ignored_auth.ts +254 -0
- package/src/core/checks/checks/index.ts +68 -0
- package/src/core/checks/checks/lifecycle_transitions.ts +416 -0
- package/src/core/checks/checks/missing_required_header.ts +40 -0
- package/src/core/checks/checks/negative_data_rejection.ts +148 -0
- package/src/core/checks/checks/not_a_server_error.ts +35 -0
- package/src/core/checks/checks/open_cors_on_sensitive.ts +160 -0
- package/src/core/checks/checks/pagination_invariants.ts +419 -0
- package/src/core/checks/checks/positive_data_acceptance.ts +33 -0
- package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
- package/src/core/checks/checks/response_headers_conformance.ts +74 -0
- package/src/core/checks/checks/response_schema_conformance.ts +30 -0
- package/src/core/checks/checks/status_code_conformance.ts +132 -0
- package/src/core/checks/checks/unsupported_method.ts +63 -0
- package/src/core/checks/checks/use_after_free.ts +78 -0
- package/src/core/checks/index.ts +30 -0
- package/src/core/checks/mode.ts +82 -0
- package/src/core/checks/recommended-action.ts +68 -0
- package/src/core/checks/registry.ts +78 -0
- package/src/core/checks/runner.ts +1461 -0
- package/src/core/checks/sarif.ts +230 -0
- package/src/core/checks/spec-findings.ts +308 -0
- package/src/core/checks/stateful.ts +121 -0
- package/src/core/checks/types.ts +305 -0
- package/src/core/checks/zond-extensions.ts +73 -0
- package/src/core/classifier/recommended-action.ts +251 -0
- package/src/core/context/current.ts +22 -6
- package/src/core/context/session.ts +78 -0
- package/src/core/coverage/loader.ts +216 -0
- package/src/core/coverage/reasons.ts +300 -0
- package/src/core/diagnostics/db-analysis.ts +293 -59
- package/src/core/diagnostics/failure-class.ts +140 -0
- package/src/core/diagnostics/failure-hints.ts +88 -89
- package/src/core/diagnostics/spec-pointer.ts +99 -0
- package/src/core/diagnostics/suggested-fixes.ts +155 -0
- package/src/core/exporter/case-study/index.ts +270 -0
- package/src/core/exporter/curl.ts +40 -0
- package/src/core/exporter/exporter.ts +48 -0
- package/src/core/exporter/html-report/escape.ts +24 -0
- package/src/core/exporter/html-report/index.ts +479 -0
- package/src/core/exporter/html-report/script.ts +100 -0
- package/src/core/exporter/html-report/styles.ts +408 -0
- package/src/core/generator/chunker.ts +38 -19
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/data-factory.ts +586 -22
- package/src/core/generator/describe.ts +1 -1
- package/src/core/generator/fixtures-builder.ts +332 -0
- package/src/core/generator/index.ts +5 -5
- package/src/core/generator/openapi-reader.ts +135 -7
- package/src/core/generator/path-param-disambig.ts +140 -0
- package/src/core/generator/resources-builder.ts +898 -0
- package/src/core/generator/schema-utils.ts +33 -3
- package/src/core/generator/serializer.ts +103 -13
- package/src/core/generator/suite-generator.ts +583 -122
- package/src/core/generator/types.ts +14 -0
- package/src/core/identity/identity-file.ts +0 -0
- package/src/core/lint/affects.ts +28 -0
- package/src/core/lint/config.ts +96 -0
- package/src/core/lint/format.ts +42 -0
- package/src/core/lint/index.ts +94 -0
- package/src/core/lint/reporter.ts +128 -0
- package/src/core/lint/rules/consistency.ts +158 -0
- package/src/core/lint/rules/heuristics.ts +97 -0
- package/src/core/lint/rules/strictness.ts +109 -0
- package/src/core/lint/types.ts +96 -0
- package/src/core/lint/walker.ts +248 -0
- package/src/core/meta/meta-store.ts +6 -73
- package/src/core/output/README.md +73 -0
- package/src/core/output/index.ts +13 -0
- package/src/core/output/run.ts +91 -0
- package/src/core/output/types.ts +122 -0
- package/src/core/parser/dynamic-values.ts +160 -0
- package/src/core/parser/env-interpolation.ts +104 -0
- package/src/core/parser/filter.ts +57 -0
- package/src/core/parser/schema.ts +129 -4
- package/src/core/parser/types.ts +19 -1
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +58 -12
- package/src/core/probe/bootstrap.ts +34 -0
- package/src/core/probe/dry-run-envelope.ts +61 -0
- package/src/core/probe/mass-assignment/classify.ts +175 -0
- package/src/core/probe/mass-assignment/cleanup.ts +52 -0
- package/src/core/probe/mass-assignment/digest.ts +114 -0
- package/src/core/probe/mass-assignment/orchestrator.ts +459 -0
- package/src/core/probe/mass-assignment/regression.ts +141 -0
- package/src/core/probe/mass-assignment/suspects.ts +92 -0
- package/src/core/probe/mass-assignment/types.ts +135 -0
- package/src/core/probe/mass-assignment-probe-class.ts +198 -0
- package/src/core/probe/mass-assignment-probe.ts +27 -0
- package/src/core/probe/mass-assignment-template.ts +240 -0
- package/src/core/probe/method-probe.ts +43 -76
- package/src/core/probe/method-shared.ts +69 -0
- package/src/core/probe/negative-probe.ts +183 -149
- package/src/core/probe/orphan-tracker.ts +188 -0
- package/src/core/probe/path-discovery.ts +439 -0
- package/src/core/probe/probe-harness.ts +119 -0
- package/src/core/probe/registry.ts +89 -0
- package/src/core/probe/runner.ts +136 -0
- package/src/core/probe/security/baseline.ts +174 -0
- package/src/core/probe/security/classify.ts +341 -0
- package/src/core/probe/security/cleanup.ts +125 -0
- package/src/core/probe/security/detectors.ts +71 -0
- package/src/core/probe/security/digest.ts +104 -0
- package/src/core/probe/security/orchestrator.ts +398 -0
- package/src/core/probe/security/regression.ts +103 -0
- package/src/core/probe/security/types.ts +151 -0
- package/src/core/probe/security-probe-class.ts +207 -0
- package/src/core/probe/security-probe.ts +32 -0
- package/src/core/probe/shared.ts +531 -0
- package/src/core/probe/static-probe-class.ts +125 -0
- package/src/core/probe/types.ts +165 -0
- package/src/core/probe/verdict-aggregator.ts +33 -0
- package/src/core/probe/webhooks-probe.ts +282 -0
- package/src/core/reporter/console.ts +41 -2
- package/src/core/reporter/index.ts +2 -3
- package/src/core/reporter/json.ts +11 -1
- package/src/core/reporter/junit.ts +27 -12
- package/src/core/reporter/ndjson.ts +37 -0
- package/src/core/reporter/types.ts +3 -0
- package/src/core/runner/assertions.ts +59 -2
- package/src/core/runner/async-pool.ts +108 -0
- package/src/core/runner/auth-path.ts +8 -0
- package/src/core/runner/ci-context.ts +72 -0
- package/src/core/runner/executor.ts +265 -36
- package/src/core/runner/form-encode.ts +41 -0
- package/src/core/runner/http-client.ts +112 -2
- package/src/core/runner/learn-drift.ts +293 -0
- package/src/core/runner/preflight-vars.ts +153 -0
- package/src/core/runner/progress-tracker.ts +73 -0
- package/src/core/runner/rate-limiter.ts +87 -33
- package/src/core/runner/run-kind.ts +45 -0
- package/src/core/runner/schema-validator.ts +308 -0
- package/src/core/runner/send-request.ts +158 -20
- package/src/core/runner/types.ts +44 -0
- package/src/core/secrets/registry.ts +164 -0
- package/src/core/secrets/secrets-file.ts +115 -0
- package/src/core/selectors/operation-filter.ts +144 -0
- package/src/core/setup-api.ts +457 -20
- package/src/core/severity/category.ts +94 -0
- package/src/core/severity/index.ts +58 -0
- package/src/core/spec/infer-schema.ts +102 -0
- package/src/core/spec/layers.ts +154 -0
- package/src/core/spec/merge-specs.ts +156 -0
- package/src/core/spec/schema-from-runs.ts +117 -0
- package/src/core/spec/schema-overlay.ts +130 -0
- package/src/core/util/ajv.ts +13 -0
- package/src/core/util/format-eta.ts +21 -0
- package/src/core/util/headers.ts +9 -0
- package/src/core/util/url.ts +24 -0
- package/src/core/utils.ts +5 -1
- package/src/core/workspace/config.ts +129 -0
- package/src/core/workspace/fixture-gap-report.ts +84 -0
- package/src/core/workspace/fixture-gaps.ts +71 -0
- package/src/core/workspace/manifest.ts +283 -0
- package/src/core/workspace/output-rotation.ts +62 -0
- package/src/core/workspace/root.ts +13 -11
- package/src/core/workspace/triage-path.ts +87 -0
- package/src/db/lint-runs.ts +47 -0
- package/src/db/migrate.ts +128 -0
- package/src/db/migrations/0001_run_kind.sql +25 -0
- package/src/db/migrations/0002_run_kind_request.sql +59 -0
- package/src/db/migrations/sql.d.ts +4 -0
- package/src/db/queries/collections.ts +133 -0
- package/src/db/queries/coverage.ts +9 -0
- package/src/db/queries/dashboard.ts +59 -0
- package/src/db/queries/results.ts +216 -0
- package/src/db/queries/runs.ts +289 -0
- package/src/db/queries/sessions.ts +42 -0
- package/src/db/queries/settings.ts +28 -0
- package/src/db/queries/types.ts +172 -0
- package/src/db/queries.ts +75 -802
- package/src/db/schema.ts +178 -50
- package/src/cli/commands/export.ts +0 -144
- package/src/cli/commands/guide.ts +0 -127
- package/src/cli/commands/init/templates/skills/scenarios.md +0 -97
- package/src/cli/commands/probe-methods.ts +0 -108
- package/src/cli/commands/probe-validation.ts +0 -124
- package/src/cli/commands/serve.ts +0 -114
- package/src/cli/commands/sync.ts +0 -268
- package/src/cli/commands/update.ts +0 -189
- package/src/cli/commands/validate.ts +0 -34
- package/src/core/diagnostics/render-md.ts +0 -112
- package/src/core/exporter/postman.ts +0 -963
- package/src/core/generator/guide-builder.ts +0 -253
- package/src/core/meta/types.ts +0 -19
- package/src/core/parser/index.ts +0 -21
- package/src/core/runner/execute-run.ts +0 -132
- package/src/core/runner/index.ts +0 -12
- package/src/core/sync/spec-differ.ts +0 -38
- package/src/web/data/collection-state.ts +0 -362
- package/src/web/routes/api.ts +0 -314
- package/src/web/routes/dashboard.ts +0 -350
- package/src/web/routes/runs.ts +0 -64
- package/src/web/schemas.ts +0 -121
- package/src/web/server.ts +0 -134
- package/src/web/static/htmx.min.cjs +0 -1
- package/src/web/static/style.css +0 -1148
- package/src/web/views/endpoints-tab.ts +0 -174
- package/src/web/views/explorer-tab.ts +0 -402
- package/src/web/views/health-strip.ts +0 -92
- package/src/web/views/layout.ts +0 -48
- package/src/web/views/results.ts +0 -210
- package/src/web/views/runs-tab.ts +0 -126
- package/src/web/views/suites-tab.ts +0 -181
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from "openapi-types";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Deep-clone an object, replacing circular references with
|
|
5
|
-
* Uses WeakSet to track visited objects.
|
|
4
|
+
* Deep-clone an object, replacing circular references with the vendor-extension
|
|
5
|
+
* sentinel `{ "x-circular": true }`. Uses WeakSet to track visited objects.
|
|
6
|
+
*
|
|
7
|
+
* Trade-off (intentional): we mark the SECOND visit of any shared object
|
|
8
|
+
* (post-`dereference()` $ref-target reused under multiple parents) as
|
|
9
|
+
* circular, not just true cycles. The alternative — a DFS path stack —
|
|
10
|
+
* preserves every duplicate fresh, but blows the GitHub spec from 14 MB
|
|
11
|
+
* to 106 MB on disk because shared `per_page`/`page`/`owner` params and
|
|
12
|
+
* response schemas are re-cloned for every endpoint that references them.
|
|
13
|
+
* Downstream tools that need param/schema visibility on every endpoint
|
|
14
|
+
* (e.g. `zond api annotate auto`, ARV-262) handle this by re-reading the
|
|
15
|
+
* source spec themselves rather than relying on `apis/<name>/spec.json`.
|
|
16
|
+
*
|
|
17
|
+
* Why a vendor extension and not `$ref`: the decycled doc is now written to
|
|
18
|
+
* disk (apis/<name>/spec.json) and re-read by `@readme/openapi-parser` in
|
|
19
|
+
* downstream commands (check spec, describe, generate). If the sentinel
|
|
20
|
+
* carried a `$ref` field, the parser would try to resolve its value as a
|
|
21
|
+
* JSON pointer / file path — e.g. `apis/stripe/[Circular]` — and fail
|
|
22
|
+
* (ARV-146). `x-*` keys are explicitly reserved for vendor extensions in
|
|
23
|
+
* OpenAPI 3.x and pass through every parser untouched.
|
|
6
24
|
*/
|
|
7
25
|
export function decycleSchema(obj: unknown): unknown {
|
|
8
26
|
const seen = new WeakSet<object>();
|
|
@@ -16,7 +34,19 @@ export function decycleSchema(obj: unknown): unknown {
|
|
|
16
34
|
|
|
17
35
|
const obj = value as Record<string, unknown>;
|
|
18
36
|
if (seen.has(obj)) {
|
|
19
|
-
|
|
37
|
+
// ARV-262: parameter-shaped objects (have both `name` and `in`)
|
|
38
|
+
// appear post-`dereference()` as shared identities across every
|
|
39
|
+
// endpoint that $ref'd them — `per_page`, `page`, `owner`, etc.
|
|
40
|
+
// Past the first visit, the full `{"x-circular": true}` stub
|
|
41
|
+
// erases name+in and downstream tools (annotate auto, generator)
|
|
42
|
+
// can no longer tell what query/header that slot represented.
|
|
43
|
+
// Preserve name+in on revisit; schema/required/description still
|
|
44
|
+
// collapse so the bulk-size trade-off is unchanged (revisits
|
|
45
|
+
// grow by ~30 B each, vs. ~7×-blowup if we cloned everything).
|
|
46
|
+
if (typeof obj.name === "string" && typeof obj.in === "string") {
|
|
47
|
+
return { name: obj.name, in: obj.in };
|
|
48
|
+
}
|
|
49
|
+
return { "x-circular": true };
|
|
20
50
|
}
|
|
21
51
|
seen.add(obj);
|
|
22
52
|
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { resolve } from "path";
|
|
2
|
+
import type { SourceMetadata } from "../parser/types.ts";
|
|
2
3
|
|
|
3
4
|
// ──────────────────────────────────────────────
|
|
4
5
|
// Utility functions (moved from skeleton.ts)
|
|
5
6
|
// ──────────────────────────────────────────────
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
function isRelativeUrl(url: string): boolean {
|
|
8
9
|
return url.startsWith("/") && !url.includes("://");
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
function resolveSpecPath(specPath: string): string {
|
|
12
13
|
if (specPath.startsWith("http://") || specPath.startsWith("https://")) {
|
|
13
14
|
return specPath;
|
|
14
15
|
}
|
|
15
16
|
return resolve(specPath);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
function sanitizeEnvName(name: string): string {
|
|
19
20
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -25,6 +26,7 @@ export function sanitizeEnvName(name: string): string {
|
|
|
25
26
|
|
|
26
27
|
export interface RawStep {
|
|
27
28
|
name: string;
|
|
29
|
+
source?: SourceMetadata;
|
|
28
30
|
[methodKey: string]: unknown;
|
|
29
31
|
expect: {
|
|
30
32
|
status?: number | number[];
|
|
@@ -37,6 +39,7 @@ export interface RawSuite {
|
|
|
37
39
|
name: string;
|
|
38
40
|
setup?: boolean;
|
|
39
41
|
tags?: string[];
|
|
42
|
+
source?: SourceMetadata;
|
|
40
43
|
folder?: string;
|
|
41
44
|
fileStem?: string;
|
|
42
45
|
base_url?: string;
|
|
@@ -66,11 +69,20 @@ export function serializeSuite(suite: RawSuite): string {
|
|
|
66
69
|
lines.push(` ${hk}: ${yamlScalar(String(hv))}`);
|
|
67
70
|
}
|
|
68
71
|
}
|
|
72
|
+
if (suite.source && Object.keys(suite.source).length > 0) {
|
|
73
|
+
lines.push("source:");
|
|
74
|
+
serializeValue(suite.source, 1, lines);
|
|
75
|
+
}
|
|
69
76
|
lines.push("tests:");
|
|
70
77
|
|
|
71
78
|
for (const test of suite.tests) {
|
|
72
79
|
lines.push(` - name: ${yamlScalar(test.name)}`);
|
|
73
80
|
|
|
81
|
+
if (test.source && Object.keys(test.source).length > 0) {
|
|
82
|
+
lines.push(" source:");
|
|
83
|
+
serializeValue(test.source, 3, lines);
|
|
84
|
+
}
|
|
85
|
+
|
|
74
86
|
// Write method-as-key (the shorthand)
|
|
75
87
|
for (const method of ["GET", "POST", "PUT", "PATCH", "DELETE"]) {
|
|
76
88
|
if (method in test) {
|
|
@@ -92,6 +104,27 @@ export function serializeSuite(suite: RawSuite): string {
|
|
|
92
104
|
serializeValue(test.json, 3, lines);
|
|
93
105
|
}
|
|
94
106
|
|
|
107
|
+
// ARV-149: form body (application/x-www-form-urlencoded). The runner
|
|
108
|
+
// serialises this via URLSearchParams; values are flat strings with
|
|
109
|
+
// bracket notation for nested fields (e.g. `address[line1]`).
|
|
110
|
+
//
|
|
111
|
+
// ARV-162 (round-08 F19): form values are ALWAYS strings on the wire —
|
|
112
|
+
// x-www-form-urlencoded has no native numbers/bools/nulls. YAML parsing
|
|
113
|
+
// `phone: +1234567890` or `width: 12.5` as int/float makes `zond check
|
|
114
|
+
// tests` reject the suite ("expected string, received number"), and
|
|
115
|
+
// `zond run` silently skipped 21/68 generated Stripe suites this way.
|
|
116
|
+
// Force-quote every value regardless of shape; key still uses yamlScalar
|
|
117
|
+
// because bracket keys (`address[line1]`) need quoting too.
|
|
118
|
+
if (test.form !== undefined && typeof test.form === "object" && test.form !== null) {
|
|
119
|
+
const formEntries = Object.entries(test.form as Record<string, unknown>);
|
|
120
|
+
if (formEntries.length > 0) {
|
|
121
|
+
lines.push(" form:");
|
|
122
|
+
for (const [fk, fv] of formEntries) {
|
|
123
|
+
lines.push(` ${yamlScalar(fk)}: "${escapeYamlDoubleQuoted(String(fv))}"`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
95
128
|
// query
|
|
96
129
|
if (test.query) {
|
|
97
130
|
lines.push(" query:");
|
|
@@ -151,7 +184,10 @@ export function serializeSuite(suite: RawSuite): string {
|
|
|
151
184
|
lines.push(` ${rk}:`);
|
|
152
185
|
serializeValue(rv, 6, lines);
|
|
153
186
|
} else {
|
|
154
|
-
|
|
187
|
+
// Preserve scalar types — `not_equals: true` (boolean) must not
|
|
188
|
+
// become `not_equals: "true"` (string), or assertions silently
|
|
189
|
+
// mistype against real boolean fields.
|
|
190
|
+
lines.push(` ${rk}: ${formatInlineValue(rv)}`);
|
|
155
191
|
}
|
|
156
192
|
}
|
|
157
193
|
}
|
|
@@ -182,16 +218,24 @@ function serializeValue(value: unknown, indent: number, lines: string[]): void {
|
|
|
182
218
|
if (entries.length > 0) {
|
|
183
219
|
const [firstKey, firstVal] = entries[0]!;
|
|
184
220
|
if (typeof firstVal === "object" && firstVal !== null) {
|
|
185
|
-
|
|
186
|
-
|
|
221
|
+
if (isEmptyContainer(firstVal)) {
|
|
222
|
+
lines.push(`${prefix}- ${firstKey}: ${Array.isArray(firstVal) ? "[]" : "{}"}`);
|
|
223
|
+
} else {
|
|
224
|
+
lines.push(`${prefix}- ${firstKey}:`);
|
|
225
|
+
serializeValue(firstVal, indent + 1, lines);
|
|
226
|
+
}
|
|
187
227
|
} else {
|
|
188
228
|
lines.push(`${prefix}- ${firstKey}: ${formatInlineValue(firstVal)}`);
|
|
189
229
|
}
|
|
190
230
|
for (let i = 1; i < entries.length; i++) {
|
|
191
231
|
const [k, v] = entries[i]!;
|
|
192
232
|
if (typeof v === "object" && v !== null) {
|
|
193
|
-
|
|
194
|
-
|
|
233
|
+
if (isEmptyContainer(v)) {
|
|
234
|
+
lines.push(`${prefix} ${k}: ${Array.isArray(v) ? "[]" : "{}"}`);
|
|
235
|
+
} else {
|
|
236
|
+
lines.push(`${prefix} ${k}:`);
|
|
237
|
+
serializeValue(v, indent + 1, lines);
|
|
238
|
+
}
|
|
195
239
|
} else {
|
|
196
240
|
lines.push(`${prefix} ${k}: ${formatInlineValue(v)}`);
|
|
197
241
|
}
|
|
@@ -209,8 +253,12 @@ function serializeValue(value: unknown, indent: number, lines: string[]): void {
|
|
|
209
253
|
if (typeof value === "object") {
|
|
210
254
|
for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
|
|
211
255
|
if (typeof val === "object" && val !== null) {
|
|
212
|
-
|
|
213
|
-
|
|
256
|
+
if (isEmptyContainer(val)) {
|
|
257
|
+
lines.push(`${prefix}${key}: ${Array.isArray(val) ? "[]" : "{}"}`);
|
|
258
|
+
} else {
|
|
259
|
+
lines.push(`${prefix}${key}:`);
|
|
260
|
+
serializeValue(val, indent + 1, lines);
|
|
261
|
+
}
|
|
214
262
|
} else {
|
|
215
263
|
lines.push(`${prefix}${key}: ${formatInlineValue(val)}`);
|
|
216
264
|
}
|
|
@@ -218,12 +266,32 @@ function serializeValue(value: unknown, indent: number, lines: string[]): void {
|
|
|
218
266
|
}
|
|
219
267
|
}
|
|
220
268
|
|
|
269
|
+
/** Empty `{}` / `[]` written as a bare `key:` (no value, no children) is
|
|
270
|
+
* re-parsed by YAML as `null` — sending `null` for an `object`-typed field
|
|
271
|
+
* guarantees 422 against strict APIs. Emit inline flow form to preserve type. */
|
|
272
|
+
function isEmptyContainer(val: unknown): boolean {
|
|
273
|
+
if (Array.isArray(val)) return val.length === 0;
|
|
274
|
+
if (typeof val === "object" && val !== null) return Object.keys(val).length === 0;
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
221
278
|
function formatInlineValue(val: unknown): string {
|
|
222
279
|
if (val === null || val === undefined) return "null";
|
|
223
280
|
if (typeof val === "string") return yamlScalar(val);
|
|
224
281
|
return String(val);
|
|
225
282
|
}
|
|
226
283
|
|
|
284
|
+
/** ARV-62 (feedback round-01 / F3): security probes emit attack payloads
|
|
285
|
+
* with raw CRLF (`crlf:`, header-injection) and other control bytes.
|
|
286
|
+
* When written into a double-quoted YAML scalar these *must* be escaped
|
|
287
|
+
* (`\r` / `\n` / `\t` / `\xNN`) — emitting the raw byte produces YAML
|
|
288
|
+
* that the parser rejects with "bad indentation of a mapping entry"
|
|
289
|
+
* (`zond run` then fails the whole suite at load-time before sending a
|
|
290
|
+
* single request). The check also covers `\r`, `\t`, `\x00–\x1f`, and
|
|
291
|
+
* `\x7f` so any other control byte that sneaks into a payload survives
|
|
292
|
+
* the YAML round-trip. */
|
|
293
|
+
// eslint-disable-next-line no-control-regex
|
|
294
|
+
const CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
|
|
227
295
|
function yamlScalar(value: string): string {
|
|
228
296
|
if (
|
|
229
297
|
value === "" ||
|
|
@@ -232,7 +300,6 @@ function yamlScalar(value: string): string {
|
|
|
232
300
|
value === "null" ||
|
|
233
301
|
value.includes(":") ||
|
|
234
302
|
value.includes("#") ||
|
|
235
|
-
value.includes("\n") ||
|
|
236
303
|
value.includes("'") ||
|
|
237
304
|
value.includes('"') ||
|
|
238
305
|
value.includes("{") ||
|
|
@@ -245,9 +312,32 @@ function yamlScalar(value: string): string {
|
|
|
245
312
|
value.startsWith("%") ||
|
|
246
313
|
value.startsWith("@") ||
|
|
247
314
|
value.startsWith("`") ||
|
|
248
|
-
/^\d+$/.test(value)
|
|
315
|
+
/^\d+$/.test(value) ||
|
|
316
|
+
CONTROL_BYTE_RE.test(value)
|
|
249
317
|
) {
|
|
250
|
-
return `"${value
|
|
318
|
+
return `"${escapeYamlDoubleQuoted(value)}"`;
|
|
251
319
|
}
|
|
252
320
|
return value;
|
|
253
321
|
}
|
|
322
|
+
|
|
323
|
+
function escapeYamlDoubleQuoted(value: string): string {
|
|
324
|
+
let out = "";
|
|
325
|
+
for (let i = 0; i < value.length; i++) {
|
|
326
|
+
const ch = value[i]!;
|
|
327
|
+
const code = ch.charCodeAt(0);
|
|
328
|
+
if (ch === "\\") { out += "\\\\"; continue; }
|
|
329
|
+
if (ch === '"') { out += '\\"'; continue; }
|
|
330
|
+
if (ch === "\n") { out += "\\n"; continue; }
|
|
331
|
+
if (ch === "\r") { out += "\\r"; continue; }
|
|
332
|
+
if (ch === "\t") { out += "\\t"; continue; }
|
|
333
|
+
if (code < 0x20 || code === 0x7f) {
|
|
334
|
+
// YAML 1.2 allows \xNN for any byte in (0..0xff); use this for any
|
|
335
|
+
// control character that doesn't have a dedicated short escape
|
|
336
|
+
// (covers \x00–\x1f minus the three handled above, plus DEL).
|
|
337
|
+
out += "\\x" + code.toString(16).padStart(2, "0");
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
out += ch;
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|