@kirrosh/zond 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +22 -21
- package/bin/zond.mjs +23 -0
- package/package.json +13 -16
- package/scripts/npm/postinstall.mjs +76 -0
- package/src/CLAUDE.md +0 -112
- package/src/bun-types.d.ts +0 -5
- package/src/cli/argv.ts +0 -122
- package/src/cli/commands/add-api.ts +0 -146
- package/src/cli/commands/api/annotate/idempotency.ts +0 -59
- package/src/cli/commands/api/annotate/index.ts +0 -880
- package/src/cli/commands/api/annotate/lifecycle.ts +0 -74
- package/src/cli/commands/api/annotate/overlay.ts +0 -206
- package/src/cli/commands/api/annotate/pagination.ts +0 -64
- package/src/cli/commands/api/annotate/prompts.ts +0 -220
- package/src/cli/commands/api/annotate/readback.ts +0 -58
- package/src/cli/commands/api/annotate/resources.ts +0 -91
- package/src/cli/commands/api/annotate/seed-bodies.ts +0 -61
- package/src/cli/commands/audit.ts +0 -786
- package/src/cli/commands/catalog.ts +0 -97
- package/src/cli/commands/check.ts +0 -361
- package/src/cli/commands/checks.ts +0 -1072
- package/src/cli/commands/ci-init.ts +0 -223
- package/src/cli/commands/clean.ts +0 -212
- package/src/cli/commands/cleanup.ts +0 -236
- package/src/cli/commands/completions.ts +0 -192
- package/src/cli/commands/coverage.ts +0 -872
- package/src/cli/commands/db.ts +0 -611
- package/src/cli/commands/describe.ts +0 -120
- package/src/cli/commands/discover.ts +0 -1356
- package/src/cli/commands/doctor.ts +0 -661
- package/src/cli/commands/fixtures.ts +0 -402
- package/src/cli/commands/generate.ts +0 -577
- package/src/cli/commands/init/agents-md.ts +0 -61
- package/src/cli/commands/init/bootstrap.ts +0 -111
- package/src/cli/commands/init/index.ts +0 -244
- package/src/cli/commands/init/skills.ts +0 -141
- package/src/cli/commands/init/templates/agents.md +0 -86
- package/src/cli/commands/init/templates/markdown.d.ts +0 -4
- package/src/cli/commands/init/templates/skills/warm-up-target.md +0 -122
- package/src/cli/commands/init/templates/skills/zond-checks.md +0 -621
- package/src/cli/commands/init/templates/skills/zond-seed.md +0 -114
- package/src/cli/commands/init/templates/skills/zond-triage.md +0 -272
- package/src/cli/commands/init/templates/skills/zond.md +0 -861
- package/src/cli/commands/init/templates/zond-config.yml +0 -14
- package/src/cli/commands/prepare-fixtures.ts +0 -97
- package/src/cli/commands/probe/_seed-bodies.ts +0 -52
- package/src/cli/commands/probe/mass-assignment.ts +0 -594
- package/src/cli/commands/probe/security.ts +0 -537
- package/src/cli/commands/probe/static.ts +0 -255
- package/src/cli/commands/probe/webhooks.ts +0 -163
- package/src/cli/commands/probe.ts +0 -535
- package/src/cli/commands/reference.ts +0 -87
- package/src/cli/commands/refresh-api.ts +0 -227
- package/src/cli/commands/remove-api.ts +0 -150
- package/src/cli/commands/report-bundle.ts +0 -310
- package/src/cli/commands/report.ts +0 -241
- package/src/cli/commands/request.ts +0 -548
- package/src/cli/commands/run.ts +0 -1142
- package/src/cli/commands/schema-from-runs.ts +0 -128
- package/src/cli/commands/secrets.ts +0 -133
- package/src/cli/commands/session.ts +0 -244
- package/src/cli/commands/use.ts +0 -74
- package/src/cli/index.ts +0 -55
- package/src/cli/json-envelope.ts +0 -108
- package/src/cli/json-schemas.ts +0 -314
- package/src/cli/output.ts +0 -40
- package/src/cli/program.ts +0 -219
- package/src/cli/resolve.ts +0 -105
- package/src/cli/runtime.ts +0 -7
- package/src/cli/safe-live.ts +0 -24
- package/src/cli/status-filter.ts +0 -114
- package/src/cli/util/api-context.ts +0 -85
- package/src/cli/version.ts +0 -8
- package/src/core/audit/persist.ts +0 -183
- package/src/core/checks/budget.ts +0 -59
- package/src/core/checks/checks/_crud-helpers.ts +0 -133
- package/src/core/checks/checks/_negative_mutator.ts +0 -133
- package/src/core/checks/checks/_readback-helpers.ts +0 -133
- package/src/core/checks/checks/content_type_conformance.ts +0 -39
- package/src/core/checks/checks/cross_call_references.ts +0 -147
- package/src/core/checks/checks/cursor_boundary_fuzzing.ts +0 -219
- package/src/core/checks/checks/ensure_resource_availability.ts +0 -62
- package/src/core/checks/checks/idempotency_replay.ts +0 -242
- package/src/core/checks/checks/ignored_auth.ts +0 -254
- package/src/core/checks/checks/index.ts +0 -68
- package/src/core/checks/checks/lifecycle_transitions.ts +0 -416
- package/src/core/checks/checks/missing_required_header.ts +0 -40
- package/src/core/checks/checks/negative_data_rejection.ts +0 -148
- package/src/core/checks/checks/not_a_server_error.ts +0 -35
- package/src/core/checks/checks/open_cors_on_sensitive.ts +0 -160
- package/src/core/checks/checks/pagination_invariants.ts +0 -419
- package/src/core/checks/checks/positive_data_acceptance.ts +0 -33
- package/src/core/checks/checks/rate_limit_headers_absent.ts +0 -77
- package/src/core/checks/checks/response_headers_conformance.ts +0 -74
- package/src/core/checks/checks/response_schema_conformance.ts +0 -30
- package/src/core/checks/checks/status_code_conformance.ts +0 -132
- package/src/core/checks/checks/unsupported_method.ts +0 -63
- package/src/core/checks/checks/use_after_free.ts +0 -78
- package/src/core/checks/index.ts +0 -30
- package/src/core/checks/mode.ts +0 -82
- package/src/core/checks/recommended-action.ts +0 -68
- package/src/core/checks/registry.ts +0 -78
- package/src/core/checks/runner.ts +0 -1461
- package/src/core/checks/sarif.ts +0 -230
- package/src/core/checks/spec-findings.ts +0 -308
- package/src/core/checks/stateful.ts +0 -121
- package/src/core/checks/types.ts +0 -305
- package/src/core/checks/zond-extensions.ts +0 -73
- package/src/core/classifier/recommended-action.ts +0 -251
- package/src/core/context/current.ts +0 -51
- package/src/core/context/session.ts +0 -78
- package/src/core/coverage/loader.ts +0 -216
- package/src/core/coverage/reasons.ts +0 -300
- package/src/core/diagnostics/db-analysis.ts +0 -666
- package/src/core/diagnostics/failure-class.ts +0 -140
- package/src/core/diagnostics/failure-hints.ts +0 -112
- package/src/core/diagnostics/spec-pointer.ts +0 -99
- package/src/core/diagnostics/suggested-fixes.ts +0 -155
- package/src/core/exporter/case-study/index.ts +0 -270
- package/src/core/exporter/curl.ts +0 -40
- package/src/core/exporter/exporter.ts +0 -48
- package/src/core/exporter/html-report/escape.ts +0 -24
- package/src/core/exporter/html-report/index.ts +0 -479
- package/src/core/exporter/html-report/script.ts +0 -100
- package/src/core/exporter/html-report/styles.ts +0 -408
- package/src/core/generator/catalog-builder.ts +0 -179
- package/src/core/generator/chunker.ts +0 -78
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/coverage-scanner.ts +0 -87
- package/src/core/generator/data-factory.ts +0 -759
- package/src/core/generator/describe.ts +0 -302
- package/src/core/generator/endpoint-warnings.ts +0 -52
- package/src/core/generator/fixtures-builder.ts +0 -332
- package/src/core/generator/index.ts +0 -14
- package/src/core/generator/openapi-reader.ts +0 -297
- package/src/core/generator/path-param-disambig.ts +0 -140
- package/src/core/generator/resources-builder.ts +0 -898
- package/src/core/generator/schema-utils.ts +0 -112
- package/src/core/generator/serializer.ts +0 -343
- package/src/core/generator/suite-generator.ts +0 -1301
- package/src/core/generator/types.ts +0 -63
- package/src/core/identity/identity-file.ts +0 -0
- package/src/core/lint/affects.ts +0 -28
- package/src/core/lint/config.ts +0 -96
- package/src/core/lint/format.ts +0 -42
- package/src/core/lint/index.ts +0 -94
- package/src/core/lint/reporter.ts +0 -128
- package/src/core/lint/rules/consistency.ts +0 -158
- package/src/core/lint/rules/heuristics.ts +0 -97
- package/src/core/lint/rules/strictness.ts +0 -109
- package/src/core/lint/types.ts +0 -96
- package/src/core/lint/walker.ts +0 -248
- package/src/core/meta/meta-store.ts +0 -11
- package/src/core/output/README.md +0 -73
- package/src/core/output/index.ts +0 -13
- package/src/core/output/run.ts +0 -91
- package/src/core/output/types.ts +0 -122
- package/src/core/parser/dynamic-values.ts +0 -160
- package/src/core/parser/env-interpolation.ts +0 -104
- package/src/core/parser/filter.ts +0 -97
- package/src/core/parser/schema.ts +0 -359
- package/src/core/parser/types.ts +0 -117
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +0 -181
- package/src/core/probe/bootstrap.ts +0 -34
- package/src/core/probe/dry-run-envelope.ts +0 -61
- package/src/core/probe/mass-assignment/classify.ts +0 -175
- package/src/core/probe/mass-assignment/cleanup.ts +0 -52
- package/src/core/probe/mass-assignment/digest.ts +0 -114
- package/src/core/probe/mass-assignment/orchestrator.ts +0 -459
- package/src/core/probe/mass-assignment/regression.ts +0 -141
- package/src/core/probe/mass-assignment/suspects.ts +0 -92
- package/src/core/probe/mass-assignment/types.ts +0 -135
- package/src/core/probe/mass-assignment-probe-class.ts +0 -198
- package/src/core/probe/mass-assignment-probe.ts +0 -27
- package/src/core/probe/mass-assignment-template.ts +0 -240
- package/src/core/probe/method-probe.ts +0 -164
- package/src/core/probe/method-shared.ts +0 -69
- package/src/core/probe/negative-probe.ts +0 -691
- package/src/core/probe/orphan-tracker.ts +0 -188
- package/src/core/probe/path-discovery.ts +0 -439
- package/src/core/probe/probe-harness.ts +0 -119
- package/src/core/probe/registry.ts +0 -89
- package/src/core/probe/runner.ts +0 -136
- package/src/core/probe/security/baseline.ts +0 -174
- package/src/core/probe/security/classify.ts +0 -341
- package/src/core/probe/security/cleanup.ts +0 -125
- package/src/core/probe/security/detectors.ts +0 -71
- package/src/core/probe/security/digest.ts +0 -104
- package/src/core/probe/security/orchestrator.ts +0 -398
- package/src/core/probe/security/regression.ts +0 -103
- package/src/core/probe/security/types.ts +0 -151
- package/src/core/probe/security-probe-class.ts +0 -207
- package/src/core/probe/security-probe.ts +0 -32
- package/src/core/probe/shared.ts +0 -531
- package/src/core/probe/static-probe-class.ts +0 -125
- package/src/core/probe/types.ts +0 -165
- package/src/core/probe/verdict-aggregator.ts +0 -33
- package/src/core/probe/webhooks-probe.ts +0 -282
- package/src/core/reporter/console.ts +0 -240
- package/src/core/reporter/index.ts +0 -22
- package/src/core/reporter/json.ts +0 -22
- package/src/core/reporter/junit.ts +0 -93
- package/src/core/reporter/ndjson.ts +0 -37
- package/src/core/reporter/types.ts +0 -15
- package/src/core/runner/assertions.ts +0 -383
- package/src/core/runner/async-pool.ts +0 -108
- package/src/core/runner/auth-path.ts +0 -8
- package/src/core/runner/ci-context.ts +0 -72
- package/src/core/runner/executor.ts +0 -652
- package/src/core/runner/expr-eval.ts +0 -41
- package/src/core/runner/form-encode.ts +0 -41
- package/src/core/runner/http-client.ts +0 -224
- package/src/core/runner/learn-drift.ts +0 -293
- package/src/core/runner/preflight-vars.ts +0 -153
- package/src/core/runner/progress-tracker.ts +0 -73
- package/src/core/runner/rate-limiter.ts +0 -185
- package/src/core/runner/run-kind.ts +0 -45
- package/src/core/runner/schema-validator.ts +0 -308
- package/src/core/runner/send-request.ts +0 -232
- package/src/core/runner/transforms.ts +0 -65
- package/src/core/runner/types.ts +0 -94
- package/src/core/secrets/registry.ts +0 -164
- package/src/core/secrets/secrets-file.ts +0 -115
- package/src/core/selectors/operation-filter.ts +0 -144
- package/src/core/setup-api.ts +0 -590
- package/src/core/severity/category.ts +0 -94
- package/src/core/severity/index.ts +0 -58
- package/src/core/spec/infer-schema.ts +0 -102
- package/src/core/spec/layers.ts +0 -154
- package/src/core/spec/merge-specs.ts +0 -156
- package/src/core/spec/schema-from-runs.ts +0 -117
- package/src/core/spec/schema-overlay.ts +0 -130
- package/src/core/util/ajv.ts +0 -13
- package/src/core/util/format-eta.ts +0 -21
- package/src/core/util/headers.ts +0 -9
- package/src/core/util/url.ts +0 -24
- package/src/core/utils.ts +0 -13
- package/src/core/workspace/config.ts +0 -129
- package/src/core/workspace/fixture-gap-report.ts +0 -84
- package/src/core/workspace/fixture-gaps.ts +0 -71
- package/src/core/workspace/manifest.ts +0 -283
- package/src/core/workspace/output-rotation.ts +0 -62
- package/src/core/workspace/root.ts +0 -96
- package/src/core/workspace/triage-path.ts +0 -87
- package/src/db/lint-runs.ts +0 -47
- package/src/db/migrate.ts +0 -128
- package/src/db/migrations/0001_run_kind.sql +0 -25
- package/src/db/migrations/0002_run_kind_request.sql +0 -59
- package/src/db/migrations/sql.d.ts +0 -4
- package/src/db/queries/collections.ts +0 -133
- package/src/db/queries/coverage.ts +0 -9
- package/src/db/queries/dashboard.ts +0 -59
- package/src/db/queries/results.ts +0 -216
- package/src/db/queries/runs.ts +0 -289
- package/src/db/queries/sessions.ts +0 -42
- package/src/db/queries/settings.ts +0 -28
- package/src/db/queries/types.ts +0 -172
- package/src/db/queries.ts +0 -75
- package/src/db/schema.ts +0 -298
|
@@ -1,135 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,198 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
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";
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TASK-146: emit-template generator.
|
|
3
|
-
*
|
|
4
|
-
* Builds a ready-to-edit YAML probe template for a single endpoint, so the
|
|
5
|
-
* user doesn't have to copy-paste the boilerplate from the skill (Phase 5.1).
|
|
6
|
-
* Used when the auto-prober marked an endpoint INCONCLUSIVE / INCONCLUSIVE-5XX
|
|
7
|
-
* and the user wants to drop down to manual catch-up.
|
|
8
|
-
*
|
|
9
|
-
* Heuristics:
|
|
10
|
-
* - Suspected fields: classic mass-assignment vectors (is_admin, role,
|
|
11
|
-
* owner_id, account_id, ...).
|
|
12
|
-
* - readOnly: true / x-zond-protected fields lifted from the request body
|
|
13
|
-
* schema — these MUST NOT round-trip back from the server.
|
|
14
|
-
* - For POST endpoints with discoverable item path (GET-by-id / DELETE
|
|
15
|
-
* counterpart) we emit a full create → verify → cleanup chain.
|
|
16
|
-
*/
|
|
17
|
-
import type { OpenAPIV3 } from "openapi-types";
|
|
18
|
-
import type { EndpointInfo } from "../generator/types.ts";
|
|
19
|
-
import type { RawSuite, RawStep } from "../generator/serializer.ts";
|
|
20
|
-
import { serializeSuite } from "../generator/serializer.ts";
|
|
21
|
-
import { readOpenApiSpec, extractEndpoints } from "../generator/openapi-reader.ts";
|
|
22
|
-
import { findDeleteCounterpart, findGetByIdCounterpart, captureFieldFor } from "./shared.ts";
|
|
23
|
-
import { SUSPECTED_FIELDS } from "./mass-assignment-probe.ts";
|
|
24
|
-
|
|
25
|
-
export interface EmitTemplateOptions {
|
|
26
|
-
specPath: string;
|
|
27
|
-
method: string;
|
|
28
|
-
path: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type EmitTemplateResult =
|
|
32
|
-
| { kind: "ok"; yaml: string; chain: "full" | "single"; protectedFields: string[] }
|
|
33
|
-
| { kind: "endpoint-not-found"; method: string; path: string; nearest: string[] };
|
|
34
|
-
|
|
35
|
-
export async function buildMassAssignmentTemplate(
|
|
36
|
-
opts: EmitTemplateOptions,
|
|
37
|
-
): Promise<EmitTemplateResult> {
|
|
38
|
-
const doc = await readOpenApiSpec(opts.specPath);
|
|
39
|
-
const all = extractEndpoints(doc);
|
|
40
|
-
|
|
41
|
-
const wantMethod = opts.method.toUpperCase();
|
|
42
|
-
const target = all.find(
|
|
43
|
-
e => e.method.toUpperCase() === wantMethod && pathsEqual(e.path, opts.path),
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
if (!target) {
|
|
47
|
-
const nearest = all
|
|
48
|
-
.filter(e => e.method.toUpperCase() === wantMethod)
|
|
49
|
-
.map(e => e.path)
|
|
50
|
-
.filter(p => similar(p, opts.path))
|
|
51
|
-
.slice(0, 5);
|
|
52
|
-
return { kind: "endpoint-not-found", method: wantMethod, path: opts.path, nearest };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const protectedFields = collectProtectedFields(target.requestBodySchema);
|
|
56
|
-
const baselineBody = buildBaselineSkeleton(target.requestBodySchema);
|
|
57
|
-
const privilegedBody = mergePrivileged(baselineBody, protectedFields);
|
|
58
|
-
|
|
59
|
-
const isMutatingCreateLike = wantMethod === "POST";
|
|
60
|
-
const readSibling = isMutatingCreateLike ? findGetByIdCounterpart(target, all) : undefined;
|
|
61
|
-
const deleteSibling = findDeleteCounterpart(target, all);
|
|
62
|
-
|
|
63
|
-
const suite = isMutatingCreateLike && readSibling
|
|
64
|
-
? buildFullChain(target, readSibling, deleteSibling, privilegedBody, protectedFields)
|
|
65
|
-
: buildSingleStep(target, privilegedBody, protectedFields);
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
kind: "ok",
|
|
69
|
-
yaml: serializeSuite(suite),
|
|
70
|
-
chain: isMutatingCreateLike && readSibling ? "full" : "single",
|
|
71
|
-
protectedFields,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function collectProtectedFields(schema?: OpenAPIV3.SchemaObject): string[] {
|
|
76
|
-
if (!schema || !schema.properties) return [];
|
|
77
|
-
const out: string[] = [];
|
|
78
|
-
for (const [name, raw] of Object.entries(schema.properties)) {
|
|
79
|
-
const prop = raw as OpenAPIV3.SchemaObject & { "x-zond-protected"?: boolean };
|
|
80
|
-
if (prop.readOnly === true || prop["x-zond-protected"] === true) out.push(name);
|
|
81
|
-
}
|
|
82
|
-
return out;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function buildBaselineSkeleton(schema?: OpenAPIV3.SchemaObject): Record<string, unknown> {
|
|
86
|
-
// Skeleton is intentionally minimal — `# …real create body sourced from
|
|
87
|
-
// fixtures…` placeholder shows up in the YAML so the user fills it in.
|
|
88
|
-
if (!schema || !schema.properties) return {};
|
|
89
|
-
const out: Record<string, unknown> = {};
|
|
90
|
-
for (const [name, raw] of Object.entries(schema.properties)) {
|
|
91
|
-
const prop = raw as OpenAPIV3.SchemaObject;
|
|
92
|
-
if (prop.readOnly === true) continue;
|
|
93
|
-
if (schema.required?.includes(name)) {
|
|
94
|
-
out[name] = placeholderForType(prop);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return out;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function placeholderForType(prop: OpenAPIV3.SchemaObject): unknown {
|
|
101
|
-
if (prop.example !== undefined) return prop.example;
|
|
102
|
-
switch (prop.type) {
|
|
103
|
-
case "integer":
|
|
104
|
-
case "number": return 1;
|
|
105
|
-
case "boolean": return false;
|
|
106
|
-
case "array": return [];
|
|
107
|
-
case "object": return {};
|
|
108
|
-
default: return `ma-test-{{$randomString}}`;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function mergePrivileged(
|
|
113
|
-
baseline: Record<string, unknown>,
|
|
114
|
-
protectedFields: string[],
|
|
115
|
-
): Record<string, unknown> {
|
|
116
|
-
const merged: Record<string, unknown> = { ...baseline };
|
|
117
|
-
// Suspected fields always added.
|
|
118
|
-
for (const [k, v] of Object.entries(SUSPECTED_FIELDS)) merged[k] = v;
|
|
119
|
-
// readOnly / x-zond-protected fields: inject distinctive sentinel values
|
|
120
|
-
// so we can detect server-side echo vs server-side regeneration.
|
|
121
|
-
for (const f of protectedFields) {
|
|
122
|
-
if (!(f in merged)) merged[f] = `attacker-${f}-{{$uuid}}`;
|
|
123
|
-
}
|
|
124
|
-
return merged;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** ARV-198: declared content type signals whether the create step needs
|
|
128
|
-
* `form:` (Stripe v1, Rails/PHP-style APIs) or the default `json:` body. */
|
|
129
|
-
function isFormEndpoint(ep: EndpointInfo): boolean {
|
|
130
|
-
return ep.requestBodyContentType === "application/x-www-form-urlencoded";
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function buildFullChain(
|
|
134
|
-
create: EndpointInfo,
|
|
135
|
-
read: EndpointInfo,
|
|
136
|
-
del: EndpointInfo | undefined,
|
|
137
|
-
privilegedBody: Record<string, unknown>,
|
|
138
|
-
protectedFields: string[],
|
|
139
|
-
): RawSuite {
|
|
140
|
-
const idVar = captureFieldFor(create) || "created_id";
|
|
141
|
-
const tests: RawStep[] = [];
|
|
142
|
-
|
|
143
|
-
// ARV-198: when the spec declares only application/x-www-form-urlencoded
|
|
144
|
-
// for this mutating endpoint, emit a `form:` block + Content-Type header.
|
|
145
|
-
// The serializer force-quotes form values (ARV-162) so booleans/numbers
|
|
146
|
-
// round-trip as strings on the wire; the template is paste-ready against
|
|
147
|
-
// Stripe-style APIs without any post-processing.
|
|
148
|
-
const createIsForm = isFormEndpoint(create);
|
|
149
|
-
const createStep: Record<string, unknown> = {
|
|
150
|
-
name: "create with privileged fields",
|
|
151
|
-
[create.method.toUpperCase()]: create.path,
|
|
152
|
-
expect: { status: [200, 201], body: { id: { capture: idVar } } },
|
|
153
|
-
};
|
|
154
|
-
if (createIsForm) {
|
|
155
|
-
createStep.headers = { "Content-Type": "application/x-www-form-urlencoded" };
|
|
156
|
-
createStep.form = privilegedBody;
|
|
157
|
-
} else {
|
|
158
|
-
createStep.json = privilegedBody;
|
|
159
|
-
}
|
|
160
|
-
tests.push(createStep as unknown as RawStep);
|
|
161
|
-
|
|
162
|
-
// Canonical assertion vocabulary: only `not_equals` is supported (no `not`,
|
|
163
|
-
// no `not_starts_with`). For protected fields we assert the exact attacker
|
|
164
|
-
// sentinel value did NOT round-trip back from the server.
|
|
165
|
-
const verifyBody: Record<string, Record<string, unknown>> = {};
|
|
166
|
-
for (const k of Object.keys(SUSPECTED_FIELDS)) {
|
|
167
|
-
verifyBody[k] = { not_equals: SUSPECTED_FIELDS[k] };
|
|
168
|
-
}
|
|
169
|
-
for (const f of protectedFields) {
|
|
170
|
-
if (!(f in verifyBody)) verifyBody[f] = { not_contains: "attacker-" };
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
tests.push({
|
|
174
|
-
name: "verify privileged fields not echoed",
|
|
175
|
-
[read.method.toUpperCase()]: read.path.replace(/\{[^}]+\}/, `{{${idVar}}}`),
|
|
176
|
-
expect: { status: 200, body: verifyBody as unknown as Record<string, Record<string, string>> },
|
|
177
|
-
} as unknown as RawStep);
|
|
178
|
-
|
|
179
|
-
if (del) {
|
|
180
|
-
tests.push({
|
|
181
|
-
name: "cleanup",
|
|
182
|
-
[del.method.toUpperCase()]: del.path.replace(/\{[^}]+\}/, `{{${idVar}}}`),
|
|
183
|
-
always: true,
|
|
184
|
-
expect: { status: [200, 202, 204] },
|
|
185
|
-
} as unknown as RawStep);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
name: `ma ${slugFromPath(create.path)}`,
|
|
190
|
-
base_url: "{{base_url}}",
|
|
191
|
-
headers: { Authorization: "Bearer {{auth_token}}" },
|
|
192
|
-
tests,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function buildSingleStep(
|
|
197
|
-
ep: EndpointInfo,
|
|
198
|
-
privilegedBody: Record<string, unknown>,
|
|
199
|
-
_protectedFields: string[],
|
|
200
|
-
): RawSuite {
|
|
201
|
-
const method = ep.method.toUpperCase();
|
|
202
|
-
const tests: RawStep[] = [];
|
|
203
|
-
const step: Record<string, unknown> = {
|
|
204
|
-
name: `mass-assignment ${method} ${ep.path}`,
|
|
205
|
-
[method]: ep.path,
|
|
206
|
-
expect: { status: [400, 422] },
|
|
207
|
-
};
|
|
208
|
-
if (method !== "GET" && method !== "DELETE") {
|
|
209
|
-
// ARV-198: same form/json branching as the full-chain create — see
|
|
210
|
-
// buildFullChain for rationale (Stripe-style mutators declare only
|
|
211
|
-
// application/x-www-form-urlencoded).
|
|
212
|
-
if (isFormEndpoint(ep)) {
|
|
213
|
-
step.headers = { "Content-Type": "application/x-www-form-urlencoded" };
|
|
214
|
-
step.form = privilegedBody;
|
|
215
|
-
} else {
|
|
216
|
-
step.json = privilegedBody;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
tests.push(step as unknown as RawStep);
|
|
220
|
-
return {
|
|
221
|
-
name: `ma ${slugFromPath(ep.path)}`,
|
|
222
|
-
base_url: "{{base_url}}",
|
|
223
|
-
headers: { Authorization: "Bearer {{auth_token}}" },
|
|
224
|
-
tests,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function pathsEqual(a: string, b: string): boolean {
|
|
229
|
-
return a.replace(/\/$/, "") === b.replace(/\/$/, "");
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function similar(a: string, b: string): boolean {
|
|
233
|
-
const aSeg = a.split("/").filter(Boolean);
|
|
234
|
-
const bSeg = b.split("/").filter(Boolean);
|
|
235
|
-
return aSeg.some(s => bSeg.includes(s));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function slugFromPath(p: string): string {
|
|
239
|
-
return p.replace(/^\//, "").replace(/\/?\{[^}]+\}/g, "").replace(/\//g, "-") || "endpoint";
|
|
240
|
-
}
|