@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { EndpointInfo, SecuritySchemeInfo } from "../../generator/types.ts";
|
|
2
|
+
import { flattenToFormFields } from "../../runner/form-encode.ts";
|
|
3
|
+
import type { RawStep, RawSuite } from "../../generator/serializer.ts";
|
|
4
|
+
import {
|
|
5
|
+
captureFieldFor,
|
|
6
|
+
convertPath,
|
|
7
|
+
endpointStem,
|
|
8
|
+
findDeleteCounterpart,
|
|
9
|
+
findGetByIdCounterpart,
|
|
10
|
+
getAuthHeaders,
|
|
11
|
+
} from "../shared.ts";
|
|
12
|
+
import { findIdParam } from "./classify.ts";
|
|
13
|
+
import type {
|
|
14
|
+
EndpointVerdict,
|
|
15
|
+
MassAssignmentResult,
|
|
16
|
+
} from "./types.ts";
|
|
17
|
+
|
|
18
|
+
const ACCEPTABLE_4XX = [400, 401, 403, 409, 415, 422];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Emit YAML suites that lock in the safe behaviour observed during the live
|
|
22
|
+
* run:
|
|
23
|
+
* • rejected (4xx) → assert status ∈ ACCEPTABLE_4XX (no regression to 2xx).
|
|
24
|
+
* • accepted-and-ignored → assert 2xx and that injected fields don't echo
|
|
25
|
+
* back. Follow-up GET — when available — additionally asserts the field
|
|
26
|
+
* is not persisted.
|
|
27
|
+
*
|
|
28
|
+
* "applied" / "inconclusive" are deliberately NOT emitted: those are bugs to
|
|
29
|
+
* fix, not baselines to lock.
|
|
30
|
+
*/
|
|
31
|
+
export function emitRegressionSuites(
|
|
32
|
+
result: MassAssignmentResult,
|
|
33
|
+
endpoints: EndpointInfo[],
|
|
34
|
+
schemes: SecuritySchemeInfo[],
|
|
35
|
+
): RawSuite[] {
|
|
36
|
+
const suites: RawSuite[] = [];
|
|
37
|
+
for (const v of result.verdicts) {
|
|
38
|
+
// ARV-250: "info" carries the post-pivot semantics of the old "low"
|
|
39
|
+
// (extras silently ignored — useful regression even when severity is
|
|
40
|
+
// demoted). Both "low" (inconclusive) and "info" (ignored) qualify
|
|
41
|
+
// for the ignored-baseline suite.
|
|
42
|
+
const isIgnoredCase = v.severity === "low" || v.severity === "info";
|
|
43
|
+
if (v.severity !== "ok" && !isIgnoredCase) continue;
|
|
44
|
+
const ep = endpoints.find(e => e.path === v.path && e.method.toUpperCase() === v.method);
|
|
45
|
+
if (!ep) continue;
|
|
46
|
+
const suiteHeaders = getAuthHeaders(ep, schemes);
|
|
47
|
+
const probeExpectedStatus = v.severity === "ok" ? ACCEPTABLE_4XX : [200, 201, 202, 204];
|
|
48
|
+
// ARV-150: emit `form:` instead of `json:` when the endpoint uses
|
|
49
|
+
// form-urlencoded bodies — otherwise the regression suite would send
|
|
50
|
+
// JSON and re-hit the original "wrong content-type" 400.
|
|
51
|
+
const bodyField =
|
|
52
|
+
ep.requestBodyContentType === "application/x-www-form-urlencoded"
|
|
53
|
+
? { form: flattenToFormFields(v.request.body) }
|
|
54
|
+
: { json: v.request.body };
|
|
55
|
+
const probeStep: RawStep = {
|
|
56
|
+
name: `mass-assignment: extras must ${v.severity === "ok" ? "be rejected" : "not apply"}`,
|
|
57
|
+
source: {
|
|
58
|
+
generator: "mass-assignment-probe",
|
|
59
|
+
endpoint: `${v.method} ${v.path}`,
|
|
60
|
+
response_branch: probeExpectedStatus.map(String).join("|"),
|
|
61
|
+
},
|
|
62
|
+
[v.method]: convertPath(ep.path),
|
|
63
|
+
...bodyField,
|
|
64
|
+
expect: {
|
|
65
|
+
status: probeExpectedStatus,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const tests: RawStep[] = [probeStep];
|
|
69
|
+
// For ignored case + we have a follow-up GET → emit a verifying GET
|
|
70
|
+
// that asserts injected fields are absent / overridden.
|
|
71
|
+
if (isIgnoredCase && v.followUpGet) {
|
|
72
|
+
const idField = captureFieldFor(ep);
|
|
73
|
+
probeStep.expect.body = {
|
|
74
|
+
...(probeStep.expect.body ?? {}),
|
|
75
|
+
[idField]: { capture: "created_id" },
|
|
76
|
+
};
|
|
77
|
+
const getEp = findGetByIdCounterpart(ep, endpoints);
|
|
78
|
+
if (getEp) {
|
|
79
|
+
const idParam = findIdParam(getEp);
|
|
80
|
+
const getStep: RawStep = {
|
|
81
|
+
name: `verify extras did not persist`,
|
|
82
|
+
source: {
|
|
83
|
+
generator: "mass-assignment-probe",
|
|
84
|
+
endpoint: `GET ${getEp.path}`,
|
|
85
|
+
response_branch: "200",
|
|
86
|
+
},
|
|
87
|
+
GET: convertPath(getEp.path).replace(`{{${idParam}}}`, "{{created_id}}"),
|
|
88
|
+
expect: {
|
|
89
|
+
status: 200,
|
|
90
|
+
body: extrasNotEqualAssertions(v),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
tests.push(getStep);
|
|
94
|
+
}
|
|
95
|
+
// cleanup
|
|
96
|
+
const delEp = findDeleteCounterpart(ep, endpoints);
|
|
97
|
+
if (delEp) {
|
|
98
|
+
const idParam = findIdParam(delEp);
|
|
99
|
+
const delStep: RawStep = {
|
|
100
|
+
name: "cleanup",
|
|
101
|
+
source: {
|
|
102
|
+
generator: "mass-assignment-probe-cleanup",
|
|
103
|
+
endpoint: `DELETE ${delEp.path}`,
|
|
104
|
+
},
|
|
105
|
+
always: true,
|
|
106
|
+
DELETE: convertPath(delEp.path).replace(`{{${idParam}}}`, "{{created_id}}"),
|
|
107
|
+
expect: { status: [200, 202, 204, 404] },
|
|
108
|
+
} as RawStep & { always: boolean };
|
|
109
|
+
tests.push(delStep);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
suites.push({
|
|
113
|
+
name: `mass-assignment ${v.method} ${v.path}`,
|
|
114
|
+
tags: ["probe-mass-assignment", v.severity === "ok" ? "rejected-baseline" : "ignored-baseline"],
|
|
115
|
+
source: {
|
|
116
|
+
type: "probe-suite",
|
|
117
|
+
generator: "mass-assignment-probe",
|
|
118
|
+
endpoint: `${v.method} ${v.path}`,
|
|
119
|
+
},
|
|
120
|
+
fileStem: `mass-assignment-${endpointStem(ep)}`,
|
|
121
|
+
base_url: "{{base_url}}",
|
|
122
|
+
...(suiteHeaders ? { headers: suiteHeaders } : {}),
|
|
123
|
+
tests,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return suites;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function extrasNotEqualAssertions(v: EndpointVerdict): Record<string, Record<string, string>> {
|
|
130
|
+
const out: Record<string, Record<string, string>> = {};
|
|
131
|
+
for (const f of v.fields) {
|
|
132
|
+
if (f.outcome === "ignored" || f.outcome === "echoed-overwritten" || f.outcome === "absent") {
|
|
133
|
+
// Assert the suspicious value did NOT take effect. We check that the
|
|
134
|
+
// observed value (from the live GET) still holds — the API is allowed
|
|
135
|
+
// to echo a server default; what's forbidden is echoing OUR sentinel.
|
|
136
|
+
const expectedNotEqual = JSON.stringify(f.injected);
|
|
137
|
+
out[f.field] = { not_equals: expectedNotEqual };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { OpenAPIV3 } from "openapi-types";
|
|
2
|
+
import type { EndpointInfo } from "../../generator/types.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sentinel values are deliberately distinctive so that — if they appear in a
|
|
6
|
+
* follow-up GET response — we can be confident the server actually persisted
|
|
7
|
+
* them rather than coincidentally generating the same value.
|
|
8
|
+
*/
|
|
9
|
+
export const SUSPECTED_FIELDS: Record<string, unknown> = {
|
|
10
|
+
is_admin: true,
|
|
11
|
+
is_system: true,
|
|
12
|
+
verified: true,
|
|
13
|
+
role: "admin",
|
|
14
|
+
account_id: "00000000-0000-0000-0000-00000000beef",
|
|
15
|
+
owner_id: "00000000-0000-0000-0000-00000000beef",
|
|
16
|
+
user_id: "00000000-0000-0000-0000-00000000beef",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Sentinel values for server-assigned fields lifted from response schema. */
|
|
20
|
+
const SERVER_FIELD_SENTINEL = {
|
|
21
|
+
uuid: "00000000-0000-0000-0000-00000000dead",
|
|
22
|
+
isoDate: "2000-01-01T00:00:00.000Z",
|
|
23
|
+
string: "zond-injected",
|
|
24
|
+
integer: -424242,
|
|
25
|
+
number: -424242,
|
|
26
|
+
boolean: false,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function requestPropertyNames(schema?: OpenAPIV3.SchemaObject): Set<string> {
|
|
30
|
+
const out = new Set<string>();
|
|
31
|
+
if (!schema) return out;
|
|
32
|
+
if (schema.properties) {
|
|
33
|
+
for (const k of Object.keys(schema.properties)) out.add(k);
|
|
34
|
+
}
|
|
35
|
+
for (const composite of [schema.allOf, schema.oneOf, schema.anyOf]) {
|
|
36
|
+
if (Array.isArray(composite)) {
|
|
37
|
+
for (const sub of composite) {
|
|
38
|
+
const s = sub as OpenAPIV3.SchemaObject;
|
|
39
|
+
if (s.properties) for (const k of Object.keys(s.properties)) out.add(k);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function isStrictContract(schema?: OpenAPIV3.SchemaObject): boolean {
|
|
47
|
+
if (!schema) return false;
|
|
48
|
+
return schema.additionalProperties === false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function pickServerFieldSentinel(s: OpenAPIV3.SchemaObject): unknown {
|
|
52
|
+
if (s.format === "uuid") return SERVER_FIELD_SENTINEL.uuid;
|
|
53
|
+
if (s.format === "date-time" || s.format === "date") return SERVER_FIELD_SENTINEL.isoDate;
|
|
54
|
+
switch (s.type) {
|
|
55
|
+
case "string": return SERVER_FIELD_SENTINEL.string;
|
|
56
|
+
case "integer": return SERVER_FIELD_SENTINEL.integer;
|
|
57
|
+
case "number": return SERVER_FIELD_SENTINEL.number;
|
|
58
|
+
case "boolean": return SERVER_FIELD_SENTINEL.boolean;
|
|
59
|
+
default: return SERVER_FIELD_SENTINEL.string;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Server-assigned fields = response 2xx schema props that don't appear in request schema. */
|
|
64
|
+
export function serverAssignedExtras(ep: EndpointInfo): Record<string, unknown> {
|
|
65
|
+
const reqProps = requestPropertyNames(ep.requestBodySchema);
|
|
66
|
+
const success = ep.responses.find(r => r.statusCode >= 200 && r.statusCode < 300 && r.schema);
|
|
67
|
+
const respProps = success?.schema?.properties;
|
|
68
|
+
const out: Record<string, unknown> = {};
|
|
69
|
+
if (!respProps) return out;
|
|
70
|
+
for (const [name, schema] of Object.entries(respProps)) {
|
|
71
|
+
if (reqProps.has(name)) continue;
|
|
72
|
+
out[name] = pickServerFieldSentinel(schema as OpenAPIV3.SchemaObject);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Extra fields that aren't legitimate request-body properties. */
|
|
78
|
+
export function suspectedExtras(
|
|
79
|
+
ep: EndpointInfo,
|
|
80
|
+
extra: Record<string, unknown> = {},
|
|
81
|
+
): Record<string, unknown> {
|
|
82
|
+
const reqProps = requestPropertyNames(ep.requestBodySchema);
|
|
83
|
+
const out: Record<string, unknown> = {};
|
|
84
|
+
// ARV-252: per-run extras (CLI --suspect-field) compose with the
|
|
85
|
+
// curated SUSPECTED_FIELDS list. Later additions win on key collision
|
|
86
|
+
// so a user can override a sentinel value if needed.
|
|
87
|
+
const merged: Record<string, unknown> = { ...SUSPECTED_FIELDS, ...extra };
|
|
88
|
+
for (const [name, value] of Object.entries(merged)) {
|
|
89
|
+
if (!reqProps.has(name)) out[name] = value;
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { EndpointInfo, SecuritySchemeInfo } from "../../generator/types.ts";
|
|
2
|
+
import type { SeedBodyConfig } from "../../generator/resources-builder.ts";
|
|
3
|
+
import type { RecommendedAction } from "../../diagnostics/failure-hints.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mass-assignment local severity. Includes the unified severity ladder
|
|
7
|
+
* (critical/high/medium/low/info — see core/severity) plus two
|
|
8
|
+
* outcome-style states specific to this probe (inconclusive-baseline,
|
|
9
|
+
* inconclusive-5xx) and probe-lifecycle markers (ok, skipped).
|
|
10
|
+
*
|
|
11
|
+
* 'medium' is retained in the type for backwards compat but ARV-250
|
|
12
|
+
* stopped emitting it — single-signal proof on absent-fields now caps
|
|
13
|
+
* to 'low' per the m-21 severity matrix.
|
|
14
|
+
*/
|
|
15
|
+
export type Severity =
|
|
16
|
+
| "high"
|
|
17
|
+
| "medium"
|
|
18
|
+
/** Baseline POST itself failed — we never reached extras-validation, so the
|
|
19
|
+
* 4xx-with-extras was a false signal. User must fix fixture / FK / scope
|
|
20
|
+
* before this endpoint can be probed (TASK-91). */
|
|
21
|
+
| "inconclusive-baseline"
|
|
22
|
+
/** Baseline POST returned ≥500 — the endpoint just crashes, mass-assignment
|
|
23
|
+
* semantics aren't observable here. Likely a duplicate of validation-probe's
|
|
24
|
+
* finding for the same endpoint (TASK-276). */
|
|
25
|
+
| "inconclusive-5xx"
|
|
26
|
+
| "low"
|
|
27
|
+
| "info"
|
|
28
|
+
| "ok"
|
|
29
|
+
| "skipped";
|
|
30
|
+
|
|
31
|
+
export interface FieldVerdict {
|
|
32
|
+
field: string;
|
|
33
|
+
injected: unknown;
|
|
34
|
+
/** "applied" | "ignored" | "echoed-but-overwritten" | "absent" | "unknown" */
|
|
35
|
+
outcome: "applied" | "ignored" | "echoed-overwritten" | "absent" | "unknown";
|
|
36
|
+
/** Value as seen in the response body (or follow-up GET if applicable). */
|
|
37
|
+
observed?: unknown;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface EndpointVerdict {
|
|
41
|
+
method: string;
|
|
42
|
+
path: string;
|
|
43
|
+
severity: Severity;
|
|
44
|
+
/** Canonical short reason (used in markdown header). */
|
|
45
|
+
summary: string;
|
|
46
|
+
request: {
|
|
47
|
+
url: string;
|
|
48
|
+
body: unknown;
|
|
49
|
+
injectedFields: string[];
|
|
50
|
+
};
|
|
51
|
+
response?: {
|
|
52
|
+
status: number;
|
|
53
|
+
body?: unknown;
|
|
54
|
+
};
|
|
55
|
+
followUpGet?: {
|
|
56
|
+
url: string;
|
|
57
|
+
status: number;
|
|
58
|
+
body?: unknown;
|
|
59
|
+
};
|
|
60
|
+
/** Result of the baseline (no-extras) probe — present whenever we sent it
|
|
61
|
+
* (always, except for skipped endpoints). Used to disambiguate
|
|
62
|
+
* «extras refused» from «baseline body invalid» (TASK-91). */
|
|
63
|
+
baseline?: {
|
|
64
|
+
status: number;
|
|
65
|
+
body?: unknown;
|
|
66
|
+
};
|
|
67
|
+
fields: FieldVerdict[];
|
|
68
|
+
/** True when request schema has additionalProperties:false (strict). */
|
|
69
|
+
strictContract: boolean;
|
|
70
|
+
cleanup?: {
|
|
71
|
+
attempted: boolean;
|
|
72
|
+
status?: number;
|
|
73
|
+
error?: string;
|
|
74
|
+
};
|
|
75
|
+
/** Reason this endpoint was skipped (only set when severity === "skipped"). */
|
|
76
|
+
skipReason?: string;
|
|
77
|
+
notes?: string[];
|
|
78
|
+
/** TASK-294: agent-routable action.
|
|
79
|
+
* high/medium → `report_backend_bug` (privilege escalation).
|
|
80
|
+
* inconclusive-baseline → `fix_fixture` (broken request body, retry).
|
|
81
|
+
* inconclusive-5xx → `report_backend_bug` (server crashed).
|
|
82
|
+
* low/ok/skipped → undefined. */
|
|
83
|
+
recommended_action?: RecommendedAction;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface MassAssignmentOptions {
|
|
87
|
+
endpoints: EndpointInfo[];
|
|
88
|
+
securitySchemes: SecuritySchemeInfo[];
|
|
89
|
+
/** Substituted variables (base_url, auth_token, api_key, path params). */
|
|
90
|
+
vars: Record<string, string>;
|
|
91
|
+
/** When true, do not issue cleanup-DELETE after 2xx responses. */
|
|
92
|
+
noCleanup?: boolean;
|
|
93
|
+
/** Per-request fetch timeout (ms). */
|
|
94
|
+
timeoutMs?: number;
|
|
95
|
+
/** When false, skip auto-discovery of path-param fixtures via GET-on-list (TASK-92).
|
|
96
|
+
* TASK-137: this flag now also controls body-FK discovery (required body
|
|
97
|
+
* fields named `*_id` / `*_slug` / `*_uuid` get filled from the matching
|
|
98
|
+
* collection list endpoint, eliminating most INCONCLUSIVE-baseline noise). */
|
|
99
|
+
discover?: boolean;
|
|
100
|
+
/** ARV-252: per-run extension to SUSPECTED_FIELDS (curated list of
|
|
101
|
+
* classic mass-assignment vectors). CLI surfaces this as repeatable
|
|
102
|
+
* `--suspect-field name=value`. Full per-api spec-extension support
|
|
103
|
+
* (x-zond-suspect-fields) is tracked in ARV-189. */
|
|
104
|
+
extraSuspectFields?: Record<string, unknown>;
|
|
105
|
+
/** ARV-269: agent-authored `seed_body` overlays from `.api-resources.local.yaml`,
|
|
106
|
+
* keyed by `"METHOD /path"` of the endpoint they apply to (typically the
|
|
107
|
+
* resource's create endpoint). When present for a probed endpoint, it
|
|
108
|
+
* replaces `generateFromSchema` as the baseline body source — same
|
|
109
|
+
* promotion stateful checks took via `resolveCreateBody`. Mass-assignment
|
|
110
|
+
* before ARV-269 ignored this overlay; on strict-validating APIs (Stripe)
|
|
111
|
+
* every baseline 400'd and the verdict collapsed to INCONCLUSIVE. */
|
|
112
|
+
seedBodies?: Map<string, SeedBodyConfig>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface MassAssignmentResult {
|
|
116
|
+
specProbed: number;
|
|
117
|
+
totalEndpoints: number;
|
|
118
|
+
verdicts: EndpointVerdict[];
|
|
119
|
+
warnings: string[];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Internal: per-step options for probeEndpoint. */
|
|
123
|
+
export interface ProbeEndpointOpts {
|
|
124
|
+
noCleanup: boolean;
|
|
125
|
+
timeoutMs?: number;
|
|
126
|
+
bodyFkMisses?: Array<{ field: string; reason: string }>;
|
|
127
|
+
/** TASK-137: field→value pairs from body-FK discovery. Overlaid on baseline
|
|
128
|
+
* after generation so a real id/slug replaces the random sentinel. */
|
|
129
|
+
bodyFkOverlay?: Record<string, string>;
|
|
130
|
+
/** ARV-252: per-run extras for the suspect-fields list. */
|
|
131
|
+
extraSuspectFields?: Record<string, unknown>;
|
|
132
|
+
/** ARV-269: optional agent-authored body overlay for this endpoint
|
|
133
|
+
* (usually the resource's create endpoint). Wins over generator. */
|
|
134
|
+
seedBody?: SeedBodyConfig;
|
|
135
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MassAssignmentProbe` — Probe-contract wrapper around the existing
|
|
3
|
+
* `runMassAssignmentProbes` engine (m-17 / ARV-49).
|
|
4
|
+
*
|
|
5
|
+
* dryRun() lists POST/PATCH/PUT endpoints with the suspect fields the
|
|
6
|
+
* probe would inject (suspectedExtras + serverAssignedExtras). Skip
|
|
7
|
+
* reasons mirror the live runner (no-body, isolated-protected). This
|
|
8
|
+
* fills the F2-15 gap (mass-assignment had no --dry-run); ARV-52 wires
|
|
9
|
+
* the corresponding CLI flag.
|
|
10
|
+
*
|
|
11
|
+
* run() delegates to runMassAssignmentProbes; report() converts to
|
|
12
|
+
* either the legacy markdown digest or the structured per-endpoint
|
|
13
|
+
* shape (ARV-51).
|
|
14
|
+
*/
|
|
15
|
+
import type { OpenAPIV3 } from "openapi-types";
|
|
16
|
+
import type { Probe, ProbeContext, ProbeFlags, EndpointPlan, ProbeResult, ProbeReportFormat, ProbeEndpointResult, ProbeEndpointStatus } from "./types.ts";
|
|
17
|
+
import {
|
|
18
|
+
runMassAssignmentProbes,
|
|
19
|
+
formatDigestMarkdown,
|
|
20
|
+
SUSPECTED_FIELDS,
|
|
21
|
+
type MassAssignmentResult,
|
|
22
|
+
type EndpointVerdict,
|
|
23
|
+
type Severity,
|
|
24
|
+
} from "./mass-assignment-probe.ts";
|
|
25
|
+
import { pathTouchesSeededVar } from "./shared.ts";
|
|
26
|
+
import { hasProbeBody } from "./probe-harness.ts";
|
|
27
|
+
|
|
28
|
+
const FLAGS: ProbeFlags = {
|
|
29
|
+
api: true,
|
|
30
|
+
tag: true,
|
|
31
|
+
include: true,
|
|
32
|
+
exclude: true,
|
|
33
|
+
dryRun: true,
|
|
34
|
+
listTags: true,
|
|
35
|
+
json: true,
|
|
36
|
+
output: true,
|
|
37
|
+
report: true,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function requestPropertyNames(schema?: OpenAPIV3.SchemaObject): Set<string> {
|
|
41
|
+
const out = new Set<string>();
|
|
42
|
+
if (!schema) return out;
|
|
43
|
+
if (schema.properties) for (const k of Object.keys(schema.properties)) out.add(k);
|
|
44
|
+
for (const composite of [schema.allOf, schema.oneOf, schema.anyOf]) {
|
|
45
|
+
if (Array.isArray(composite)) {
|
|
46
|
+
for (const sub of composite) {
|
|
47
|
+
const s = sub as OpenAPIV3.SchemaObject;
|
|
48
|
+
if (s.properties) for (const k of Object.keys(s.properties)) out.add(k);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function suspectFieldsFor(ep: { requestBodySchema?: OpenAPIV3.SchemaObject }): string[] {
|
|
56
|
+
const reqProps = requestPropertyNames(ep.requestBodySchema);
|
|
57
|
+
const out: string[] = [];
|
|
58
|
+
for (const name of Object.keys(SUSPECTED_FIELDS)) {
|
|
59
|
+
if (!reqProps.has(name)) out.push(name);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function statusFromSeverity(s: Severity): ProbeEndpointStatus {
|
|
65
|
+
switch (s) {
|
|
66
|
+
case "high": return "high";
|
|
67
|
+
case "low": return "low";
|
|
68
|
+
case "medium": return "low";
|
|
69
|
+
case "info": return "low";
|
|
70
|
+
case "ok": return "ok";
|
|
71
|
+
case "skipped": return "skipped";
|
|
72
|
+
case "inconclusive-baseline":
|
|
73
|
+
case "inconclusive-5xx":
|
|
74
|
+
return "inconclusive";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function evidenceFromVerdict(v: EndpointVerdict): Record<string, unknown> {
|
|
79
|
+
return {
|
|
80
|
+
summary: v.summary,
|
|
81
|
+
request: { url: v.request.url, injectedFields: v.request.injectedFields },
|
|
82
|
+
response: v.response ? { status: v.response.status } : undefined,
|
|
83
|
+
fields: v.fields,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function toProbeResult(ma: MassAssignmentResult): ProbeResult {
|
|
88
|
+
const endpoints: ProbeEndpointResult[] = ma.verdicts.map((v) => ({
|
|
89
|
+
path: v.path,
|
|
90
|
+
method: v.method,
|
|
91
|
+
classes_run: ["mass-assignment"],
|
|
92
|
+
findings:
|
|
93
|
+
v.severity === "skipped" || v.severity === "ok"
|
|
94
|
+
? []
|
|
95
|
+
: [{
|
|
96
|
+
class: "mass-assignment",
|
|
97
|
+
severity:
|
|
98
|
+
v.severity === "high" ? "high" :
|
|
99
|
+
v.severity === "low" || v.severity === "medium" ? "low" :
|
|
100
|
+
"inconclusive",
|
|
101
|
+
evidence: evidenceFromVerdict(v),
|
|
102
|
+
}],
|
|
103
|
+
status: statusFromSeverity(v.severity),
|
|
104
|
+
...(v.severity === "skipped" ? { skip_reason: v.skipReason ?? v.summary } : {}),
|
|
105
|
+
}));
|
|
106
|
+
const by_status: Record<ProbeEndpointStatus, number> = {
|
|
107
|
+
ok: 0, high: 0, low: 0, inconclusive: 0, skipped: 0,
|
|
108
|
+
};
|
|
109
|
+
for (const ep of endpoints) by_status[ep.status]++;
|
|
110
|
+
return {
|
|
111
|
+
endpoints,
|
|
112
|
+
summary: {
|
|
113
|
+
totalEndpoints: ma.totalEndpoints,
|
|
114
|
+
probed: ma.specProbed,
|
|
115
|
+
by_status,
|
|
116
|
+
},
|
|
117
|
+
warnings: ma.warnings,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export class MassAssignmentProbe implements Probe {
|
|
122
|
+
readonly name = "mass-assignment";
|
|
123
|
+
readonly description =
|
|
124
|
+
"Live probe for mass-assignment / privilege-escalation: classifies POST/PATCH/PUT against suspected extra fields (is_admin, role, account_id, …).";
|
|
125
|
+
readonly commonFlags = FLAGS;
|
|
126
|
+
|
|
127
|
+
async dryRun(ctx: ProbeContext): Promise<EndpointPlan[]> {
|
|
128
|
+
const isolated = ctx.options["isolated"] === true;
|
|
129
|
+
const out: EndpointPlan[] = [];
|
|
130
|
+
for (const ep of ctx.endpoints) {
|
|
131
|
+
if (ep.deprecated) continue;
|
|
132
|
+
const m = ep.method.toUpperCase();
|
|
133
|
+
if (m !== "POST" && m !== "PUT" && m !== "PATCH") continue;
|
|
134
|
+
|
|
135
|
+
if (isolated && (m === "PUT" || m === "PATCH") && pathTouchesSeededVar(ep.path, ctx.vars)) {
|
|
136
|
+
out.push({
|
|
137
|
+
path: ep.path,
|
|
138
|
+
method: m,
|
|
139
|
+
planned: false,
|
|
140
|
+
classes_planned: [],
|
|
141
|
+
fields_planned: [],
|
|
142
|
+
skip_reason: "isolated-protected",
|
|
143
|
+
});
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ARV-150: parity with the live runner — form-urlencoded counts as a body.
|
|
148
|
+
if (!hasProbeBody(ep)) {
|
|
149
|
+
out.push({
|
|
150
|
+
path: ep.path,
|
|
151
|
+
method: m,
|
|
152
|
+
planned: false,
|
|
153
|
+
classes_planned: [],
|
|
154
|
+
fields_planned: [],
|
|
155
|
+
skip_reason: "no-body",
|
|
156
|
+
});
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const fields = suspectFieldsFor(ep);
|
|
161
|
+
out.push({
|
|
162
|
+
path: ep.path,
|
|
163
|
+
method: m,
|
|
164
|
+
planned: true,
|
|
165
|
+
classes_planned: ["mass-assignment"],
|
|
166
|
+
fields_planned: fields,
|
|
167
|
+
skip_reason: null,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async run(ctx: ProbeContext): Promise<ProbeResult> {
|
|
174
|
+
const ma = await runMassAssignmentProbes({
|
|
175
|
+
endpoints: ctx.endpoints,
|
|
176
|
+
securitySchemes: ctx.securitySchemes,
|
|
177
|
+
vars: ctx.vars,
|
|
178
|
+
noCleanup: ctx.options["noCleanup"] === true,
|
|
179
|
+
timeoutMs: typeof ctx.options["timeoutMs"] === "number" ? (ctx.options["timeoutMs"] as number) : undefined,
|
|
180
|
+
discover: ctx.options["noDiscover"] !== true,
|
|
181
|
+
});
|
|
182
|
+
const result = toProbeResult(ma);
|
|
183
|
+
result.extras = { raw: ma };
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
report(format: ProbeReportFormat, result: ProbeResult): string | object {
|
|
188
|
+
if (format === "markdown") {
|
|
189
|
+
const raw = result.extras?.["raw"] as MassAssignmentResult | undefined;
|
|
190
|
+
if (raw) return formatDigestMarkdown(raw, "");
|
|
191
|
+
return "(no markdown digest available)";
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
endpoints: result.endpoints,
|
|
195
|
+
summary: result.summary,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel re-export for `zond probe-mass-assignment`.
|
|
3
|
+
*
|
|
4
|
+
* The probe pipeline lives in `./mass-assignment/`:
|
|
5
|
+
* - types.ts — public types / interfaces
|
|
6
|
+
* - suspects.ts — suspected-fields table + schema helpers
|
|
7
|
+
* - classify.ts — per-field classification + severity finalisation
|
|
8
|
+
* - cleanup.ts — best-effort DELETE on baseline POSTs
|
|
9
|
+
* - digest.ts — markdown digest formatter
|
|
10
|
+
* - regression.ts — regression-suite YAML emission
|
|
11
|
+
* - orchestrator.ts — runMassAssignmentProbes + probeEndpoint loop
|
|
12
|
+
*
|
|
13
|
+
* History: split out of a 1135-LOC monolith (ARV-296, 2026-05-18) to keep
|
|
14
|
+
* each concern in a focused file. The public API surface is unchanged.
|
|
15
|
+
*/
|
|
16
|
+
export type {
|
|
17
|
+
Severity,
|
|
18
|
+
FieldVerdict,
|
|
19
|
+
EndpointVerdict,
|
|
20
|
+
MassAssignmentOptions,
|
|
21
|
+
MassAssignmentResult,
|
|
22
|
+
} from "./mass-assignment/types.ts";
|
|
23
|
+
export { SUSPECTED_FIELDS } from "./mass-assignment/suspects.ts";
|
|
24
|
+
export { runMassAssignmentProbes } from "./mass-assignment/orchestrator.ts";
|
|
25
|
+
export { isSubscriptionGated } from "./mass-assignment/classify.ts";
|
|
26
|
+
export { formatDigestMarkdown } from "./mass-assignment/digest.ts";
|
|
27
|
+
export { emitRegressionSuites } from "./mass-assignment/regression.ts";
|