@aexhq/sdk 0.13.6

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.
Files changed (112) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +160 -0
  3. package/dist/_contracts/connection-ticket.d.ts +21 -0
  4. package/dist/_contracts/connection-ticket.js +49 -0
  5. package/dist/_contracts/event-envelope.d.ts +276 -0
  6. package/dist/_contracts/event-envelope.js +324 -0
  7. package/dist/_contracts/event-stream-client.d.ts +47 -0
  8. package/dist/_contracts/event-stream-client.js +141 -0
  9. package/dist/_contracts/http.d.ts +35 -0
  10. package/dist/_contracts/http.js +114 -0
  11. package/dist/_contracts/index.d.ts +28 -0
  12. package/dist/_contracts/index.js +29 -0
  13. package/dist/_contracts/managed-key.d.ts +74 -0
  14. package/dist/_contracts/managed-key.js +110 -0
  15. package/dist/_contracts/operations.d.ts +237 -0
  16. package/dist/_contracts/operations.js +632 -0
  17. package/dist/_contracts/provider-support.d.ts +220 -0
  18. package/dist/_contracts/provider-support.js +90 -0
  19. package/dist/_contracts/proxy-protocol.d.ts +257 -0
  20. package/dist/_contracts/proxy-protocol.js +234 -0
  21. package/dist/_contracts/proxy-validation.d.ts +19 -0
  22. package/dist/_contracts/proxy-validation.js +51 -0
  23. package/dist/_contracts/run-artifacts.d.ts +47 -0
  24. package/dist/_contracts/run-artifacts.js +101 -0
  25. package/dist/_contracts/run-config.d.ts +304 -0
  26. package/dist/_contracts/run-config.js +659 -0
  27. package/dist/_contracts/run-cost.d.ts +125 -0
  28. package/dist/_contracts/run-cost.js +616 -0
  29. package/dist/_contracts/run-custody.d.ts +226 -0
  30. package/dist/_contracts/run-custody.js +465 -0
  31. package/dist/_contracts/run-record.d.ts +127 -0
  32. package/dist/_contracts/run-record.js +177 -0
  33. package/dist/_contracts/run-retention.d.ts +213 -0
  34. package/dist/_contracts/run-retention.js +484 -0
  35. package/dist/_contracts/run-unit.d.ts +194 -0
  36. package/dist/_contracts/run-unit.js +215 -0
  37. package/dist/_contracts/runner-event.d.ts +114 -0
  38. package/dist/_contracts/runner-event.js +187 -0
  39. package/dist/_contracts/runtime-manifest.d.ts +106 -0
  40. package/dist/_contracts/runtime-manifest.js +98 -0
  41. package/dist/_contracts/runtime-security-profile.d.ts +27 -0
  42. package/dist/_contracts/runtime-security-profile.js +82 -0
  43. package/dist/_contracts/runtime-sizes.d.ts +144 -0
  44. package/dist/_contracts/runtime-sizes.js +136 -0
  45. package/dist/_contracts/runtime-types.d.ts +212 -0
  46. package/dist/_contracts/runtime-types.js +2 -0
  47. package/dist/_contracts/sdk-errors.d.ts +34 -0
  48. package/dist/_contracts/sdk-errors.js +52 -0
  49. package/dist/_contracts/sdk-secrets.d.ts +31 -0
  50. package/dist/_contracts/sdk-secrets.js +220 -0
  51. package/dist/_contracts/side-effect-audit.d.ts +129 -0
  52. package/dist/_contracts/side-effect-audit.js +494 -0
  53. package/dist/_contracts/sse.d.ts +74 -0
  54. package/dist/_contracts/sse.js +0 -0
  55. package/dist/_contracts/stable.d.ts +26 -0
  56. package/dist/_contracts/stable.js +44 -0
  57. package/dist/_contracts/status.d.ts +19 -0
  58. package/dist/_contracts/status.js +61 -0
  59. package/dist/_contracts/submission.d.ts +383 -0
  60. package/dist/_contracts/submission.js +1380 -0
  61. package/dist/agents-md.d.ts +46 -0
  62. package/dist/agents-md.js +83 -0
  63. package/dist/agents-md.js.map +1 -0
  64. package/dist/asset-upload.d.ts +66 -0
  65. package/dist/asset-upload.js +168 -0
  66. package/dist/asset-upload.js.map +1 -0
  67. package/dist/bundle.d.ts +33 -0
  68. package/dist/bundle.js +89 -0
  69. package/dist/bundle.js.map +1 -0
  70. package/dist/cli.mjs +4140 -0
  71. package/dist/cli.mjs.sha256 +1 -0
  72. package/dist/client.d.ts +460 -0
  73. package/dist/client.js +857 -0
  74. package/dist/client.js.map +1 -0
  75. package/dist/fetch-archive.d.ts +16 -0
  76. package/dist/fetch-archive.js +170 -0
  77. package/dist/fetch-archive.js.map +1 -0
  78. package/dist/file.d.ts +57 -0
  79. package/dist/file.js +153 -0
  80. package/dist/file.js.map +1 -0
  81. package/dist/index.d.ts +30 -0
  82. package/dist/index.js +34 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/mcp-server.d.ts +84 -0
  85. package/dist/mcp-server.js +114 -0
  86. package/dist/mcp-server.js.map +1 -0
  87. package/dist/node-fs.d.ts +12 -0
  88. package/dist/node-fs.js +44 -0
  89. package/dist/node-fs.js.map +1 -0
  90. package/dist/proxy-endpoint.d.ts +131 -0
  91. package/dist/proxy-endpoint.js +147 -0
  92. package/dist/proxy-endpoint.js.map +1 -0
  93. package/dist/skill.d.ts +117 -0
  94. package/dist/skill.js +169 -0
  95. package/dist/skill.js.map +1 -0
  96. package/dist/version.d.ts +9 -0
  97. package/dist/version.js +10 -0
  98. package/dist/version.js.map +1 -0
  99. package/docs/cleanup.md +38 -0
  100. package/docs/credentials.md +153 -0
  101. package/docs/events.md +76 -0
  102. package/docs/mcp.md +47 -0
  103. package/docs/outputs.md +157 -0
  104. package/docs/product-boundaries.md +57 -0
  105. package/docs/provider-runtime-capabilities.md +103 -0
  106. package/docs/quickstart.md +110 -0
  107. package/docs/release.md +99 -0
  108. package/docs/run-config.md +53 -0
  109. package/docs/run-record.md +39 -0
  110. package/docs/skills.md +139 -0
  111. package/docs/testing.md +29 -0
  112. package/package.json +47 -0
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Loose record describing a run as the dashboard BFF returns it. Concrete
3
+ * dashboard-managed fields appear in the index signature; the SDK and CLI
4
+ * may surface them without strong typing per-field.
5
+ *
6
+ * `runtimeManifest` is derived from the validated submission + the chosen
7
+ * provider on every read — see `runtime-manifest.ts`. It is `undefined`
8
+ * on responses from BFFs that predate Phase 2 of the runtime-environment
9
+ * rollout; SDK consumers MUST treat it as best-effort and not panic on
10
+ * its absence.
11
+ */
12
+ export interface Run {
13
+ readonly id: string;
14
+ readonly status: string;
15
+ readonly workspaceId?: string;
16
+ readonly createdAt?: string;
17
+ readonly updatedAt?: string;
18
+ readonly terminalAt?: string | null;
19
+ readonly errorMessage?: string | null;
20
+ readonly usage?: UsageSummary;
21
+ readonly costTelemetry?: import("./run-cost.js").RunCostTelemetry;
22
+ readonly runtimeManifest?: import("./runtime-manifest.js").RuntimeManifest;
23
+ readonly [key: string]: unknown;
24
+ }
25
+ export interface UsageSummary {
26
+ readonly inputTokens?: number;
27
+ readonly outputTokens?: number;
28
+ readonly cacheReadInputTokens?: number;
29
+ readonly cacheCreationInputTokens?: number;
30
+ readonly totalTokens?: number;
31
+ }
32
+ /**
33
+ * A run event as recorded by the dashboard. Includes the `type` field
34
+ * that the `is*Event` type guards narrow on plus any provider payload.
35
+ *
36
+ * The unified-stream discriminators (`channel`, `source`, `sourceSeq`,
37
+ * `emittedAt`, `receivedAt`, `level`) carry through from the coordinator
38
+ * envelope (see `event-envelope.ts` —
39
+ * {@link import("./event-envelope.js").AexEvent}) so SDK/CLI consumers can
40
+ * split/filter the one stream by channel or source. All are OPTIONAL: archived
41
+ * events from before the unification (and any producer that omits an ordering
42
+ * attribute) lack them, an absent `channel` means `"event"` (see `channelOf`),
43
+ * and `level` is present only on `log`-channel records.
44
+ */
45
+ export interface RunEvent {
46
+ readonly id: string;
47
+ readonly type: string;
48
+ readonly runId?: string;
49
+ readonly recordedAt?: string;
50
+ /** Which sub-stream this record rides — `"event"` (typed) or `"log"`. Absent ⇒ `"event"`. */
51
+ readonly channel?: import("./event-envelope.js").AexEventChannel;
52
+ /** Coarse origin classifier (agent/worker/runtime/mcp/aex/workflow/machine). */
53
+ readonly source?: import("./event-envelope.js").AexEventSource;
54
+ /** Per-source monotonic counter assigned at the source (carried, not re-ordered). */
55
+ readonly sourceSeq?: number;
56
+ /** Source wall-clock ms at emit (carried for a best-effort client time view). */
57
+ readonly emittedAt?: number;
58
+ /** The DO's authoritative receive-time (wall-clock ms) stamped at ingest, companion to `seq`. */
59
+ readonly receivedAt?: number;
60
+ /** Log severity, first-class on a `channel: "log"` record ("info" | "warn" | "error"). */
61
+ readonly level?: import("./event-envelope.js").AexLogLevel;
62
+ readonly [key: string]: unknown;
63
+ }
64
+ /**
65
+ * Provider-emitted event payload. Provider-specific fields are passed through
66
+ * structurally so historical events and managed-runner events can be displayed
67
+ * and downloaded without each client needing provider-specific schemas.
68
+ */
69
+ export interface ProviderEvent {
70
+ readonly type: string;
71
+ readonly id?: string | undefined;
72
+ readonly created_at?: string | undefined;
73
+ readonly [key: string]: unknown;
74
+ }
75
+ /**
76
+ * One captured output file as the dashboard reports it. Use
77
+ * `createOutputLink` to get a short-lived signed URL for download.
78
+ */
79
+ export interface Output {
80
+ readonly id: string;
81
+ readonly filename?: string;
82
+ readonly sizeBytes?: number;
83
+ readonly contentType?: string;
84
+ readonly createdAt?: string;
85
+ readonly [key: string]: unknown;
86
+ }
87
+ export type OutputFilePathMatch = "exact" | "suffix";
88
+ export interface OutputFilePathSelector {
89
+ readonly path: string;
90
+ readonly match?: OutputFilePathMatch;
91
+ }
92
+ export interface OutputFileIdSelector {
93
+ readonly id: string;
94
+ }
95
+ export type OutputFileSelector = Output | OutputFileIdSelector | OutputFilePathSelector;
96
+ export interface OutputFileDownload {
97
+ readonly output: Output;
98
+ readonly bytes: Uint8Array;
99
+ }
100
+ export interface SignedOutputLink {
101
+ readonly url: string;
102
+ readonly expiresAt?: string;
103
+ readonly [key: string]: unknown;
104
+ }
105
+ export interface WhoAmI {
106
+ readonly principalType: "api_token" | "user";
107
+ readonly workspaceId?: string;
108
+ readonly tokenId?: string;
109
+ readonly tokenName?: string | null;
110
+ readonly scopes?: readonly string[];
111
+ /**
112
+ * Workspace-level caps the BFF will enforce on subsequent calls.
113
+ * Surfaced so consumers (e.g. broll's app-side admission gate) can
114
+ * decide whether to keep their own gate or rely on platform headers.
115
+ * All fields optional — older BFFs may omit. Numbers are concrete
116
+ * snapshots at the time of the `whoami` call.
117
+ */
118
+ readonly caps?: {
119
+ /** Token-bucket cap on POST /api/runs per minute, per workspace. */
120
+ readonly runSubmitPerMinute?: number;
121
+ /** Hard cap on concurrent non-terminal runs the workspace may hold. */
122
+ readonly maxConcurrentRuns?: number;
123
+ /** Storage cap (bytes) on captured output objects, workspace-wide. */
124
+ readonly storageCapBytes?: number;
125
+ /** Current captured-output usage in bytes. */
126
+ readonly storageUsedBytes?: number;
127
+ /**
128
+ * Wall-clock ceiling on a single run before forced termination.
129
+ * `null` means no aex-imposed cap, but this is **not unlimited
130
+ * overall**: the managed runner, infrastructure, or upstream provider may
131
+ * still impose a ceiling, and a run that exceeds it terminates regardless.
132
+ */
133
+ readonly maxRunDurationMs?: number | null;
134
+ };
135
+ readonly [key: string]: unknown;
136
+ }
137
+ /**
138
+ * Workspace skill bundle as the dashboard BFF returns it. Mirrors a row
139
+ * of `skill_bundles` joined with its computed manifest. `state` is the
140
+ * upload lifecycle (`pending` -> `ready`); only `ready` rows are
141
+ * referenceable from a run. `deletedAt` is the soft-delete tombstone
142
+ * (`null` for live bundles).
143
+ *
144
+ * See the public architecture notes and server-side persistence schema for
145
+ * the authoritative shape.
146
+ */
147
+ export interface Skill {
148
+ readonly id: string;
149
+ readonly workspaceId?: string;
150
+ readonly name: string;
151
+ readonly state: "pending" | "ready";
152
+ readonly hash?: string | null;
153
+ readonly sizeBytes?: number | null;
154
+ readonly fileCount?: number | null;
155
+ readonly manifest?: ReadonlyArray<{
156
+ readonly path: string;
157
+ readonly size: number;
158
+ readonly mode: number;
159
+ }>;
160
+ readonly createdAt?: string;
161
+ readonly updatedAt?: string;
162
+ readonly finalizedAt?: string | null;
163
+ readonly deletedAt?: string | null;
164
+ readonly [key: string]: unknown;
165
+ }
166
+ /**
167
+ * Wire-level record for a workspace AgentsMd file as returned by the BFF.
168
+ * Mirrors `PublicWorkspaceFile` from the dashboard service layer.
169
+ */
170
+ export interface AgentsMdRecord {
171
+ readonly id: string;
172
+ readonly kind?: "agentsmd";
173
+ readonly name: string;
174
+ readonly state: "pending" | "ready";
175
+ readonly hash?: string | null;
176
+ readonly sizeBytes?: number | null;
177
+ readonly fileCount?: number | null;
178
+ readonly manifest?: ReadonlyArray<{
179
+ readonly path: string;
180
+ readonly size: number;
181
+ readonly mode: number;
182
+ }>;
183
+ readonly createdAt?: string;
184
+ readonly updatedAt?: string;
185
+ readonly finalizedAt?: string | null;
186
+ readonly deletedAt?: string | null;
187
+ readonly [key: string]: unknown;
188
+ }
189
+ /**
190
+ * Wire-level record for a workspace File as returned by the BFF.
191
+ * Mirrors `PublicWorkspaceFile` from the dashboard service layer
192
+ * with kind='file' and `f_*` ids.
193
+ */
194
+ export interface FileRecord {
195
+ readonly id: string;
196
+ readonly kind?: "file";
197
+ readonly name: string;
198
+ readonly state: "pending" | "ready";
199
+ readonly hash?: string | null;
200
+ readonly sizeBytes?: number | null;
201
+ readonly fileCount?: number | null;
202
+ readonly manifest?: ReadonlyArray<{
203
+ readonly path: string;
204
+ readonly size: number;
205
+ readonly mode: number;
206
+ }>;
207
+ readonly createdAt?: string;
208
+ readonly updatedAt?: string;
209
+ readonly finalizedAt?: string | null;
210
+ readonly deletedAt?: string | null;
211
+ readonly [key: string]: unknown;
212
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runtime-types.js.map
@@ -0,0 +1,34 @@
1
+ export type AexErrorCode = "RUN_CONFIG_INVALID" | "CREDENTIAL_INVALID" | "PROVIDER_ERROR" | "RUN_STATE_ERROR" | "CLEANUP_ERROR" | "RUNTIME_UNSUPPORTED" | "API_ERROR";
2
+ export declare class AexError extends Error {
3
+ readonly code: AexErrorCode;
4
+ readonly details?: unknown;
5
+ constructor(code: AexErrorCode, message: string, details?: unknown);
6
+ }
7
+ export declare class RunConfigValidationError extends AexError {
8
+ constructor(message: string, details?: unknown);
9
+ }
10
+ export declare class CredentialValidationError extends AexError {
11
+ constructor(message: string, details?: unknown);
12
+ }
13
+ export declare class ProviderError extends AexError {
14
+ readonly status: number | undefined;
15
+ constructor(message: string, options?: {
16
+ status?: number;
17
+ details?: unknown;
18
+ });
19
+ }
20
+ export declare class RunStateError extends AexError {
21
+ constructor(message: string, details?: unknown);
22
+ }
23
+ export declare class CleanupError extends AexError {
24
+ constructor(message: string, details?: unknown);
25
+ }
26
+ /**
27
+ * Thrown by SDK and CLI operations when the dashboard BFF returns a non-2xx
28
+ * response. Carries the HTTP status and parsed body for the caller to inspect.
29
+ */
30
+ export declare class AexApiError extends AexError {
31
+ readonly status: number;
32
+ readonly body: unknown;
33
+ constructor(status: number, message: string, body: unknown);
34
+ }
@@ -0,0 +1,52 @@
1
+ import { redactSecrets } from "./sdk-secrets.js";
2
+ export class AexError extends Error {
3
+ code;
4
+ details;
5
+ constructor(code, message, details) {
6
+ super(redactSecrets(message));
7
+ this.name = this.constructor.name;
8
+ this.code = code;
9
+ this.details = details === undefined ? undefined : redactSecrets(details);
10
+ }
11
+ }
12
+ export class RunConfigValidationError extends AexError {
13
+ constructor(message, details) {
14
+ super("RUN_CONFIG_INVALID", message, details);
15
+ }
16
+ }
17
+ export class CredentialValidationError extends AexError {
18
+ constructor(message, details) {
19
+ super("CREDENTIAL_INVALID", message, details);
20
+ }
21
+ }
22
+ export class ProviderError extends AexError {
23
+ status;
24
+ constructor(message, options = {}) {
25
+ super("PROVIDER_ERROR", message, options.details);
26
+ this.status = options.status;
27
+ }
28
+ }
29
+ export class RunStateError extends AexError {
30
+ constructor(message, details) {
31
+ super("RUN_STATE_ERROR", message, details);
32
+ }
33
+ }
34
+ export class CleanupError extends AexError {
35
+ constructor(message, details) {
36
+ super("CLEANUP_ERROR", message, details);
37
+ }
38
+ }
39
+ /**
40
+ * Thrown by SDK and CLI operations when the dashboard BFF returns a non-2xx
41
+ * response. Carries the HTTP status and parsed body for the caller to inspect.
42
+ */
43
+ export class AexApiError extends AexError {
44
+ status;
45
+ body;
46
+ constructor(status, message, body) {
47
+ super("API_ERROR", message, body);
48
+ this.status = status;
49
+ this.body = redactSecrets(body);
50
+ }
51
+ }
52
+ //# sourceMappingURL=sdk-errors.js.map
@@ -0,0 +1,31 @@
1
+ import { Transform } from "node:stream";
2
+ export declare class SecretString {
3
+ #private;
4
+ constructor(value: string, label?: string);
5
+ unwrap(): string;
6
+ toString(): string;
7
+ toJSON(): string;
8
+ }
9
+ export declare function redactSecrets<T>(value: T): T;
10
+ /**
11
+ * Redact every secret-shaped run in `input`.
12
+ *
13
+ * `known` is an OPTIONAL belt-and-suspenders set of literal values to also mask
14
+ * (e.g. creds the caller happens to have loaded). Correctness does NOT depend on
15
+ * it — every shape above is caught with or without seeding — but masking known
16
+ * values first removes any residual substring of a value the shapes split.
17
+ */
18
+ export declare function redactString(input: string, known?: Iterable<string>): string;
19
+ export declare function containsSecretLikeValue(input: string): boolean;
20
+ /**
21
+ * A line-buffered `Transform` that redacts secrets BEFORE bytes touch disk or
22
+ * console. Pipe a child process's stdout/stderr through it
23
+ * (`child.stdout.pipe(createRedactingStream()).pipe(process.stdout)`) so a
24
+ * secret can never be written and "cleaned up after"; the secret you didn't
25
+ * recognise is the one already on disk.
26
+ *
27
+ * Buffers by line so a secret split across a chunk boundary is still redacted:
28
+ * a partial trailing line is held until the next chunk (or `flush`) completes
29
+ * it. `known` is forwarded to `redactString` for optional value seeding.
30
+ */
31
+ export declare function createRedactingStream(known?: Iterable<string>): Transform;
@@ -0,0 +1,220 @@
1
+ import { Transform } from "node:stream";
2
+ /**
3
+ * Value-AGNOSTIC secret patterns: each matches a SHAPE, not a known value.
4
+ * Correctness must not depend on seeding the redactor with the literal
5
+ * secret — the two real leaks this project hit were a database password that
6
+ * survived a naive `sed` mask (as a substring) and an Anthropic key emitted by
7
+ * `wrangler workflows describe` that the harness NEVER loaded. A value-seeded
8
+ * redactor is blind to both; these patterns catch them by form.
9
+ *
10
+ * Ordering matters: structured shapes (connection strings, auth headers, JWT)
11
+ * come BEFORE the generic key/entropy catch-alls so the more descriptive
12
+ * `[REDACTED …]` label wins on overlapping matches.
13
+ */
14
+ const SECRET_PATTERNS = [
15
+ // postgres / postgresql connection strings — redact the whole URI so the
16
+ // embedded password can never survive as a substring (the `sed`-mask leak).
17
+ /\bpostgres(?:ql)?:\/\/[^\s"']+/gi,
18
+ // Authorization: Bearer <token> — redact the credential, keep the header name
19
+ // so logs stay legible.
20
+ /\b(Authorization\s*:\s*Bearer)\s+[A-Za-z0-9._~+/=-]{8,}/gi,
21
+ // Anthropic + OpenAI-style prefixed keys.
22
+ /sk-ant-[A-Za-z0-9_-]{16,}/g,
23
+ /sk-[A-Za-z0-9_-]{20,}/g,
24
+ // aex workspace / proxy tokens: apt_… / ant_….
25
+ /\b(?:apt|ant)_[A-Za-z0-9_-]{16,}/g,
26
+ // Slack tokens.
27
+ /xox[pbar]-[A-Za-z0-9-]{10,}/g,
28
+ // AWS access key id + secret access key shapes.
29
+ /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,
30
+ /\baws_secret_access_key["'\s:=]+[A-Za-z0-9/+=]{40}/gi,
31
+ // JWT-shaped: header.payload.signature, each base64url, header starts `eyJ`.
32
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
33
+ // Keyword-introduced secret assignments (bearer/token/api_key/…).
34
+ /(?:bearer|token|api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret)["'\s:=]+[A-Za-z0-9_\-./+=]{12,}/gi
35
+ ];
36
+ const REDACTED = "[REDACTED]";
37
+ /**
38
+ * Generic high-entropy token catch-all, applied AFTER the named patterns.
39
+ * Catches the secret nobody recognised: any long base64url/hex-ish run that is
40
+ * BOTH high-entropy AND character-class-diverse.
41
+ *
42
+ * The candidate run deliberately EXCLUDES `_`: secrets are contiguous opaque
43
+ * runs, whereas `SCREAMING_SNAKE_CASE` env names and `snake_case` identifiers
44
+ * are `_`-separated words — excluding `_` breaks those into sub-24 fragments so
45
+ * they are never eaten (the `CF_CONFORMANCE_PROBE_URL` false positive). The
46
+ * class-diversity gate (≥2 of lower/upper/digit) then keeps the remaining
47
+ * single-class runs (all-lowercase module paths like `internal/modules/esm`)
48
+ * while still catching mixed-case/alnum secret blobs. Entropy alone cannot
49
+ * separate these — `the_quick…` (4.16) out-scores a real key prefix.
50
+ */
51
+ const HIGH_ENTROPY_CANDIDATE = /[A-Za-z0-9+/=-]{24,}/g;
52
+ const ENTROPY_BITS_PER_CHAR = 3.0;
53
+ const MIN_CHAR_CLASSES = 2;
54
+ /**
55
+ * A mixed-case run with no digit and < this length is treated as a benign
56
+ * identifier, not a secret. Real opaque secrets are alnum-mixed (carry a
57
+ * digit) or very long; digit-free camelCase identifiers like
58
+ * `asyncRunEntryPointWithESMLoader` / `createDurableObjectNamespace` (which
59
+ * appear in stack traces the diagnostic bundle captures) are 24–39 chars and
60
+ * digit-free — eating them would gut debuggability. The length escape hatch
61
+ * still catches the rare long digit-free secret.
62
+ */
63
+ const HIGH_ENTROPY_NO_DIGIT_MIN_LEN = 40;
64
+ export class SecretString {
65
+ #value;
66
+ constructor(value, label = "secret") {
67
+ if (!value) {
68
+ throw new Error(`${label} is required`);
69
+ }
70
+ this.#value = value;
71
+ }
72
+ unwrap() {
73
+ return this.#value;
74
+ }
75
+ toString() {
76
+ return REDACTED;
77
+ }
78
+ toJSON() {
79
+ return REDACTED;
80
+ }
81
+ }
82
+ export function redactSecrets(value) {
83
+ if (typeof value === "string") {
84
+ return redactString(value);
85
+ }
86
+ if (Array.isArray(value)) {
87
+ return value.map((item) => redactSecrets(item));
88
+ }
89
+ if (value && typeof value === "object") {
90
+ const out = {};
91
+ for (const [key, item] of Object.entries(value)) {
92
+ if (isSecretKey(key)) {
93
+ out[key] = REDACTED;
94
+ }
95
+ else {
96
+ out[key] = redactSecrets(item);
97
+ }
98
+ }
99
+ return out;
100
+ }
101
+ return value;
102
+ }
103
+ /**
104
+ * Redact every secret-shaped run in `input`.
105
+ *
106
+ * `known` is an OPTIONAL belt-and-suspenders set of literal values to also mask
107
+ * (e.g. creds the caller happens to have loaded). Correctness does NOT depend on
108
+ * it — every shape above is caught with or without seeding — but masking known
109
+ * values first removes any residual substring of a value the shapes split.
110
+ */
111
+ export function redactString(input, known = []) {
112
+ let out = input;
113
+ for (const value of known) {
114
+ if (value && value.length >= 4) {
115
+ out = out.split(value).join(REDACTED);
116
+ }
117
+ }
118
+ out = SECRET_PATTERNS.reduce((current, pattern) => current.replace(pattern, (match, captured) =>
119
+ // Patterns with a captured prefix (Authorization header) keep the
120
+ // prefix; the rest replace the whole match.
121
+ typeof captured === "string" ? `${captured} ${REDACTED}` : REDACTED), out);
122
+ return out.replace(HIGH_ENTROPY_CANDIDATE, (match) => looksHighEntropySecret(match) ? REDACTED : match);
123
+ }
124
+ export function containsSecretLikeValue(input) {
125
+ if (SECRET_PATTERNS.some((pattern) => {
126
+ pattern.lastIndex = 0;
127
+ return pattern.test(input);
128
+ })) {
129
+ return true;
130
+ }
131
+ HIGH_ENTROPY_CANDIDATE.lastIndex = 0;
132
+ let candidate;
133
+ while ((candidate = HIGH_ENTROPY_CANDIDATE.exec(input)) !== null) {
134
+ if (looksHighEntropySecret(candidate[0])) {
135
+ return true;
136
+ }
137
+ }
138
+ return false;
139
+ }
140
+ /**
141
+ * A line-buffered `Transform` that redacts secrets BEFORE bytes touch disk or
142
+ * console. Pipe a child process's stdout/stderr through it
143
+ * (`child.stdout.pipe(createRedactingStream()).pipe(process.stdout)`) so a
144
+ * secret can never be written and "cleaned up after"; the secret you didn't
145
+ * recognise is the one already on disk.
146
+ *
147
+ * Buffers by line so a secret split across a chunk boundary is still redacted:
148
+ * a partial trailing line is held until the next chunk (or `flush`) completes
149
+ * it. `known` is forwarded to `redactString` for optional value seeding.
150
+ */
151
+ export function createRedactingStream(known = []) {
152
+ const knownValues = [...known];
153
+ let carry = "";
154
+ return new Transform({
155
+ transform(chunk, _encoding, callback) {
156
+ carry += chunk.toString();
157
+ const lastNewline = carry.lastIndexOf("\n");
158
+ if (lastNewline === -1) {
159
+ callback();
160
+ return;
161
+ }
162
+ const ready = carry.slice(0, lastNewline + 1);
163
+ carry = carry.slice(lastNewline + 1);
164
+ callback(null, redactString(ready, knownValues));
165
+ },
166
+ flush(callback) {
167
+ if (carry === "") {
168
+ callback();
169
+ return;
170
+ }
171
+ const tail = carry;
172
+ carry = "";
173
+ callback(null, redactString(tail, knownValues));
174
+ }
175
+ });
176
+ }
177
+ function isSecretKey(key) {
178
+ return /(?:api[_-]?key|authorization|token|secret|password|credential)/i.test(key);
179
+ }
180
+ /** A candidate run is a high-entropy secret if it is both information-dense
181
+ * AND mixes character classes (the property that separates opaque secret blobs
182
+ * from single-class identifiers/paths). */
183
+ function looksHighEntropySecret(value) {
184
+ if (charClassCount(value) < MIN_CHAR_CLASSES)
185
+ return false;
186
+ if (shannonEntropyBits(value) < ENTROPY_BITS_PER_CHAR)
187
+ return false;
188
+ // Require a digit OR extreme length so digit-free mixed-case identifiers
189
+ // (stack-trace frames, API symbol names) are not destroyed in diagnostic
190
+ // output, while real secret shapes (alnum-mixed, or very long) still match.
191
+ return /[0-9]/.test(value) || value.length >= HIGH_ENTROPY_NO_DIGIT_MIN_LEN;
192
+ }
193
+ /** How many of {lowercase, uppercase, digit} appear in `value` (0–3). */
194
+ function charClassCount(value) {
195
+ let count = 0;
196
+ if (/[a-z]/.test(value))
197
+ count++;
198
+ if (/[A-Z]/.test(value))
199
+ count++;
200
+ if (/[0-9]/.test(value))
201
+ count++;
202
+ return count;
203
+ }
204
+ /** Shannon entropy in bits/char — the per-character information density. */
205
+ function shannonEntropyBits(value) {
206
+ if (value.length === 0) {
207
+ return 0;
208
+ }
209
+ const counts = new Map();
210
+ for (const char of value) {
211
+ counts.set(char, (counts.get(char) ?? 0) + 1);
212
+ }
213
+ let bits = 0;
214
+ for (const count of counts.values()) {
215
+ const p = count / value.length;
216
+ bits -= p * Math.log2(p);
217
+ }
218
+ return bits;
219
+ }
220
+ //# sourceMappingURL=sdk-secrets.js.map