@kirrosh/zond 0.26.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +22 -21
- package/bin/zond.mjs +23 -0
- package/package.json +13 -16
- package/scripts/npm/postinstall.mjs +76 -0
- package/src/CLAUDE.md +0 -112
- package/src/bun-types.d.ts +0 -5
- package/src/cli/argv.ts +0 -122
- package/src/cli/commands/add-api.ts +0 -146
- package/src/cli/commands/api/annotate/idempotency.ts +0 -59
- package/src/cli/commands/api/annotate/index.ts +0 -880
- package/src/cli/commands/api/annotate/lifecycle.ts +0 -74
- package/src/cli/commands/api/annotate/overlay.ts +0 -206
- package/src/cli/commands/api/annotate/pagination.ts +0 -64
- package/src/cli/commands/api/annotate/prompts.ts +0 -220
- package/src/cli/commands/api/annotate/readback.ts +0 -58
- package/src/cli/commands/api/annotate/resources.ts +0 -91
- package/src/cli/commands/api/annotate/seed-bodies.ts +0 -61
- package/src/cli/commands/audit.ts +0 -786
- package/src/cli/commands/catalog.ts +0 -97
- package/src/cli/commands/check.ts +0 -361
- package/src/cli/commands/checks.ts +0 -1072
- package/src/cli/commands/ci-init.ts +0 -223
- package/src/cli/commands/clean.ts +0 -212
- package/src/cli/commands/cleanup.ts +0 -236
- package/src/cli/commands/completions.ts +0 -192
- package/src/cli/commands/coverage.ts +0 -872
- package/src/cli/commands/db.ts +0 -611
- package/src/cli/commands/describe.ts +0 -120
- package/src/cli/commands/discover.ts +0 -1356
- package/src/cli/commands/doctor.ts +0 -661
- package/src/cli/commands/fixtures.ts +0 -402
- package/src/cli/commands/generate.ts +0 -577
- package/src/cli/commands/init/agents-md.ts +0 -61
- package/src/cli/commands/init/bootstrap.ts +0 -111
- package/src/cli/commands/init/index.ts +0 -244
- package/src/cli/commands/init/skills.ts +0 -141
- package/src/cli/commands/init/templates/agents.md +0 -86
- package/src/cli/commands/init/templates/markdown.d.ts +0 -4
- package/src/cli/commands/init/templates/skills/warm-up-target.md +0 -122
- package/src/cli/commands/init/templates/skills/zond-checks.md +0 -621
- package/src/cli/commands/init/templates/skills/zond-seed.md +0 -114
- package/src/cli/commands/init/templates/skills/zond-triage.md +0 -272
- package/src/cli/commands/init/templates/skills/zond.md +0 -861
- package/src/cli/commands/init/templates/zond-config.yml +0 -14
- package/src/cli/commands/prepare-fixtures.ts +0 -97
- package/src/cli/commands/probe/_seed-bodies.ts +0 -52
- package/src/cli/commands/probe/mass-assignment.ts +0 -594
- package/src/cli/commands/probe/security.ts +0 -537
- package/src/cli/commands/probe/static.ts +0 -255
- package/src/cli/commands/probe/webhooks.ts +0 -163
- package/src/cli/commands/probe.ts +0 -535
- package/src/cli/commands/reference.ts +0 -87
- package/src/cli/commands/refresh-api.ts +0 -227
- package/src/cli/commands/remove-api.ts +0 -150
- package/src/cli/commands/report-bundle.ts +0 -310
- package/src/cli/commands/report.ts +0 -241
- package/src/cli/commands/request.ts +0 -548
- package/src/cli/commands/run.ts +0 -1162
- package/src/cli/commands/schema-from-runs.ts +0 -128
- package/src/cli/commands/secrets.ts +0 -133
- package/src/cli/commands/session.ts +0 -244
- package/src/cli/commands/use.ts +0 -74
- package/src/cli/index.ts +0 -55
- package/src/cli/json-envelope.ts +0 -108
- package/src/cli/json-schemas.ts +0 -314
- package/src/cli/output.ts +0 -40
- package/src/cli/program.ts +0 -219
- package/src/cli/resolve.ts +0 -105
- package/src/cli/runtime.ts +0 -7
- package/src/cli/safe-live.ts +0 -24
- package/src/cli/status-filter.ts +0 -114
- package/src/cli/util/api-context.ts +0 -85
- package/src/cli/version.ts +0 -8
- package/src/core/audit/persist.ts +0 -183
- package/src/core/checks/budget.ts +0 -59
- package/src/core/checks/checks/_crud-helpers.ts +0 -133
- package/src/core/checks/checks/_negative_mutator.ts +0 -133
- package/src/core/checks/checks/_readback-helpers.ts +0 -133
- package/src/core/checks/checks/content_type_conformance.ts +0 -39
- package/src/core/checks/checks/cross_call_references.ts +0 -147
- package/src/core/checks/checks/cursor_boundary_fuzzing.ts +0 -219
- package/src/core/checks/checks/ensure_resource_availability.ts +0 -62
- package/src/core/checks/checks/idempotency_replay.ts +0 -242
- package/src/core/checks/checks/ignored_auth.ts +0 -254
- package/src/core/checks/checks/index.ts +0 -68
- package/src/core/checks/checks/lifecycle_transitions.ts +0 -416
- package/src/core/checks/checks/missing_required_header.ts +0 -40
- package/src/core/checks/checks/negative_data_rejection.ts +0 -148
- package/src/core/checks/checks/not_a_server_error.ts +0 -35
- package/src/core/checks/checks/open_cors_on_sensitive.ts +0 -160
- package/src/core/checks/checks/pagination_invariants.ts +0 -419
- package/src/core/checks/checks/positive_data_acceptance.ts +0 -33
- package/src/core/checks/checks/rate_limit_headers_absent.ts +0 -77
- package/src/core/checks/checks/response_headers_conformance.ts +0 -74
- package/src/core/checks/checks/response_schema_conformance.ts +0 -30
- package/src/core/checks/checks/status_code_conformance.ts +0 -132
- package/src/core/checks/checks/unsupported_method.ts +0 -63
- package/src/core/checks/checks/use_after_free.ts +0 -78
- package/src/core/checks/index.ts +0 -30
- package/src/core/checks/mode.ts +0 -82
- package/src/core/checks/recommended-action.ts +0 -68
- package/src/core/checks/registry.ts +0 -78
- package/src/core/checks/runner.ts +0 -1461
- package/src/core/checks/sarif.ts +0 -230
- package/src/core/checks/spec-findings.ts +0 -308
- package/src/core/checks/stateful.ts +0 -121
- package/src/core/checks/types.ts +0 -305
- package/src/core/checks/zond-extensions.ts +0 -73
- package/src/core/classifier/recommended-action.ts +0 -251
- package/src/core/context/current.ts +0 -51
- package/src/core/context/session.ts +0 -78
- package/src/core/coverage/loader.ts +0 -216
- package/src/core/coverage/reasons.ts +0 -300
- package/src/core/diagnostics/db-analysis.ts +0 -666
- package/src/core/diagnostics/failure-class.ts +0 -140
- package/src/core/diagnostics/failure-hints.ts +0 -112
- package/src/core/diagnostics/spec-pointer.ts +0 -99
- package/src/core/diagnostics/suggested-fixes.ts +0 -155
- package/src/core/exporter/case-study/index.ts +0 -270
- package/src/core/exporter/curl.ts +0 -40
- package/src/core/exporter/exporter.ts +0 -48
- package/src/core/exporter/html-report/escape.ts +0 -24
- package/src/core/exporter/html-report/index.ts +0 -479
- package/src/core/exporter/html-report/script.ts +0 -100
- package/src/core/exporter/html-report/styles.ts +0 -408
- package/src/core/generator/catalog-builder.ts +0 -179
- package/src/core/generator/chunker.ts +0 -78
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/coverage-scanner.ts +0 -87
- package/src/core/generator/data-factory.ts +0 -759
- package/src/core/generator/describe.ts +0 -302
- package/src/core/generator/endpoint-warnings.ts +0 -52
- package/src/core/generator/fixtures-builder.ts +0 -332
- package/src/core/generator/index.ts +0 -14
- package/src/core/generator/openapi-reader.ts +0 -297
- package/src/core/generator/path-param-disambig.ts +0 -140
- package/src/core/generator/resources-builder.ts +0 -898
- package/src/core/generator/schema-utils.ts +0 -112
- package/src/core/generator/serializer.ts +0 -343
- package/src/core/generator/suite-generator.ts +0 -1301
- package/src/core/generator/types.ts +0 -63
- package/src/core/identity/identity-file.ts +0 -0
- package/src/core/lint/affects.ts +0 -28
- package/src/core/lint/config.ts +0 -96
- package/src/core/lint/format.ts +0 -42
- package/src/core/lint/index.ts +0 -94
- package/src/core/lint/reporter.ts +0 -128
- package/src/core/lint/rules/consistency.ts +0 -158
- package/src/core/lint/rules/heuristics.ts +0 -97
- package/src/core/lint/rules/strictness.ts +0 -109
- package/src/core/lint/types.ts +0 -96
- package/src/core/lint/walker.ts +0 -248
- package/src/core/meta/meta-store.ts +0 -11
- package/src/core/output/README.md +0 -73
- package/src/core/output/index.ts +0 -13
- package/src/core/output/run.ts +0 -91
- package/src/core/output/types.ts +0 -122
- package/src/core/parser/dynamic-values.ts +0 -160
- package/src/core/parser/env-interpolation.ts +0 -104
- package/src/core/parser/filter.ts +0 -97
- package/src/core/parser/schema.ts +0 -359
- package/src/core/parser/types.ts +0 -117
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +0 -181
- package/src/core/probe/bootstrap.ts +0 -34
- package/src/core/probe/dry-run-envelope.ts +0 -61
- package/src/core/probe/mass-assignment/classify.ts +0 -175
- package/src/core/probe/mass-assignment/cleanup.ts +0 -52
- package/src/core/probe/mass-assignment/digest.ts +0 -114
- package/src/core/probe/mass-assignment/orchestrator.ts +0 -459
- package/src/core/probe/mass-assignment/regression.ts +0 -141
- package/src/core/probe/mass-assignment/suspects.ts +0 -92
- package/src/core/probe/mass-assignment/types.ts +0 -135
- package/src/core/probe/mass-assignment-probe-class.ts +0 -198
- package/src/core/probe/mass-assignment-probe.ts +0 -27
- package/src/core/probe/mass-assignment-template.ts +0 -240
- package/src/core/probe/method-probe.ts +0 -164
- package/src/core/probe/method-shared.ts +0 -69
- package/src/core/probe/negative-probe.ts +0 -691
- package/src/core/probe/orphan-tracker.ts +0 -188
- package/src/core/probe/path-discovery.ts +0 -439
- package/src/core/probe/probe-harness.ts +0 -119
- package/src/core/probe/registry.ts +0 -89
- package/src/core/probe/runner.ts +0 -136
- package/src/core/probe/security/baseline.ts +0 -174
- package/src/core/probe/security/classify.ts +0 -341
- package/src/core/probe/security/cleanup.ts +0 -125
- package/src/core/probe/security/detectors.ts +0 -71
- package/src/core/probe/security/digest.ts +0 -104
- package/src/core/probe/security/orchestrator.ts +0 -398
- package/src/core/probe/security/regression.ts +0 -103
- package/src/core/probe/security/types.ts +0 -151
- package/src/core/probe/security-probe-class.ts +0 -207
- package/src/core/probe/security-probe.ts +0 -32
- package/src/core/probe/shared.ts +0 -531
- package/src/core/probe/static-probe-class.ts +0 -125
- package/src/core/probe/types.ts +0 -165
- package/src/core/probe/verdict-aggregator.ts +0 -33
- package/src/core/probe/webhooks-probe.ts +0 -282
- package/src/core/reporter/console.ts +0 -240
- package/src/core/reporter/index.ts +0 -22
- package/src/core/reporter/json.ts +0 -22
- package/src/core/reporter/junit.ts +0 -93
- package/src/core/reporter/ndjson.ts +0 -37
- package/src/core/reporter/types.ts +0 -15
- package/src/core/runner/assertions.ts +0 -383
- package/src/core/runner/async-pool.ts +0 -108
- package/src/core/runner/auth-path.ts +0 -8
- package/src/core/runner/ci-context.ts +0 -72
- package/src/core/runner/executor.ts +0 -652
- package/src/core/runner/expr-eval.ts +0 -41
- package/src/core/runner/form-encode.ts +0 -41
- package/src/core/runner/http-client.ts +0 -224
- package/src/core/runner/learn-drift.ts +0 -293
- package/src/core/runner/preflight-vars.ts +0 -153
- package/src/core/runner/progress-tracker.ts +0 -73
- package/src/core/runner/rate-limiter.ts +0 -185
- package/src/core/runner/run-kind.ts +0 -45
- package/src/core/runner/schema-validator.ts +0 -308
- package/src/core/runner/send-request.ts +0 -232
- package/src/core/runner/transforms.ts +0 -65
- package/src/core/runner/types.ts +0 -94
- package/src/core/secrets/registry.ts +0 -164
- package/src/core/secrets/secrets-file.ts +0 -115
- package/src/core/selectors/operation-filter.ts +0 -144
- package/src/core/setup-api.ts +0 -590
- package/src/core/severity/category.ts +0 -94
- package/src/core/severity/index.ts +0 -58
- package/src/core/spec/infer-schema.ts +0 -102
- package/src/core/spec/layers.ts +0 -154
- package/src/core/spec/merge-specs.ts +0 -156
- package/src/core/spec/schema-from-runs.ts +0 -117
- package/src/core/spec/schema-overlay.ts +0 -130
- package/src/core/util/ajv.ts +0 -13
- package/src/core/util/format-eta.ts +0 -21
- package/src/core/util/headers.ts +0 -9
- package/src/core/util/url.ts +0 -24
- package/src/core/utils.ts +0 -13
- package/src/core/workspace/config.ts +0 -129
- package/src/core/workspace/fixture-gap-report.ts +0 -84
- package/src/core/workspace/fixture-gaps.ts +0 -71
- package/src/core/workspace/manifest.ts +0 -283
- package/src/core/workspace/output-rotation.ts +0 -62
- package/src/core/workspace/root.ts +0 -96
- package/src/core/workspace/triage-path.ts +0 -87
- package/src/db/lint-runs.ts +0 -47
- package/src/db/migrate.ts +0 -128
- package/src/db/migrations/0001_run_kind.sql +0 -25
- package/src/db/migrations/0002_run_kind_request.sql +0 -59
- package/src/db/migrations/sql.d.ts +0 -4
- package/src/db/queries/collections.ts +0 -133
- package/src/db/queries/coverage.ts +0 -9
- package/src/db/queries/dashboard.ts +0 -59
- package/src/db/queries/results.ts +0 -216
- package/src/db/queries/runs.ts +0 -289
- package/src/db/queries/sessions.ts +0 -42
- package/src/db/queries/settings.ts +0 -28
- package/src/db/queries/types.ts +0 -172
- package/src/db/queries.ts +0 -75
- package/src/db/schema.ts +0 -298
package/src/core/runner/types.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export type StepStatus = "pass" | "fail" | "skip" | "error";
|
|
2
|
-
|
|
3
|
-
export interface HttpRequest {
|
|
4
|
-
method: string;
|
|
5
|
-
url: string;
|
|
6
|
-
headers: Record<string, string>;
|
|
7
|
-
body?: string;
|
|
8
|
-
formData?: FormData;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface HttpResponse {
|
|
12
|
-
status: number;
|
|
13
|
-
headers: Record<string, string>;
|
|
14
|
-
body: string;
|
|
15
|
-
body_parsed?: unknown;
|
|
16
|
-
duration_ms: number;
|
|
17
|
-
/** TASK-144: number of network-level retries that preceded this response.
|
|
18
|
-
* 0 when the request succeeded on the first attempt. */
|
|
19
|
-
network_retry_count?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* `kind` lets the UI surface the assertion that matches the test's stated
|
|
24
|
-
* intent (primary) ahead of OpenAPI schema noise (schema) and housekeeping
|
|
25
|
-
* checks like duration/header asserts (auxiliary). Optional for backwards
|
|
26
|
-
* compatibility — older runs render as `primary` by default.
|
|
27
|
-
*/
|
|
28
|
-
export type AssertionKind = "primary" | "schema" | "auxiliary";
|
|
29
|
-
|
|
30
|
-
export interface AssertionResult {
|
|
31
|
-
field: string;
|
|
32
|
-
rule: string;
|
|
33
|
-
passed: boolean;
|
|
34
|
-
actual: unknown;
|
|
35
|
-
expected: unknown;
|
|
36
|
-
kind?: AssertionKind;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface StepResult {
|
|
40
|
-
name: string;
|
|
41
|
-
status: StepStatus;
|
|
42
|
-
duration_ms: number;
|
|
43
|
-
request: HttpRequest;
|
|
44
|
-
response?: HttpResponse;
|
|
45
|
-
assertions: AssertionResult[];
|
|
46
|
-
captures: Record<string, unknown>;
|
|
47
|
-
error?: string;
|
|
48
|
-
provenance?: import("../parser/types.ts").SourceMetadata | null;
|
|
49
|
-
/** TASK-101: classification of why this failure happened — definitely_bug,
|
|
50
|
-
* likely_bug, quirk, env_issue. `null` for passed/skipped/unclassifiable. */
|
|
51
|
-
failure_class?: import("../diagnostics/failure-class.ts").FailureClass | null;
|
|
52
|
-
failure_class_reason?: string | null;
|
|
53
|
-
/** TASK-102: JSON Pointer into the OpenAPI doc + frozen excerpt of the
|
|
54
|
-
* schema at that pointer. Captured at run time so later spec edits don't
|
|
55
|
-
* rewrite history. `null` for manual YAML or when spec isn't available. */
|
|
56
|
-
spec_pointer?: string | null;
|
|
57
|
-
spec_excerpt?: string | null;
|
|
58
|
-
/** TASK-144: how many network-level retries the http-client performed
|
|
59
|
-
* before this step settled. Omitted (or 0) when no retry was needed.
|
|
60
|
-
* Surfaced in the JSON report so flaky-network steps are visible. */
|
|
61
|
-
network_retry?: number;
|
|
62
|
-
/** ARV-157: summary of `--validate-schema` outcome for this step. Present
|
|
63
|
-
* only when the runner had a schema validator attached AND a JSON body
|
|
64
|
-
* was returned (so consumers can distinguish "no drift" from "never
|
|
65
|
-
* validated"). Granular failures still live in `assertions[]` with
|
|
66
|
-
* `kind: "schema"`; this block is the at-a-glance shape skill docs
|
|
67
|
-
* describe and JSON-report consumers grep for. */
|
|
68
|
-
schema_validation?: {
|
|
69
|
-
result: "PASS" | "FAIL" | "no-endpoint" | "no-schema";
|
|
70
|
-
matched_endpoint: { method: string; path: string } | null;
|
|
71
|
-
matched_response_status: string | null;
|
|
72
|
-
error_count: number;
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface TestRunResult {
|
|
77
|
-
suite_name: string;
|
|
78
|
-
suite_tags?: string[];
|
|
79
|
-
suite_description?: string;
|
|
80
|
-
suite_file?: string;
|
|
81
|
-
started_at: string;
|
|
82
|
-
finished_at: string;
|
|
83
|
-
total: number;
|
|
84
|
-
passed: number;
|
|
85
|
-
failed: number;
|
|
86
|
-
skipped: number;
|
|
87
|
-
/** ARV-318: steps whose status is "error" (couldn't execute — env_issue,
|
|
88
|
-
* network, etc.). Previously counted in `total` but in none of
|
|
89
|
-
* passed/failed/skipped, so `total === passed+failed+skipped` silently
|
|
90
|
-
* broke and error steps reconciled into no bucket. Optional for back-compat
|
|
91
|
-
* with older result literals; the runner always sets it. */
|
|
92
|
-
errored?: number;
|
|
93
|
-
steps: StepResult[];
|
|
94
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SecretRegistry — runtime registry of secret values + sanitizer
|
|
3
|
-
* (TASK-166, m-10).
|
|
4
|
-
*
|
|
5
|
-
* The registry is the single point that knows "this string is a secret;
|
|
6
|
-
* if you see it anywhere in a request URL, body, response, log line, or
|
|
7
|
-
* exporter, replace it with `<redacted:<var-name>>`". Every persisted
|
|
8
|
-
* artifact path (DB-write — TASK-167, exporters — TASK-168) calls
|
|
9
|
-
* `redact()` / `redactObject()` before writing, so the user can ship a
|
|
10
|
-
* digest / HTML report without scrubbing tokens by hand.
|
|
11
|
-
*
|
|
12
|
-
* Design rules:
|
|
13
|
-
* - Exact-match only — no heuristics ("looks like a JWT", "starts with
|
|
14
|
-
* `sk_`"). False positives are worse than false negatives here.
|
|
15
|
-
* - Minimum length 8 — protects against `auth_token: ""` or `id: 1`
|
|
16
|
-
* turning every "1" in the report into `<redacted>`.
|
|
17
|
-
* - One marker format documented in one place: `<redacted:<name>>`.
|
|
18
|
-
* - `setEnabled(false)` returns a no-op redactor for `--no-redact` (local
|
|
19
|
-
* debug). Default is enabled.
|
|
20
|
-
*
|
|
21
|
-
* Marker format: `<redacted:auth_token>` — the name comes from the
|
|
22
|
-
* variable that registered the value (e.g. `.env.yaml` key, `--env` flag,
|
|
23
|
-
* `.secrets.yaml` future entry). Anything that opens a redacted artifact
|
|
24
|
-
* sees the variable name and knows where to look it up locally.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/** Minimum length below which a registered value is silently ignored. */
|
|
28
|
-
export const MIN_SECRET_LENGTH = 8;
|
|
29
|
-
|
|
30
|
-
const REDACTED_MARKER_RE = /<redacted:[a-zA-Z0-9_.-]+>/;
|
|
31
|
-
|
|
32
|
-
export interface SecretEntry {
|
|
33
|
-
name: string;
|
|
34
|
-
value: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export class SecretRegistry {
|
|
38
|
-
/** value → name. We keep map keyed by *value* so a single redact pass
|
|
39
|
-
* iterates the unique values rather than all registrations. Two names
|
|
40
|
-
* registering the same value collapse to one entry — the most recent
|
|
41
|
-
* wins. */
|
|
42
|
-
private byValue = new Map<string, string>();
|
|
43
|
-
private enabled = true;
|
|
44
|
-
|
|
45
|
-
register(name: string, value: unknown): void {
|
|
46
|
-
if (typeof value !== "string") return;
|
|
47
|
-
if (value.length < MIN_SECRET_LENGTH) return;
|
|
48
|
-
this.byValue.set(value, name);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Bulk-register every string value in a flat object. Used by
|
|
53
|
-
* `.env.yaml` / `.secrets.yaml` loaders so we don't have to know in
|
|
54
|
-
* advance which keys are sensitive. The variable name carried into the
|
|
55
|
-
* marker is the object key.
|
|
56
|
-
*/
|
|
57
|
-
registerAll(entries: Record<string, unknown> | undefined | null): void {
|
|
58
|
-
if (!entries) return;
|
|
59
|
-
for (const [k, v] of Object.entries(entries)) this.register(k, v);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Disable redaction (for `--no-redact` local debug). */
|
|
63
|
-
setEnabled(enabled: boolean): void {
|
|
64
|
-
this.enabled = enabled;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
isEnabled(): boolean {
|
|
68
|
-
return this.enabled;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Names of every var that had a value registered. Stable diagnostic. */
|
|
72
|
-
redactedNames(): string[] {
|
|
73
|
-
return [...new Set(this.byValue.values())].sort();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
hasSecrets(): boolean {
|
|
77
|
-
return this.byValue.size > 0;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Drop all registered secrets — used between test cases. */
|
|
81
|
-
clear(): void {
|
|
82
|
-
this.byValue.clear();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Replace every occurrence of a registered value in `text` with the
|
|
87
|
-
* marker `<redacted:<name>>`. Longest values first, so a token that
|
|
88
|
-
* happens to contain a shorter registered substring still ends up
|
|
89
|
-
* redacted as the more-specific match.
|
|
90
|
-
*/
|
|
91
|
-
redact(text: string): string {
|
|
92
|
-
if (!this.enabled || this.byValue.size === 0) return text;
|
|
93
|
-
if (typeof text !== "string" || text.length === 0) return text;
|
|
94
|
-
|
|
95
|
-
let out = text;
|
|
96
|
-
for (const [value, name] of this.sortedEntries()) {
|
|
97
|
-
if (out.indexOf(value) === -1) continue;
|
|
98
|
-
out = out.split(value).join(`<redacted:${name}>`);
|
|
99
|
-
}
|
|
100
|
-
return out;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Deep-clone variant for arbitrary structured data (request/response
|
|
105
|
-
* bodies, header maps, JSON envelopes). Strings get redacted; numbers,
|
|
106
|
-
* booleans, null, Buffers stay as-is. Cycles are not expected on the
|
|
107
|
-
* artifact paths but we guard with `seen` to be safe.
|
|
108
|
-
*/
|
|
109
|
-
redactObject<T>(obj: T): T {
|
|
110
|
-
if (!this.enabled || this.byValue.size === 0) return obj;
|
|
111
|
-
return this.deepRedact(obj, new WeakSet()) as T;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private sortedEntries(): Array<[string, string]> {
|
|
115
|
-
return [...this.byValue.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private deepRedact(node: unknown, seen: WeakSet<object>): unknown {
|
|
119
|
-
if (node == null) return node;
|
|
120
|
-
if (typeof node === "string") return this.redact(node);
|
|
121
|
-
if (typeof node !== "object") return node;
|
|
122
|
-
if (seen.has(node as object)) return node;
|
|
123
|
-
seen.add(node as object);
|
|
124
|
-
|
|
125
|
-
if (Array.isArray(node)) {
|
|
126
|
-
return node.map((v) => this.deepRedact(v, seen));
|
|
127
|
-
}
|
|
128
|
-
// Buffers / Uint8Array / Date — leave intact.
|
|
129
|
-
if (node instanceof Uint8Array || node instanceof Date) return node;
|
|
130
|
-
|
|
131
|
-
const out: Record<string, unknown> = {};
|
|
132
|
-
for (const [k, v] of Object.entries(node)) {
|
|
133
|
-
out[k] = this.deepRedact(v, seen);
|
|
134
|
-
}
|
|
135
|
-
return out;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Process-wide registry. CLI commands populate it once after loading
|
|
141
|
-
* `.env.yaml` / `.secrets.yaml`; library callers can pass their own
|
|
142
|
-
* instance instead of touching this singleton in tests.
|
|
143
|
-
*/
|
|
144
|
-
let globalRegistry: SecretRegistry | undefined;
|
|
145
|
-
|
|
146
|
-
export function getSecretRegistry(): SecretRegistry {
|
|
147
|
-
if (!globalRegistry) globalRegistry = new SecretRegistry();
|
|
148
|
-
return globalRegistry;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Replace the global registry. Tests use this to reset state. */
|
|
152
|
-
export function setSecretRegistry(reg: SecretRegistry): void {
|
|
153
|
-
globalRegistry = reg;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** Convenience: redact a string via the global registry. */
|
|
157
|
-
export function redact(text: string): string {
|
|
158
|
-
return getSecretRegistry().redact(text);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Convenience: redact a nested object via the global registry. */
|
|
162
|
-
export function redactObject<T>(value: T): T {
|
|
163
|
-
return getSecretRegistry().redactObject(value);
|
|
164
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `.secrets.yaml` — gitignored flat YAML file holding raw secret values
|
|
3
|
-
* for an API (TASK-170, m-10). Companion to `.env.yaml` which references
|
|
4
|
-
* keys here via `@secret:<name>`.
|
|
5
|
-
*
|
|
6
|
-
* # apis/<name>/.secrets.yaml (NEVER committed)
|
|
7
|
-
* auth_token: "tok_..."
|
|
8
|
-
* dsn: "https://...@example.com/..."
|
|
9
|
-
*
|
|
10
|
-
* # apis/<name>/.env.yaml (committable)
|
|
11
|
-
* auth_token: "@secret:auth_token"
|
|
12
|
-
* base_url: "https://api.example.com"
|
|
13
|
-
*
|
|
14
|
-
* Mental model: anything in `.secrets.yaml` is registered with the
|
|
15
|
-
* `SecretRegistry` at load-time, so it gets redacted in any persisted
|
|
16
|
-
* artifact (DB, exporters, digests). Anything in `.env.yaml` is plain.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
20
|
-
import { dirname, join } from "node:path";
|
|
21
|
-
import { getSecretRegistry } from "./registry.ts";
|
|
22
|
-
|
|
23
|
-
const SECRETS_FILENAME = ".secrets.yaml";
|
|
24
|
-
const SECRET_REF_RE = /^@secret:([A-Za-z_][A-Za-z0-9_.-]*)$/;
|
|
25
|
-
|
|
26
|
-
/** Resolved contents of a `.secrets.yaml`. */
|
|
27
|
-
export interface SecretsFile {
|
|
28
|
-
filePath: string;
|
|
29
|
-
values: Record<string, string>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Read `.secrets.yaml` from a directory, register every value with the
|
|
34
|
-
* global SecretRegistry, and return the parsed map. Returns `null` when
|
|
35
|
-
* the file is absent — callers should treat that as "no secrets to
|
|
36
|
-
* register" rather than a failure.
|
|
37
|
-
*/
|
|
38
|
-
export function loadSecretsFile(dir: string): SecretsFile | null {
|
|
39
|
-
const filePath = join(dir, SECRETS_FILENAME);
|
|
40
|
-
if (!existsSync(filePath)) return null;
|
|
41
|
-
const text = readFileSync(filePath, "utf-8");
|
|
42
|
-
let parsed: unknown;
|
|
43
|
-
try {
|
|
44
|
-
parsed = (Bun as any).YAML.parse(text);
|
|
45
|
-
} catch (err) {
|
|
46
|
-
throw new Error(`Failed to parse ${filePath}: ${(err as Error).message}`);
|
|
47
|
-
}
|
|
48
|
-
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
49
|
-
throw new Error(`${filePath} must contain a flat YAML object of key: "value" entries`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const values: Record<string, string> = {};
|
|
53
|
-
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
|
|
54
|
-
if (v == null) continue; // empty placeholder — skip
|
|
55
|
-
if (typeof v === "object") {
|
|
56
|
-
throw new Error(
|
|
57
|
-
`${filePath}: nested values are not supported (key "${k}"). ` +
|
|
58
|
-
`.secrets.yaml is intentionally flat — keep one level of key/value pairs.`,
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
values[k] = String(v);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const reg = getSecretRegistry();
|
|
65
|
-
reg.registerAll(values);
|
|
66
|
-
|
|
67
|
-
return { filePath, values };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Walk up a directory chain to find the first `.secrets.yaml` and load
|
|
72
|
-
* it. Used by the env loader so a single secrets file at the API root
|
|
73
|
-
* (`apis/<name>/.secrets.yaml`) is picked up regardless of which
|
|
74
|
-
* subdirectory `zond run` was invoked from.
|
|
75
|
-
*/
|
|
76
|
-
export function loadSecretsFromAncestor(start: string, stopAt?: string): SecretsFile | null {
|
|
77
|
-
let dir = start;
|
|
78
|
-
for (let i = 0; i < 8; i++) {
|
|
79
|
-
const file = loadSecretsFile(dir);
|
|
80
|
-
if (file) return file;
|
|
81
|
-
if (stopAt && dir === stopAt) return null;
|
|
82
|
-
const parent = dirname(dir);
|
|
83
|
-
if (parent === dir) return null;
|
|
84
|
-
dir = parent;
|
|
85
|
-
}
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Resolve any `@secret:<name>` reference inside an env object against
|
|
91
|
-
* the values from `secrets`. Throws when a referenced name is missing
|
|
92
|
-
* (fail-loud).
|
|
93
|
-
*/
|
|
94
|
-
export function resolveSecretRefs(
|
|
95
|
-
envValues: Record<string, string>,
|
|
96
|
-
secrets: SecretsFile | null,
|
|
97
|
-
filePath: string,
|
|
98
|
-
): Record<string, string> {
|
|
99
|
-
const out: Record<string, string> = { ...envValues };
|
|
100
|
-
for (const [k, v] of Object.entries(out)) {
|
|
101
|
-
const m = typeof v === "string" ? v.match(SECRET_REF_RE) : null;
|
|
102
|
-
if (!m) continue;
|
|
103
|
-
const refName = m[1]!;
|
|
104
|
-
const value = secrets?.values[refName];
|
|
105
|
-
if (value == null) {
|
|
106
|
-
const where = secrets ? secrets.filePath : `${dirname(filePath)}/${SECRETS_FILENAME}`;
|
|
107
|
-
throw new Error(
|
|
108
|
-
`${filePath}: key "${k}" references @secret:${refName} but no such entry exists in ${where}. ` +
|
|
109
|
-
`Add \`${refName}: "<value>"\` to ${where} (or remove the @secret: prefix to use a literal value).`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
out[k] = value;
|
|
113
|
-
}
|
|
114
|
-
return out;
|
|
115
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified operation filter (m-15 ARV-9).
|
|
3
|
-
*
|
|
4
|
-
* Parses `--include`/`--exclude` filter specs in a single grammar so
|
|
5
|
-
* `zond run`, `zond checks`, `zond probe`, and `zond generate` all
|
|
6
|
-
* accept the same `<selector>:<value>` strings without each command
|
|
7
|
-
* inventing its own flag set.
|
|
8
|
-
*
|
|
9
|
-
* Grammar:
|
|
10
|
-
*
|
|
11
|
-
* <spec> := <selector> ":" <value>
|
|
12
|
-
* <selector> := "path" | "method" | "tag" | "operation-id" | "operationId"
|
|
13
|
-
* <value>:
|
|
14
|
-
* path — POSIX-style regex matched against `op.path`
|
|
15
|
-
* method — comma-separated HTTP methods, case-insensitive
|
|
16
|
-
* tag — comma-separated tag names, exact match (case-sensitive)
|
|
17
|
-
* operation-id — POSIX-style regex matched against `op.operationId`
|
|
18
|
-
*
|
|
19
|
-
* Semantics:
|
|
20
|
-
* - Multiple `--include` flags combine with OR (op passes if it
|
|
21
|
-
* matches *any* include); when no include is given, every op is
|
|
22
|
-
* considered included.
|
|
23
|
-
* - `--exclude` always removes a match — combines with OR too.
|
|
24
|
-
* - Excludes are evaluated *after* includes.
|
|
25
|
-
*
|
|
26
|
-
* Errors are returned in a `errors[]` array on the compile result so
|
|
27
|
-
* the CLI can surface a friendly multi-line message instead of a
|
|
28
|
-
* stack trace (AC #4).
|
|
29
|
-
*/
|
|
30
|
-
import type { EndpointInfo } from "../generator/types.ts";
|
|
31
|
-
|
|
32
|
-
export type SelectorKind = "path" | "method" | "tag" | "operation-id";
|
|
33
|
-
|
|
34
|
-
const SELECTOR_ALIASES: Record<string, SelectorKind> = {
|
|
35
|
-
path: "path",
|
|
36
|
-
method: "method",
|
|
37
|
-
tag: "tag",
|
|
38
|
-
"operation-id": "operation-id",
|
|
39
|
-
operationid: "operation-id",
|
|
40
|
-
operation_id: "operation-id",
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export interface ParsedSelector {
|
|
44
|
-
kind: SelectorKind;
|
|
45
|
-
raw: string;
|
|
46
|
-
/** Regex form for `path` and `operation-id` selectors. */
|
|
47
|
-
pattern?: RegExp;
|
|
48
|
-
/** Lowercase token list for `method`/`tag` selectors. */
|
|
49
|
-
values?: string[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export type ParseResult =
|
|
53
|
-
| { ok: true; selector: ParsedSelector }
|
|
54
|
-
| { ok: false; error: string };
|
|
55
|
-
|
|
56
|
-
export function parseFilterSpec(spec: string): ParseResult {
|
|
57
|
-
const idx = spec.indexOf(":");
|
|
58
|
-
if (idx <= 0) {
|
|
59
|
-
return { ok: false, error: `Filter "${spec}": expected "<selector>:<value>" (e.g. path:/users/.*)` };
|
|
60
|
-
}
|
|
61
|
-
const head = spec.slice(0, idx).trim().toLowerCase();
|
|
62
|
-
const tail = spec.slice(idx + 1).trim();
|
|
63
|
-
if (tail.length === 0) {
|
|
64
|
-
return { ok: false, error: `Filter "${spec}": value is empty after "${head}:"` };
|
|
65
|
-
}
|
|
66
|
-
const kind = SELECTOR_ALIASES[head];
|
|
67
|
-
if (!kind) {
|
|
68
|
-
const known = Object.keys(SELECTOR_ALIASES).filter((k) => k === SELECTOR_ALIASES[k]).join(", ");
|
|
69
|
-
return { ok: false, error: `Filter "${spec}": unknown selector "${head}". Known: ${known}` };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (kind === "path" || kind === "operation-id") {
|
|
73
|
-
let pattern: RegExp;
|
|
74
|
-
try {
|
|
75
|
-
pattern = new RegExp(tail);
|
|
76
|
-
} catch (err) {
|
|
77
|
-
return { ok: false, error: `Filter "${spec}": invalid regex — ${(err as Error).message}` };
|
|
78
|
-
}
|
|
79
|
-
return { ok: true, selector: { kind, raw: spec, pattern } };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// method / tag — comma-separated.
|
|
83
|
-
const values = tail.split(",").map((v) => v.trim()).filter(Boolean);
|
|
84
|
-
if (values.length === 0) {
|
|
85
|
-
return { ok: false, error: `Filter "${spec}": no values after "${head}:"` };
|
|
86
|
-
}
|
|
87
|
-
if (kind === "method") {
|
|
88
|
-
return { ok: true, selector: { kind, raw: spec, values: values.map((v) => v.toUpperCase()) } };
|
|
89
|
-
}
|
|
90
|
-
return { ok: true, selector: { kind, raw: spec, values } };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function selectorMatches(sel: ParsedSelector, op: EndpointInfo): boolean {
|
|
94
|
-
switch (sel.kind) {
|
|
95
|
-
case "path":
|
|
96
|
-
return sel.pattern!.test(op.path);
|
|
97
|
-
case "method":
|
|
98
|
-
return sel.values!.includes(op.method.toUpperCase());
|
|
99
|
-
case "tag":
|
|
100
|
-
return op.tags.some((t) => sel.values!.includes(t));
|
|
101
|
-
case "operation-id":
|
|
102
|
-
return op.operationId !== undefined && sel.pattern!.test(op.operationId);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface CompileFilterOptions {
|
|
107
|
-
includes?: string[];
|
|
108
|
-
excludes?: string[];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export interface CompiledFilter {
|
|
112
|
-
filter: (op: EndpointInfo) => boolean;
|
|
113
|
-
errors: string[];
|
|
114
|
-
/** Parsed selectors — handy for debug `--explain` output. */
|
|
115
|
-
parsed: { includes: ParsedSelector[]; excludes: ParsedSelector[] };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function compileOperationFilter(opts: CompileFilterOptions = {}): CompiledFilter {
|
|
119
|
-
const errors: string[] = [];
|
|
120
|
-
const includes: ParsedSelector[] = [];
|
|
121
|
-
const excludes: ParsedSelector[] = [];
|
|
122
|
-
for (const raw of opts.includes ?? []) {
|
|
123
|
-
const r = parseFilterSpec(raw);
|
|
124
|
-
if (r.ok) includes.push(r.selector);
|
|
125
|
-
else errors.push(r.error);
|
|
126
|
-
}
|
|
127
|
-
for (const raw of opts.excludes ?? []) {
|
|
128
|
-
const r = parseFilterSpec(raw);
|
|
129
|
-
if (r.ok) excludes.push(r.selector);
|
|
130
|
-
else errors.push(r.error);
|
|
131
|
-
}
|
|
132
|
-
const filter = (op: EndpointInfo): boolean => {
|
|
133
|
-
if (includes.length > 0) {
|
|
134
|
-
const passInclude = includes.some((s) => selectorMatches(s, op));
|
|
135
|
-
if (!passInclude) return false;
|
|
136
|
-
}
|
|
137
|
-
for (const s of excludes) {
|
|
138
|
-
if (selectorMatches(s, op)) return false;
|
|
139
|
-
}
|
|
140
|
-
return true;
|
|
141
|
-
};
|
|
142
|
-
return { filter, errors, parsed: { includes, excludes } };
|
|
143
|
-
}
|
|
144
|
-
|