@kirrosh/zond 0.21.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.
- package/CHANGELOG.md +758 -3
- package/README.md +78 -15
- package/package.json +17 -10
- package/src/cli/argv.ts +122 -0
- package/src/cli/commands/add-api.ts +134 -0
- package/src/cli/commands/api/annotate/idempotency.ts +59 -0
- package/src/cli/commands/api/annotate/index.ts +525 -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 +60 -0
- package/src/cli/commands/api/annotate/prompts.ts +183 -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 +480 -0
- package/src/cli/commands/bootstrap.ts +710 -0
- package/src/cli/commands/catalog.ts +35 -0
- package/src/cli/commands/check.ts +348 -0
- package/src/cli/commands/checks.ts +756 -0
- package/src/cli/commands/ci-init.ts +55 -6
- package/src/cli/commands/clean.ts +212 -0
- package/src/cli/commands/cleanup.ts +262 -0
- package/src/cli/commands/completions.ts +192 -0
- package/src/cli/commands/coverage.ts +605 -132
- package/src/cli/commands/db.ts +180 -8
- package/src/cli/commands/describe.ts +37 -2
- package/src/cli/commands/discover.ts +1236 -0
- package/src/cli/commands/doctor.ts +607 -0
- package/src/cli/commands/fixtures.ts +402 -0
- package/src/cli/commands/generate.ts +420 -47
- package/src/cli/commands/init/agents-md.ts +61 -0
- package/src/cli/commands/init/bootstrap.ts +108 -0
- package/src/cli/commands/init/index.ts +244 -0
- package/src/cli/commands/init/skills.ts +98 -0
- package/src/cli/commands/init/templates/agents.md +77 -0
- package/src/cli/commands/init/templates/markdown.d.ts +4 -0
- package/src/cli/commands/init/templates/skills/zond-checks.md +397 -0
- package/src/cli/commands/init/templates/skills/zond-triage.md +210 -0
- package/src/cli/commands/init/templates/skills/zond.md +651 -0
- package/src/cli/commands/init/templates/zond-config.yml +14 -0
- package/src/cli/commands/prepare-fixtures.ts +135 -0
- package/src/cli/commands/probe/mass-assignment.ts +503 -0
- package/src/cli/commands/probe/security.ts +454 -0
- package/src/cli/commands/probe/static.ts +255 -0
- package/src/cli/commands/probe/webhooks.ts +161 -0
- package/src/cli/commands/probe.ts +459 -0
- package/src/cli/commands/reference.ts +87 -0
- package/src/cli/commands/refresh-api.ts +169 -0
- package/src/cli/commands/remove-api.ts +150 -0
- package/src/cli/commands/report-bundle.ts +318 -0
- package/src/cli/commands/report.ts +241 -0
- package/src/cli/commands/request.ts +379 -4
- package/src/cli/commands/run.ts +911 -33
- package/src/cli/commands/session.ts +244 -0
- package/src/cli/commands/use.ts +74 -0
- package/src/cli/index.ts +36 -607
- package/src/cli/json-envelope.ts +112 -3
- package/src/cli/json-schemas.ts +263 -0
- package/src/cli/program.ts +218 -0
- package/src/cli/resolve.ts +105 -0
- package/src/cli/status-filter.ts +124 -0
- package/src/cli/util/api-context.ts +85 -0
- package/src/cli/version.ts +8 -0
- package/src/core/anti-fp/bootstrap.ts +34 -0
- package/src/core/anti-fp/index.ts +33 -0
- package/src/core/anti-fp/registry.ts +44 -0
- package/src/core/anti-fp/rules/baseline-echo.ts +74 -0
- package/src/core/anti-fp/rules/schemathesis/body_negation_becomes_valid.ts +52 -0
- package/src/core/anti-fp/rules/schemathesis/coverage_phase_boundary_positive.ts +38 -0
- package/src/core/anti-fp/rules/schemathesis/has_unverifiable_mutations.ts +35 -0
- package/src/core/anti-fp/rules/schemathesis/index.ts +24 -0
- package/src/core/anti-fp/rules/schemathesis/string_type_mutation_becomes_valid.ts +53 -0
- package/src/core/anti-fp/rules/subscription-gated/index.ts +11 -0
- package/src/core/anti-fp/rules/subscription-gated/paid-plan-403.ts +75 -0
- package/src/core/anti-fp/types.ts +68 -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 +134 -0
- package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
- package/src/core/checks/checks/idempotency_replay.ts +246 -0
- package/src/core/checks/checks/ignored_auth.ts +211 -0
- package/src/core/checks/checks/index.ts +65 -0
- package/src/core/checks/checks/lifecycle_transitions.ts +273 -0
- package/src/core/checks/checks/missing_required_header.ts +40 -0
- package/src/core/checks/checks/negative_data_rejection.ts +45 -0
- package/src/core/checks/checks/not_a_server_error.ts +27 -0
- package/src/core/checks/checks/open_cors_on_sensitive.ts +131 -0
- package/src/core/checks/checks/pagination_invariants.ts +238 -0
- package/src/core/checks/checks/positive_data_acceptance.ts +36 -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 +61 -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 +79 -0
- package/src/core/checks/recommended-action.ts +64 -0
- package/src/core/checks/registry.ts +78 -0
- package/src/core/checks/runner.ts +874 -0
- package/src/core/checks/sarif.ts +230 -0
- package/src/core/checks/stateful.ts +121 -0
- package/src/core/checks/types.ts +189 -0
- package/src/core/classifier/recommended-action.ts +222 -0
- package/src/core/context/current.ts +51 -0
- package/src/core/context/session.ts +78 -0
- package/src/core/coverage/loader.ts +185 -0
- package/src/core/coverage/reasons.ts +300 -0
- package/src/core/diagnostics/db-analysis.ts +161 -12
- package/src/core/diagnostics/failure-class.ts +120 -0
- package/src/core/diagnostics/failure-hints.ts +212 -9
- package/src/core/diagnostics/spec-pointer.ts +99 -0
- package/src/core/diagnostics/suggested-fixes.ts +156 -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 +53 -15
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/create-body.ts +89 -0
- package/src/core/generator/data-factory.ts +490 -33
- package/src/core/generator/describe.ts +1 -1
- package/src/core/generator/fixtures-builder.ts +325 -0
- package/src/core/generator/index.ts +7 -5
- package/src/core/generator/openapi-reader.ts +55 -3
- package/src/core/generator/path-param-disambig.ts +114 -0
- package/src/core/generator/resources-builder.ts +648 -0
- package/src/core/generator/schema-utils.ts +11 -3
- package/src/core/generator/serializer.ts +114 -15
- package/src/core/generator/suite-generator.ts +484 -77
- package/src/core/generator/types.ts +8 -0
- package/src/core/identity/identity-file.ts +129 -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 +91 -0
- package/src/core/output/index.ts +13 -0
- package/src/core/output/run.ts +126 -0
- package/src/core/output/types.ts +129 -0
- package/src/core/parser/env-interpolation.ts +104 -0
- package/src/core/parser/filter.ts +57 -0
- package/src/core/parser/schema.ts +132 -5
- package/src/core/parser/types.ts +29 -2
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +108 -13
- package/src/core/probe/bootstrap.ts +34 -0
- package/src/core/probe/dry-run-envelope.ts +57 -0
- package/src/core/probe/mass-assignment-probe-class.ts +198 -0
- package/src/core/probe/mass-assignment-probe.ts +1122 -0
- package/src/core/probe/mass-assignment-template.ts +212 -0
- package/src/core/probe/method-probe.ts +164 -0
- package/src/core/probe/method-shared.ts +69 -0
- package/src/core/probe/negative-probe.ts +691 -0
- package/src/core/probe/orphan-tracker.ts +188 -0
- package/src/core/probe/path-discovery.ts +440 -0
- package/src/core/probe/probe-harness.ts +120 -0
- package/src/core/probe/registry.ts +89 -0
- package/src/core/probe/runner.ts +136 -0
- package/src/core/probe/security-probe-class.ts +201 -0
- package/src/core/probe/security-probe.ts +1453 -0
- package/src/core/probe/shared.ts +505 -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 +284 -0
- package/src/core/reporter/console.ts +69 -4
- package/src/core/reporter/index.ts +2 -3
- package/src/core/reporter/json.ts +15 -2
- 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 +62 -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 +391 -52
- package/src/core/runner/form-encode.ts +51 -0
- package/src/core/runner/http-client.ts +115 -7
- package/src/core/runner/learn-drift.ts +293 -0
- package/src/core/runner/preflight-vars.ts +149 -0
- package/src/core/runner/progress-tracker.ts +73 -0
- package/src/core/runner/rate-limiter.ts +203 -0
- package/src/core/runner/run-kind.ts +39 -0
- package/src/core/runner/schema-validator.ts +312 -0
- package/src/core/runner/send-request.ts +153 -20
- package/src/core/runner/types.ts +38 -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 +419 -17
- package/src/core/severity/category.ts +94 -0
- package/src/core/severity/index.ts +121 -0
- package/src/core/spec/layers.ts +154 -0
- package/src/core/util/format-eta.ts +21 -0
- package/src/core/utils.ts +5 -1
- package/src/core/workspace/config.ts +129 -0
- package/src/core/workspace/manifest.ts +283 -0
- package/src/core/workspace/output-rotation.ts +62 -0
- package/src/core/workspace/root.ts +94 -0
- package/src/core/workspace/triage-path.ts +87 -0
- package/src/db/lint-runs.ts +47 -0
- package/src/db/migrate.ts +126 -0
- package/src/db/migrations/0001_run_kind.sql +25 -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 +128 -0
- package/src/db/queries/runs.ts +235 -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 +72 -802
- package/src/db/schema.ts +179 -48
- package/src/cli/commands/export.ts +0 -144
- package/src/cli/commands/guide.ts +0 -127
- package/src/cli/commands/init.ts +0 -57
- package/src/cli/commands/serve.ts +0 -81
- package/src/cli/commands/sync.ts +0 -269
- package/src/cli/commands/update.ts +0 -189
- package/src/cli/commands/validate.ts +0 -34
- package/src/core/exporter/postman.ts +0 -963
- package/src/core/generator/guide-builder.ts +0 -253
- package/src/core/meta/types.ts +0 -21
- 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,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,9 +26,10 @@ 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
|
-
status?: number;
|
|
32
|
+
status?: number | number[];
|
|
31
33
|
body?: Record<string, Record<string, string>>;
|
|
32
34
|
headers?: Record<string, unknown>;
|
|
33
35
|
};
|
|
@@ -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:");
|
|
@@ -103,6 +136,11 @@ export function serializeSuite(suite: RawSuite): string {
|
|
|
103
136
|
lines.push(` skip_if: ${yamlScalar(String(test.skip_if))}`);
|
|
104
137
|
}
|
|
105
138
|
|
|
139
|
+
// always (cleanup steps that survive cascade-skip on tainted captures)
|
|
140
|
+
if (test.always === true) {
|
|
141
|
+
lines.push(" always: true");
|
|
142
|
+
}
|
|
143
|
+
|
|
106
144
|
// retry_until
|
|
107
145
|
if (test.retry_until && typeof test.retry_until === "object") {
|
|
108
146
|
const rt = test.retry_until as Record<string, unknown>;
|
|
@@ -131,7 +169,11 @@ export function serializeSuite(suite: RawSuite): string {
|
|
|
131
169
|
if (hasExpect) {
|
|
132
170
|
lines.push(" expect:");
|
|
133
171
|
if (test.expect.status !== undefined) {
|
|
134
|
-
|
|
172
|
+
if (Array.isArray(test.expect.status)) {
|
|
173
|
+
lines.push(` status: [${test.expect.status.join(", ")}]`);
|
|
174
|
+
} else {
|
|
175
|
+
lines.push(` status: ${test.expect.status}`);
|
|
176
|
+
}
|
|
135
177
|
}
|
|
136
178
|
if (test.expect.body) {
|
|
137
179
|
lines.push(" body:");
|
|
@@ -142,7 +184,10 @@ export function serializeSuite(suite: RawSuite): string {
|
|
|
142
184
|
lines.push(` ${rk}:`);
|
|
143
185
|
serializeValue(rv, 6, lines);
|
|
144
186
|
} else {
|
|
145
|
-
|
|
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)}`);
|
|
146
191
|
}
|
|
147
192
|
}
|
|
148
193
|
}
|
|
@@ -173,16 +218,24 @@ function serializeValue(value: unknown, indent: number, lines: string[]): void {
|
|
|
173
218
|
if (entries.length > 0) {
|
|
174
219
|
const [firstKey, firstVal] = entries[0]!;
|
|
175
220
|
if (typeof firstVal === "object" && firstVal !== null) {
|
|
176
|
-
|
|
177
|
-
|
|
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
|
+
}
|
|
178
227
|
} else {
|
|
179
228
|
lines.push(`${prefix}- ${firstKey}: ${formatInlineValue(firstVal)}`);
|
|
180
229
|
}
|
|
181
230
|
for (let i = 1; i < entries.length; i++) {
|
|
182
231
|
const [k, v] = entries[i]!;
|
|
183
232
|
if (typeof v === "object" && v !== null) {
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
}
|
|
186
239
|
} else {
|
|
187
240
|
lines.push(`${prefix} ${k}: ${formatInlineValue(v)}`);
|
|
188
241
|
}
|
|
@@ -200,8 +253,12 @@ function serializeValue(value: unknown, indent: number, lines: string[]): void {
|
|
|
200
253
|
if (typeof value === "object") {
|
|
201
254
|
for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
|
|
202
255
|
if (typeof val === "object" && val !== null) {
|
|
203
|
-
|
|
204
|
-
|
|
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
|
+
}
|
|
205
262
|
} else {
|
|
206
263
|
lines.push(`${prefix}${key}: ${formatInlineValue(val)}`);
|
|
207
264
|
}
|
|
@@ -209,12 +266,32 @@ function serializeValue(value: unknown, indent: number, lines: string[]): void {
|
|
|
209
266
|
}
|
|
210
267
|
}
|
|
211
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
|
+
|
|
212
278
|
function formatInlineValue(val: unknown): string {
|
|
213
279
|
if (val === null || val === undefined) return "null";
|
|
214
280
|
if (typeof val === "string") return yamlScalar(val);
|
|
215
281
|
return String(val);
|
|
216
282
|
}
|
|
217
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]/;
|
|
218
295
|
function yamlScalar(value: string): string {
|
|
219
296
|
if (
|
|
220
297
|
value === "" ||
|
|
@@ -223,7 +300,6 @@ function yamlScalar(value: string): string {
|
|
|
223
300
|
value === "null" ||
|
|
224
301
|
value.includes(":") ||
|
|
225
302
|
value.includes("#") ||
|
|
226
|
-
value.includes("\n") ||
|
|
227
303
|
value.includes("'") ||
|
|
228
304
|
value.includes('"') ||
|
|
229
305
|
value.includes("{") ||
|
|
@@ -236,9 +312,32 @@ function yamlScalar(value: string): string {
|
|
|
236
312
|
value.startsWith("%") ||
|
|
237
313
|
value.startsWith("@") ||
|
|
238
314
|
value.startsWith("`") ||
|
|
239
|
-
/^\d+$/.test(value)
|
|
315
|
+
/^\d+$/.test(value) ||
|
|
316
|
+
CONTROL_BYTE_RE.test(value)
|
|
240
317
|
) {
|
|
241
|
-
return `"${value
|
|
318
|
+
return `"${escapeYamlDoubleQuoted(value)}"`;
|
|
242
319
|
}
|
|
243
320
|
return value;
|
|
244
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
|
+
}
|