@seekrit/cli 2.5.1 → 2.7.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/dist/index.js +1892 -1094
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import
|
|
3
|
+
import * as z$1 from "zod";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { arch, homedir, hostname, platform, tmpdir, userInfo } from "node:os";
|
|
6
6
|
import { basename, dirname, join, parse, resolve } from "node:path";
|
|
@@ -17,48 +17,48 @@ const POLICY_DEFAULT_TTL_SECONDS = 604800;
|
|
|
17
17
|
const POLICY_MIN_TTL_SECONDS = 3600;
|
|
18
18
|
const POLICY_MAX_TTL_SECONDS = 7776e3;
|
|
19
19
|
/** A bare hostname: no scheme, no port, no path, no wildcard. */
|
|
20
|
-
const policyHostSchema = z.string().trim().min(1).max(253).toLowerCase().refine((h) => !/[:/\s*]/.test(h), { message: "host must be a bare hostname (no scheme, port, path, or wildcard)" }).refine((h) => /^[a-z0-9.-]+$/.test(h), { message: "host contains invalid characters" });
|
|
21
|
-
const policyMethodSchema = z.string().trim().toUpperCase().regex(/^[A-Z]{3,10}$/, "method must be an HTTP method name");
|
|
20
|
+
const policyHostSchema = z$1.string().trim().min(1).max(253).toLowerCase().refine((h) => !/[:/\s*]/.test(h), { message: "host must be a bare hostname (no scheme, port, path, or wildcard)" }).refine((h) => /^[a-z0-9.-]+$/.test(h), { message: "host contains invalid characters" });
|
|
21
|
+
const policyMethodSchema = z$1.string().trim().toUpperCase().regex(/^[A-Z]{3,10}$/, "method must be an HTTP method name");
|
|
22
22
|
/**
|
|
23
23
|
* A path pattern. Must be absolute, because it is matched against a request
|
|
24
24
|
* path — a relative pattern is a mistake that would silently match nothing.
|
|
25
25
|
*/
|
|
26
|
-
const policyPathSchema = z.string().trim().min(1).max(512).startsWith("/", "path pattern must start with /").refine((p) => !p.includes("?"), { message: "path patterns match the path only, not the query" });
|
|
27
|
-
const policySecretNameSchema = z.string().trim().regex(/^[A-Za-z0-9_]+$/, "secret names are letters, digits, and underscores");
|
|
26
|
+
const policyPathSchema = z$1.string().trim().min(1).max(512).startsWith("/", "path pattern must start with /").refine((p) => !p.includes("?"), { message: "path patterns match the path only, not the query" });
|
|
27
|
+
const policySecretNameSchema = z$1.string().trim().regex(/^[A-Za-z0-9_]+$/, "secret names are letters, digits, and underscores");
|
|
28
28
|
/** A TCP port. 0 is refused: it never matches a connection, so a rule naming it is a mistake. */
|
|
29
|
-
const policyPortSchema = z.number().int().min(1).max(65535);
|
|
30
|
-
const agentPolicyRuleSchema = z.object({
|
|
29
|
+
const policyPortSchema = z$1.number().int().min(1).max(65535);
|
|
30
|
+
const agentPolicyRuleSchema = z$1.object({
|
|
31
31
|
host: policyHostSchema,
|
|
32
|
-
methods: z.array(policyMethodSchema).max(16).default([]),
|
|
33
|
-
paths: z.array(policyPathSchema).max(64).default([]),
|
|
34
|
-
allow: z.array(policySecretNameSchema).max(64).default([]),
|
|
35
|
-
https_ports: z.array(policyPortSchema).max(32).optional(),
|
|
36
|
-
http_ports: z.array(policyPortSchema).max(32).optional(),
|
|
37
|
-
label: z.string().trim().max(120).optional()
|
|
32
|
+
methods: z$1.array(policyMethodSchema).max(16).default([]),
|
|
33
|
+
paths: z$1.array(policyPathSchema).max(64).default([]),
|
|
34
|
+
allow: z$1.array(policySecretNameSchema).max(64).default([]),
|
|
35
|
+
https_ports: z$1.array(policyPortSchema).max(32).optional(),
|
|
36
|
+
http_ports: z$1.array(policyPortSchema).max(32).optional(),
|
|
37
|
+
label: z$1.string().trim().max(120).optional()
|
|
38
38
|
});
|
|
39
|
-
z.object({
|
|
39
|
+
z$1.object({
|
|
40
40
|
/** `ap1.<body>.<signature>` — opaque to the server. */
|
|
41
|
-
bundle: z.string().min(16).max(262144) });
|
|
42
|
-
z.object({
|
|
43
|
-
name: z.string().trim().min(1).max(120),
|
|
44
|
-
slug: z.string().trim().min(1).max(64).regex(/^[a-z0-9][a-z0-9-]*$/, "slug is lowercase letters, digits, and dashes"),
|
|
41
|
+
bundle: z$1.string().min(16).max(262144) });
|
|
42
|
+
z$1.object({
|
|
43
|
+
name: z$1.string().trim().min(1).max(120),
|
|
44
|
+
slug: z$1.string().trim().min(1).max(64).regex(/^[a-z0-9][a-z0-9-]*$/, "slug is lowercase letters, digits, and dashes"),
|
|
45
45
|
/** The environment whose secrets this agent's policy may name. Optional. */
|
|
46
|
-
environmentId: z.string().trim().min(1).max(64).optional()
|
|
46
|
+
environmentId: z$1.string().trim().min(1).max(64).optional()
|
|
47
47
|
});
|
|
48
|
-
z.object({
|
|
49
|
-
name: z.string().trim().min(1).max(120).optional(),
|
|
50
|
-
enabled: z.boolean().optional(),
|
|
51
|
-
environmentId: z.string().trim().min(1).max(64).nullish()
|
|
48
|
+
z$1.object({
|
|
49
|
+
name: z$1.string().trim().min(1).max(120).optional(),
|
|
50
|
+
enabled: z$1.boolean().optional(),
|
|
51
|
+
environmentId: z$1.string().trim().min(1).max(64).nullish()
|
|
52
52
|
});
|
|
53
|
-
z.object({
|
|
53
|
+
z$1.object({
|
|
54
54
|
host: policyHostSchema,
|
|
55
55
|
/** How the agent reaches the host. Omit for a destination the operator fixed. */
|
|
56
|
-
transport: z.object({
|
|
57
|
-
scheme: z.enum(["https", "http"]),
|
|
56
|
+
transport: z$1.object({
|
|
57
|
+
scheme: z$1.enum(["https", "http"]),
|
|
58
58
|
port: policyPortSchema
|
|
59
59
|
}).optional(),
|
|
60
60
|
method: policyMethodSchema,
|
|
61
|
-
path: z.string().trim().min(1).max(2048),
|
|
61
|
+
path: z$1.string().trim().min(1).max(2048),
|
|
62
62
|
secret: policySecretNameSchema.optional()
|
|
63
63
|
});
|
|
64
64
|
/**
|
|
@@ -170,7 +170,7 @@ async function policySignerThumbprint(jwk) {
|
|
|
170
170
|
y: jwk.y
|
|
171
171
|
});
|
|
172
172
|
const digest = await crypto.subtle.digest("SHA-256", utf8(canonical));
|
|
173
|
-
return base64url(new Uint8Array(digest));
|
|
173
|
+
return base64url$1(new Uint8Array(digest));
|
|
174
174
|
}
|
|
175
175
|
/**
|
|
176
176
|
* Sign a draft bundle with the publishing admin's key, returning the `ap1.`
|
|
@@ -197,8 +197,8 @@ async function signAgentPolicy(privateKey, signerJwk, draft) {
|
|
|
197
197
|
}, privateKey, bytes));
|
|
198
198
|
return [
|
|
199
199
|
"ap1",
|
|
200
|
-
base64url(bytes),
|
|
201
|
-
base64url(sig)
|
|
200
|
+
base64url$1(bytes),
|
|
201
|
+
base64url$1(sig)
|
|
202
202
|
].join(".");
|
|
203
203
|
}
|
|
204
204
|
/** A structurally invalid, unverifiable, or expired bundle. */
|
|
@@ -386,7 +386,7 @@ function utf8(text) {
|
|
|
386
386
|
function utf8Decode$1(bytes) {
|
|
387
387
|
return new TextDecoder().decode(bytes);
|
|
388
388
|
}
|
|
389
|
-
function base64url(bytes) {
|
|
389
|
+
function base64url$1(bytes) {
|
|
390
390
|
let binary = "";
|
|
391
391
|
for (const b of bytes) binary += String.fromCharCode(b);
|
|
392
392
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -399,10 +399,10 @@ function fromBase64url(text) {
|
|
|
399
399
|
return out;
|
|
400
400
|
}
|
|
401
401
|
/** One aggregated cell: a dimension tuple and how many times it happened. */
|
|
402
|
-
const activityEntrySchema = z.object({
|
|
402
|
+
const activityEntrySchema = z$1.object({
|
|
403
403
|
host: policyHostSchema,
|
|
404
404
|
method: policyMethodSchema,
|
|
405
|
-
decision: z.enum([
|
|
405
|
+
decision: z$1.enum([
|
|
406
406
|
"allow",
|
|
407
407
|
"no_rule",
|
|
408
408
|
"port_not_allowed",
|
|
@@ -419,27 +419,27 @@ const activityEntrySchema = z.object({
|
|
|
419
419
|
* a review, because "rule 3 refused this" and "nothing covered this" call for
|
|
420
420
|
* opposite changes.
|
|
421
421
|
*/
|
|
422
|
-
ruleIndex: z.number().int().min(0).max(255).nullable(),
|
|
423
|
-
count: z.number().int().min(1).max(1e6),
|
|
422
|
+
ruleIndex: z$1.number().int().min(0).max(255).nullable(),
|
|
423
|
+
count: z$1.number().int().min(1).max(1e6),
|
|
424
424
|
/**
|
|
425
425
|
* Secret names actually injected, name → count. Only meaningful on `allow`.
|
|
426
426
|
* This is what lets a review say "rule 2 permits three secrets and the agent
|
|
427
427
|
* has only ever used one" — the most useful narrowing there is, and impossible
|
|
428
428
|
* to see from policy alone.
|
|
429
429
|
*/
|
|
430
|
-
secrets: z.record(policySecretNameSchema, z.number().int().min(1)).optional()
|
|
430
|
+
secrets: z$1.record(policySecretNameSchema, z$1.number().int().min(1)).optional()
|
|
431
431
|
});
|
|
432
|
-
z.object({
|
|
432
|
+
z$1.object({
|
|
433
433
|
/** Start of the window these counts cover (ISO 8601). */
|
|
434
|
-
windowStart: z.string().trim().min(20).max(40),
|
|
434
|
+
windowStart: z$1.string().trim().min(20).max(40),
|
|
435
435
|
/** Policy version in force while they were collected, for the ledger. */
|
|
436
|
-
policyVersion: z.number().int().min(0).optional(),
|
|
436
|
+
policyVersion: z$1.number().int().min(0).optional(),
|
|
437
437
|
/**
|
|
438
438
|
* Capped so one report cannot be unbounded work. A proxy with more distinct
|
|
439
439
|
* cells than this in a window has a policy far broader than a review can help
|
|
440
440
|
* with, and truncating loudly beats accepting anything.
|
|
441
441
|
*/
|
|
442
|
-
entries: z.array(activityEntrySchema).min(1).max(500)
|
|
442
|
+
entries: z$1.array(activityEntrySchema).min(1).max(500)
|
|
443
443
|
});
|
|
444
444
|
// ../../packages/core/src/agent-login.ts
|
|
445
445
|
/** Where each kind of login is stored unless told otherwise. */
|
|
@@ -703,38 +703,38 @@ function countApplicable(proposals) {
|
|
|
703
703
|
*/
|
|
704
704
|
const TASK_MAX_TTL_SECONDS = 43200;
|
|
705
705
|
/** An EC P-256 public JWK, for a sender-constraint proof key. */
|
|
706
|
-
const taskProofJwkSchema = z.object({
|
|
707
|
-
kty: z.literal("EC"),
|
|
708
|
-
crv: z.literal("P-256"),
|
|
709
|
-
x: z.string().min(1).max(128),
|
|
710
|
-
y: z.string().min(1).max(128)
|
|
706
|
+
const taskProofJwkSchema = z$1.object({
|
|
707
|
+
kty: z$1.literal("EC"),
|
|
708
|
+
crv: z$1.literal("P-256"),
|
|
709
|
+
x: z$1.string().min(1).max(128),
|
|
710
|
+
y: z$1.string().min(1).max(128)
|
|
711
711
|
});
|
|
712
|
-
z.object({
|
|
712
|
+
z$1.object({
|
|
713
713
|
/**
|
|
714
714
|
* The public `skd_…` segment of the minted token. Sent because the API never
|
|
715
715
|
* sees the token at dispatch and still needs a readable handle for the audit
|
|
716
716
|
* row and for a revoke to name — the id half of a credential, without the
|
|
717
717
|
* secret half.
|
|
718
718
|
*/
|
|
719
|
-
taskRef: z.string().trim().regex(/^skd_[0-9A-Za-z]+$/, "taskRef must be the skd_… segment of the minted token"),
|
|
719
|
+
taskRef: z$1.string().trim().regex(/^skd_[0-9A-Za-z]+$/, "taskRef must be the skd_… segment of the minted token"),
|
|
720
720
|
/**
|
|
721
721
|
* SHA-256 (base64url) of the token the dispatcher minted. The token itself
|
|
722
722
|
* never reaches this API on the dispatch path — only on introspection, where
|
|
723
723
|
* it is hashed and discarded.
|
|
724
724
|
*/
|
|
725
|
-
tokenHash: z.string().trim().min(16).max(128),
|
|
725
|
+
tokenHash: z$1.string().trim().min(16).max(128),
|
|
726
726
|
/**
|
|
727
727
|
* Secret names this run may use. Omit for "whatever the agent's policy
|
|
728
728
|
* allows" — mirroring `Session.scopes: Option<BTreeSet<String>>` in the proxy,
|
|
729
729
|
* so absent means unnarrowed in both places.
|
|
730
730
|
*/
|
|
731
|
-
scopes: z.array(policySecretNameSchema).max(64).optional(),
|
|
732
|
-
ttlSeconds: z.number().int().min(60).max(TASK_MAX_TTL_SECONDS).optional(),
|
|
731
|
+
scopes: z$1.array(policySecretNameSchema).max(64).optional(),
|
|
732
|
+
ttlSeconds: z$1.number().int().min(60).max(TASK_MAX_TTL_SECONDS).optional(),
|
|
733
733
|
/**
|
|
734
734
|
* What this run is for, for the audit row and the operator's task list. Free
|
|
735
735
|
* text, and **not** a security input: never put a secret value in it.
|
|
736
736
|
*/
|
|
737
|
-
label: z.string().trim().max(200).optional(),
|
|
737
|
+
label: z$1.string().trim().max(200).optional(),
|
|
738
738
|
/**
|
|
739
739
|
* Public half of a proof key the presenter holds, recorded as an RFC 7638
|
|
740
740
|
* thumbprint. See `AgentTaskSession.proofThumbprint` for what this does and —
|
|
@@ -742,9 +742,9 @@ z.object({
|
|
|
742
742
|
*/
|
|
743
743
|
proofJwk: taskProofJwkSchema.optional()
|
|
744
744
|
});
|
|
745
|
-
z.object({
|
|
745
|
+
z$1.object({
|
|
746
746
|
/** The presented token. In the body, never a URL — it is a credential. */
|
|
747
|
-
token: z.string().trim().min(8).max(512) });
|
|
747
|
+
token: z$1.string().trim().min(8).max(512) });
|
|
748
748
|
// ../../packages/core/src/archive.ts
|
|
749
749
|
/**
|
|
750
750
|
* The **break-glass archive** format: one signed JSON file holding everything
|
|
@@ -920,43 +920,43 @@ async function verifySignature(manifest, signature) {
|
|
|
920
920
|
const publicKey = await crypto.subtle.importKey("raw", hexToBytes(signature.publicKey), { name: "Ed25519" }, false, ["verify"]);
|
|
921
921
|
return crypto.subtle.verify({ name: "Ed25519" }, publicKey, hexToBytes(signature.value), signingPayload(manifest));
|
|
922
922
|
}
|
|
923
|
-
const sectionHeaderSchema = z.object({
|
|
924
|
-
name: z.enum(ARCHIVE_SECTIONS),
|
|
925
|
-
count: z.number().int().min(0),
|
|
926
|
-
truncated: z.boolean(),
|
|
927
|
-
digest: z.string().regex(/^sha256:[0-9a-f]{64}$/)
|
|
923
|
+
const sectionHeaderSchema = z$1.object({
|
|
924
|
+
name: z$1.enum(ARCHIVE_SECTIONS),
|
|
925
|
+
count: z$1.number().int().min(0),
|
|
926
|
+
truncated: z$1.boolean(),
|
|
927
|
+
digest: z$1.string().regex(/^sha256:[0-9a-f]{64}$/)
|
|
928
928
|
});
|
|
929
|
-
const manifestSchema = z.object({
|
|
930
|
-
archiveId: z.string().min(1),
|
|
931
|
-
createdAt: z.string().min(1),
|
|
932
|
-
org: z.object({
|
|
933
|
-
id: z.string(),
|
|
934
|
-
slug: z.string(),
|
|
935
|
-
name: z.string()
|
|
929
|
+
const manifestSchema = z$1.object({
|
|
930
|
+
archiveId: z$1.string().min(1),
|
|
931
|
+
createdAt: z$1.string().min(1),
|
|
932
|
+
org: z$1.object({
|
|
933
|
+
id: z$1.string(),
|
|
934
|
+
slug: z$1.string(),
|
|
935
|
+
name: z$1.string()
|
|
936
936
|
}),
|
|
937
|
-
producer: z.object({
|
|
938
|
-
service: z.string(),
|
|
939
|
-
environment: z.string(),
|
|
940
|
-
formatVersion: z.string()
|
|
937
|
+
producer: z$1.object({
|
|
938
|
+
service: z$1.string(),
|
|
939
|
+
environment: z$1.string(),
|
|
940
|
+
formatVersion: z$1.string()
|
|
941
941
|
}),
|
|
942
|
-
requestedBy: z.object({
|
|
943
|
-
actorType: z.string(),
|
|
944
|
-
actorId: z.string(),
|
|
945
|
-
label: z.string().nullable()
|
|
942
|
+
requestedBy: z$1.object({
|
|
943
|
+
actorType: z$1.string(),
|
|
944
|
+
actorId: z$1.string(),
|
|
945
|
+
label: z$1.string().nullable()
|
|
946
946
|
}),
|
|
947
|
-
options: z.object({
|
|
948
|
-
includeVersions: z.boolean(),
|
|
949
|
-
includeAudit: z.boolean(),
|
|
950
|
-
auditLimit: z.number().int().min(0)
|
|
947
|
+
options: z$1.object({
|
|
948
|
+
includeVersions: z$1.boolean(),
|
|
949
|
+
includeAudit: z$1.boolean(),
|
|
950
|
+
auditLimit: z$1.number().int().min(0)
|
|
951
951
|
}),
|
|
952
|
-
sections: z.array(sectionHeaderSchema),
|
|
953
|
-
digest: z.string().regex(/^sha256:[0-9a-f]{64}$/)
|
|
952
|
+
sections: z$1.array(sectionHeaderSchema),
|
|
953
|
+
digest: z$1.string().regex(/^sha256:[0-9a-f]{64}$/)
|
|
954
954
|
});
|
|
955
|
-
const signatureSchema = z.object({
|
|
956
|
-
algorithm: z.literal("ed25519"),
|
|
957
|
-
publicKey: z.string().regex(/^[0-9a-f]{64}$/),
|
|
958
|
-
keyId: z.string().regex(/^[0-9a-f]{16}$/),
|
|
959
|
-
value: z.string().regex(/^[0-9a-f]{128}$/)
|
|
955
|
+
const signatureSchema = z$1.object({
|
|
956
|
+
algorithm: z$1.literal("ed25519"),
|
|
957
|
+
publicKey: z$1.string().regex(/^[0-9a-f]{64}$/),
|
|
958
|
+
keyId: z$1.string().regex(/^[0-9a-f]{16}$/),
|
|
959
|
+
value: z$1.string().regex(/^[0-9a-f]{128}$/)
|
|
960
960
|
});
|
|
961
961
|
/**
|
|
962
962
|
* Envelope schema. `data` is validated only as an object: a v1 verifier must
|
|
@@ -965,11 +965,11 @@ const signatureSchema = z.object({
|
|
|
965
965
|
* `verifyArchive`'s undeclared/declared cross-check rather than by rejecting
|
|
966
966
|
* the file outright.
|
|
967
967
|
*/
|
|
968
|
-
const archiveSchema = z.object({
|
|
969
|
-
format: z.literal(ARCHIVE_FORMAT),
|
|
968
|
+
const archiveSchema = z$1.object({
|
|
969
|
+
format: z$1.literal(ARCHIVE_FORMAT),
|
|
970
970
|
manifest: manifestSchema,
|
|
971
971
|
signature: signatureSchema.nullable(),
|
|
972
|
-
data: z.record(z.string(), z.unknown())
|
|
972
|
+
data: z$1.record(z$1.string(), z$1.unknown())
|
|
973
973
|
});
|
|
974
974
|
/** Parse untrusted JSON text into an archive. Throws with a readable message. */
|
|
975
975
|
function parseArchive(text) {
|
|
@@ -1238,15 +1238,15 @@ function describeChange(kind, name, restoreFrom) {
|
|
|
1238
1238
|
}
|
|
1239
1239
|
// ../../packages/core/src/connect.ts
|
|
1240
1240
|
/** Workload authentication is separate from secret-decryption principals. */
|
|
1241
|
-
const workloadPublicKeySchema = z.object({
|
|
1242
|
-
kty: z.literal("EC"),
|
|
1243
|
-
crv: z.literal("P-256"),
|
|
1244
|
-
x: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
|
|
1245
|
-
y: z.string().regex(/^[A-Za-z0-9_-]{43}$/)
|
|
1241
|
+
const workloadPublicKeySchema = z$1.object({
|
|
1242
|
+
kty: z$1.literal("EC"),
|
|
1243
|
+
crv: z$1.literal("P-256"),
|
|
1244
|
+
x: z$1.string().regex(/^[A-Za-z0-9_-]{43}$/),
|
|
1245
|
+
y: z$1.string().regex(/^[A-Za-z0-9_-]{43}$/)
|
|
1246
1246
|
}).strict();
|
|
1247
|
-
const audienceSchema = z.string().min(1).max(256).regex(/^[^\s]+$/);
|
|
1247
|
+
const audienceSchema = z$1.string().min(1).max(256).regex(/^[^\s]+$/);
|
|
1248
1248
|
/** HTTPS endpoint syntax. Resolved-address safety is enforced by the server runtime. */
|
|
1249
|
-
const connectUrlSchema = z.string().max(2048).url().refine((value) => {
|
|
1249
|
+
const connectUrlSchema = z$1.string().max(2048).url().refine((value) => {
|
|
1250
1250
|
try {
|
|
1251
1251
|
const u = new URL(value);
|
|
1252
1252
|
return u.protocol === "https:" && !u.username && !u.password && !u.hash && !u.search && !u.port && u.hostname.length <= 253 && u.hostname.split(".").every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)) && u.hostname.includes(".") && !/^[0-9.]+$/.test(u.hostname) && !u.hostname.endsWith(".") && !/(^|\.)(localhost|local|localdomain|internal|invalid|home|home\.arpa|onion)$/.test(u.hostname);
|
|
@@ -1254,12 +1254,12 @@ const connectUrlSchema = z.string().max(2048).url().refine((value) => {
|
|
|
1254
1254
|
return false;
|
|
1255
1255
|
}
|
|
1256
1256
|
}, "use a public HTTPS hostname without credentials, query, fragment, or port");
|
|
1257
|
-
const federationPolicySchema = z.object({
|
|
1257
|
+
const federationPolicySchema = z$1.object({
|
|
1258
1258
|
issuer: connectUrlSchema,
|
|
1259
1259
|
jwksUri: connectUrlSchema,
|
|
1260
1260
|
audience: audienceSchema,
|
|
1261
|
-
subject: z.string().min(1).max(512),
|
|
1262
|
-
claims: z.record(z.string().regex(/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/), z.string().min(1).max(512)).refine((claims) => Object.keys(claims).length <= 20 && Object.keys(claims).every((key) => ![
|
|
1261
|
+
subject: z$1.string().min(1).max(512),
|
|
1262
|
+
claims: z$1.record(z$1.string().regex(/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/), z$1.string().min(1).max(512)).refine((claims) => Object.keys(claims).length <= 20 && Object.keys(claims).every((key) => ![
|
|
1263
1263
|
"iss",
|
|
1264
1264
|
"aud",
|
|
1265
1265
|
"sub",
|
|
@@ -1268,29 +1268,29 @@ const federationPolicySchema = z.object({
|
|
|
1268
1268
|
"nbf"
|
|
1269
1269
|
].includes(key))).default({})
|
|
1270
1270
|
}).strict();
|
|
1271
|
-
z.object({
|
|
1272
|
-
name: z.string().trim().min(1).max(100),
|
|
1273
|
-
environmentId: z.string().min(1).max(128),
|
|
1271
|
+
z$1.object({
|
|
1272
|
+
name: z$1.string().trim().min(1).max(100),
|
|
1273
|
+
environmentId: z$1.string().min(1).max(128),
|
|
1274
1274
|
publicKey: workloadPublicKeySchema.optional(),
|
|
1275
1275
|
federation: federationPolicySchema.optional(),
|
|
1276
|
-
audiences: z.array(audienceSchema).max(20).default([]),
|
|
1277
|
-
maxTtlSeconds: z.number().int().min(60).max(900).default(300)
|
|
1276
|
+
audiences: z$1.array(audienceSchema).max(20).default([]),
|
|
1277
|
+
maxTtlSeconds: z$1.number().int().min(60).max(900).default(300)
|
|
1278
1278
|
}).strict().refine((v) => !!v.publicKey !== !!v.federation, "choose a public key or a federation policy");
|
|
1279
|
-
z.object({
|
|
1280
|
-
enabled: z.boolean().optional(),
|
|
1279
|
+
z$1.object({
|
|
1280
|
+
enabled: z$1.boolean().optional(),
|
|
1281
1281
|
publicKey: workloadPublicKeySchema.optional()
|
|
1282
1282
|
}).strict().refine((v) => v.enabled !== void 0 || v.publicKey !== void 0);
|
|
1283
|
-
const githubPermissionsSchema = z.record(z.string().regex(/^[a-z_]{1,64}$/), z.enum(["read", "write"])).refine((p) => Object.keys(p).length > 0 && Object.keys(p).length <= 40, "specify between 1 and 40 permissions");
|
|
1284
|
-
const githubScopeSchema = z.object({
|
|
1285
|
-
repositoryIds: z.array(z.number().int().positive().max(Number.MAX_SAFE_INTEGER)).min(1).max(100),
|
|
1283
|
+
const githubPermissionsSchema = z$1.record(z$1.string().regex(/^[a-z_]{1,64}$/), z$1.enum(["read", "write"])).refine((p) => Object.keys(p).length > 0 && Object.keys(p).length <= 40, "specify between 1 and 40 permissions");
|
|
1284
|
+
const githubScopeSchema = z$1.object({
|
|
1285
|
+
repositoryIds: z$1.array(z$1.number().int().positive().max(Number.MAX_SAFE_INTEGER)).min(1).max(100),
|
|
1286
1286
|
permissions: githubPermissionsSchema
|
|
1287
1287
|
}).strict();
|
|
1288
1288
|
/** Exact paths or explicitly allowed descendants, without URL decoding ambiguity. */
|
|
1289
|
-
const connectPathSchema = z.string().min(1).max(1024).refine((path) => /^\/[A-Za-z0-9_./~-]*$/.test(path) && !path.includes("//") && !path.split("/").some((part) => part === "." || part === ".."), "use an absolute API path without encoded characters or dot segments");
|
|
1290
|
-
const proxyRuleSchema = z.object({
|
|
1289
|
+
const connectPathSchema = z$1.string().min(1).max(1024).refine((path) => /^\/[A-Za-z0-9_./~-]*$/.test(path) && !path.includes("//") && !path.split("/").some((part) => part === "." || part === ".."), "use an absolute API path without encoded characters or dot segments");
|
|
1290
|
+
const proxyRuleSchema = z$1.object({
|
|
1291
1291
|
path: connectPathSchema,
|
|
1292
|
-
descendants: z.boolean().default(false),
|
|
1293
|
-
methods: z.array(z.enum([
|
|
1292
|
+
descendants: z$1.boolean().default(false),
|
|
1293
|
+
methods: z$1.array(z$1.enum([
|
|
1294
1294
|
"GET",
|
|
1295
1295
|
"POST",
|
|
1296
1296
|
"PUT",
|
|
@@ -1299,18 +1299,18 @@ const proxyRuleSchema = z.object({
|
|
|
1299
1299
|
"HEAD"
|
|
1300
1300
|
])).min(1).max(6)
|
|
1301
1301
|
}).strict();
|
|
1302
|
-
const staticScopeSchema = z.object({ rules: z.array(proxyRuleSchema).min(1).max(30) }).strict();
|
|
1303
|
-
const oauthScopeSchema = z.object({ scopes: z.array(z.string().regex(/^[\x21\x23-\x5b\x5d-\x7e]{1,200}$/)).min(1).max(50).refine((scopes) => new Set(scopes).size === scopes.length, "scopes must be unique") }).strict();
|
|
1302
|
+
const staticScopeSchema = z$1.object({ rules: z$1.array(proxyRuleSchema).min(1).max(30) }).strict();
|
|
1303
|
+
const oauthScopeSchema = z$1.object({ scopes: z$1.array(z$1.string().regex(/^[\x21\x23-\x5b\x5d-\x7e]{1,200}$/)).min(1).max(50).refine((scopes) => new Set(scopes).size === scopes.length, "scopes must be unique") }).strict();
|
|
1304
1304
|
const oauthConfigSchema = oauthScopeSchema.extend({
|
|
1305
|
-
provider: z.literal("oauth"),
|
|
1306
|
-
grantType: z.enum(["client_credentials", "authorization_code"]),
|
|
1307
|
-
clientId: z.string().min(1).max(512),
|
|
1305
|
+
provider: z$1.literal("oauth"),
|
|
1306
|
+
grantType: z$1.enum(["client_credentials", "authorization_code"]),
|
|
1307
|
+
clientId: z$1.string().min(1).max(512),
|
|
1308
1308
|
tokenEndpoint: connectUrlSchema,
|
|
1309
1309
|
authorizationEndpoint: connectUrlSchema.optional(),
|
|
1310
|
-
tokenAuthMethod: z.enum(["client_secret_basic", "client_secret_post"]).default("client_secret_basic")
|
|
1310
|
+
tokenAuthMethod: z$1.enum(["client_secret_basic", "client_secret_post"]).default("client_secret_basic")
|
|
1311
1311
|
});
|
|
1312
1312
|
const customApiConfigSchema = staticScopeSchema.extend({
|
|
1313
|
-
provider: z.literal("custom_api"),
|
|
1313
|
+
provider: z$1.literal("custom_api"),
|
|
1314
1314
|
origin: connectUrlSchema.refine((value) => {
|
|
1315
1315
|
try {
|
|
1316
1316
|
return new URL(value).pathname === "/";
|
|
@@ -1318,46 +1318,46 @@ const customApiConfigSchema = staticScopeSchema.extend({
|
|
|
1318
1318
|
return false;
|
|
1319
1319
|
}
|
|
1320
1320
|
}, "use an origin without a path"),
|
|
1321
|
-
headerName: z.string().regex(/^[A-Za-z][A-Za-z0-9-]{0,63}$/).refine((v) => !/^(accept$|user-agent$|host|cookie|content-|proxy-|forwarded|x-forwarded-|cf-|connection|transfer-encoding|upgrade|trailer|te$)/i.test(v)),
|
|
1322
|
-
headerPrefix: z.string().max(64).regex(/^[\x20-\x7e]*$/).default("Bearer ")
|
|
1321
|
+
headerName: z$1.string().regex(/^[A-Za-z][A-Za-z0-9-]{0,63}$/).refine((v) => !/^(accept$|user-agent$|host|cookie|content-|proxy-|forwarded|x-forwarded-|cf-|connection|transfer-encoding|upgrade|trailer|te$)/i.test(v)),
|
|
1322
|
+
headerPrefix: z$1.string().max(64).regex(/^[\x20-\x7e]*$/).default("Bearer ")
|
|
1323
1323
|
});
|
|
1324
|
-
const connectConfigSchema = z.discriminatedUnion("provider", [
|
|
1324
|
+
const connectConfigSchema = z$1.discriminatedUnion("provider", [
|
|
1325
1325
|
customApiConfigSchema,
|
|
1326
1326
|
oauthConfigSchema,
|
|
1327
1327
|
githubScopeSchema.extend({
|
|
1328
|
-
provider: z.literal("github_app"),
|
|
1329
|
-
appId: z.string().regex(/^[A-Za-z0-9_.-]{1,128}$/),
|
|
1330
|
-
installationId: z.string().regex(/^[1-9][0-9]{0,19}$/)
|
|
1328
|
+
provider: z$1.literal("github_app"),
|
|
1329
|
+
appId: z$1.string().regex(/^[A-Za-z0-9_.-]{1,128}$/),
|
|
1330
|
+
installationId: z$1.string().regex(/^[1-9][0-9]{0,19}$/)
|
|
1331
1331
|
}),
|
|
1332
1332
|
staticScopeSchema.extend({
|
|
1333
|
-
provider: z.literal("static_key"),
|
|
1334
|
-
service: z.literal("resend"),
|
|
1335
|
-
rules: z.array(proxyRuleSchema.refine((rule) => rule.path === "/emails" || rule.path.startsWith("/emails/"), "the Resend connector supports only /emails and its descendants")).min(1).max(30)
|
|
1333
|
+
provider: z$1.literal("static_key"),
|
|
1334
|
+
service: z$1.literal("resend"),
|
|
1335
|
+
rules: z$1.array(proxyRuleSchema.refine((rule) => rule.path === "/emails" || rule.path.startsWith("/emails/"), "the Resend connector supports only /emails and its descendants")).min(1).max(30)
|
|
1336
1336
|
})
|
|
1337
1337
|
]).refine((v) => v.provider !== "oauth" || v.grantType !== "authorization_code" || !!v.authorizationEndpoint, "authorization-code connections require an authorization endpoint");
|
|
1338
|
-
const connectGrantPolicySchema = z.discriminatedUnion("provider", [
|
|
1339
|
-
staticScopeSchema.extend({ provider: z.literal("custom_api") }),
|
|
1340
|
-
oauthScopeSchema.extend({ provider: z.literal("oauth") }),
|
|
1341
|
-
githubScopeSchema.extend({ provider: z.literal("github_app") }),
|
|
1342
|
-
staticScopeSchema.extend({ provider: z.literal("static_key") })
|
|
1338
|
+
const connectGrantPolicySchema = z$1.discriminatedUnion("provider", [
|
|
1339
|
+
staticScopeSchema.extend({ provider: z$1.literal("custom_api") }),
|
|
1340
|
+
oauthScopeSchema.extend({ provider: z$1.literal("oauth") }),
|
|
1341
|
+
githubScopeSchema.extend({ provider: z$1.literal("github_app") }),
|
|
1342
|
+
staticScopeSchema.extend({ provider: z$1.literal("static_key") })
|
|
1343
1343
|
]);
|
|
1344
|
-
z.object({
|
|
1345
|
-
name: z.string().trim().min(1).max(100),
|
|
1344
|
+
z$1.object({
|
|
1345
|
+
name: z$1.string().trim().min(1).max(100),
|
|
1346
1346
|
config: connectConfigSchema,
|
|
1347
|
-
credentialWrapped: z.string().startsWith("wd1.").max(32e3)
|
|
1347
|
+
credentialWrapped: z$1.string().startsWith("wd1.").max(32e3)
|
|
1348
1348
|
}).strict();
|
|
1349
|
-
z.object({
|
|
1350
|
-
enabled: z.boolean().optional(),
|
|
1351
|
-
credentialWrapped: z.string().startsWith("wd1.").max(32e3).optional()
|
|
1349
|
+
z$1.object({
|
|
1350
|
+
enabled: z$1.boolean().optional(),
|
|
1351
|
+
credentialWrapped: z$1.string().startsWith("wd1.").max(32e3).optional()
|
|
1352
1352
|
}).strict().refine((v) => v.enabled !== void 0 || v.credentialWrapped !== void 0);
|
|
1353
|
-
z.object({
|
|
1354
|
-
workloadId: z.string().min(1).max(128),
|
|
1355
|
-
connectionId: z.string().min(1).max(128),
|
|
1353
|
+
z$1.object({
|
|
1354
|
+
workloadId: z$1.string().min(1).max(128),
|
|
1355
|
+
connectionId: z$1.string().min(1).max(128),
|
|
1356
1356
|
policy: connectGrantPolicySchema
|
|
1357
1357
|
}).strict();
|
|
1358
|
-
z.object({
|
|
1358
|
+
z$1.object({
|
|
1359
1359
|
path: connectPathSchema,
|
|
1360
|
-
method: z.enum([
|
|
1360
|
+
method: z$1.enum([
|
|
1361
1361
|
"GET",
|
|
1362
1362
|
"POST",
|
|
1363
1363
|
"PUT",
|
|
@@ -1365,11 +1365,11 @@ z.object({
|
|
|
1365
1365
|
"DELETE",
|
|
1366
1366
|
"HEAD"
|
|
1367
1367
|
]).default("GET"),
|
|
1368
|
-
query: z.record(z.string().min(1).max(100), z.string().max(2048)).default({}),
|
|
1368
|
+
query: z$1.record(z$1.string().min(1).max(100), z$1.string().max(2048)).default({}),
|
|
1369
1369
|
/** JSON only; callers cannot inject authorization or forwarding headers. */
|
|
1370
|
-
body: z.string().max(65536).optional()
|
|
1370
|
+
body: z$1.string().max(65536).optional()
|
|
1371
1371
|
}).strict().refine((v) => !["GET", "HEAD"].includes(v.method) || v.body === void 0, "GET and HEAD cannot carry a body");
|
|
1372
|
-
z.object({ audience: audienceSchema }).strict();
|
|
1372
|
+
z$1.object({ audience: audienceSchema }).strict();
|
|
1373
1373
|
// ../../packages/core/src/dotenv.ts
|
|
1374
1374
|
/**
|
|
1375
1375
|
* `.env` parsing and serialization — the canonical definition of what a `.env`
|
|
@@ -1752,44 +1752,268 @@ function prettyJsonOrRaw(value) {
|
|
|
1752
1752
|
const inspection = inspectJsonValue(value);
|
|
1753
1753
|
return inspection.kind === "json" ? inspection.pretty : value;
|
|
1754
1754
|
}
|
|
1755
|
-
|
|
1755
|
+
// ../../packages/core/src/managed-providers/aws-cache.ts
|
|
1756
|
+
const cacheRegionSchema = z$1.string().regex(/^[a-z]{2}(?:-[a-z]+)+-\d$/);
|
|
1757
|
+
const cacheGroupSchema = z$1.string().min(1).max(128).regex(/^[a-zA-Z][a-zA-Z0-9-]*$/);
|
|
1758
|
+
const cacheAccessSchema = z$1.string().min(1).max(4096).refine((value) => /^on(?:\s|$)/.test(value) && !/(?:^|\s)(?:nopass|resetpass|[>#<])/.test(value), "ACL must enable the user and must not contain password directives");
|
|
1759
|
+
// ../../packages/core/src/managed-providers/common.ts
|
|
1760
|
+
const managedEndpointSchema = z$1.string().url().max(2048).refine((value) => {
|
|
1761
|
+
try {
|
|
1762
|
+
const url = new URL(value);
|
|
1763
|
+
url.port = "";
|
|
1764
|
+
return connectUrlSchema.safeParse(url.toString()).success;
|
|
1765
|
+
} catch {
|
|
1766
|
+
return false;
|
|
1767
|
+
}
|
|
1768
|
+
}, "use a public HTTPS endpoint without embedded credentials, query, or fragment");
|
|
1769
|
+
const managedTtlSchema = z$1.number().int().min(60).max(604800).optional();
|
|
1770
|
+
const nameSchema$1 = z$1.string().min(1).max(128).regex(/^[a-zA-Z_][a-zA-Z0-9_-]*$/);
|
|
1771
|
+
// ../../packages/core/src/managed-providers/memorydb.ts
|
|
1772
|
+
const memorydbTargetConfigSchema = z$1.object({
|
|
1773
|
+
provider: z$1.literal("memorydb"),
|
|
1774
|
+
executor: z$1.literal("in_do"),
|
|
1775
|
+
region: cacheRegionSchema,
|
|
1776
|
+
aclName: cacheGroupSchema,
|
|
1777
|
+
accessString: cacheAccessSchema,
|
|
1778
|
+
maxTtlSeconds: managedTtlSchema
|
|
1779
|
+
}).strict();
|
|
1780
|
+
// ../../packages/core/src/managed-providers/elasticache.ts
|
|
1781
|
+
const elasticacheTargetConfigSchema = z$1.object({
|
|
1782
|
+
provider: z$1.literal("elasticache"),
|
|
1783
|
+
executor: z$1.literal("in_do"),
|
|
1784
|
+
region: cacheRegionSchema,
|
|
1785
|
+
userGroupId: cacheGroupSchema,
|
|
1786
|
+
engine: z$1.enum(["redis", "valkey"]).default("redis"),
|
|
1787
|
+
accessString: cacheAccessSchema,
|
|
1788
|
+
maxTtlSeconds: managedTtlSchema
|
|
1789
|
+
}).strict();
|
|
1790
|
+
// ../../packages/core/src/managed-providers/snowflake.ts
|
|
1791
|
+
const identifier = z$1.string().regex(/^[a-zA-Z_][a-zA-Z0-9_$]{0,254}$/).transform((value) => value.toUpperCase());
|
|
1792
|
+
const snowflakeTargetConfigSchema = z$1.object({
|
|
1793
|
+
provider: z$1.literal("snowflake"),
|
|
1794
|
+
executor: z$1.literal("in_do"),
|
|
1795
|
+
account: z$1.string().min(1).max(200).regex(/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*$/i),
|
|
1796
|
+
adminRole: identifier.default("USERADMIN"),
|
|
1797
|
+
roles: z$1.array(identifier).min(1).max(50),
|
|
1798
|
+
warehouse: identifier.optional(),
|
|
1799
|
+
networkPolicy: identifier.optional(),
|
|
1800
|
+
maxTtlSeconds: managedTtlSchema
|
|
1801
|
+
}).strict();
|
|
1802
|
+
// ../../packages/core/src/managed-providers/couchbase.ts
|
|
1803
|
+
const resourceName = z$1.string().min(1).max(255);
|
|
1804
|
+
const access = z$1.object({
|
|
1805
|
+
privileges: z$1.array(z$1.enum(["data_reader", "data_writer"])).min(1).max(2),
|
|
1806
|
+
resources: z$1.object({ buckets: z$1.array(z$1.object({
|
|
1807
|
+
name: resourceName,
|
|
1808
|
+
scopes: z$1.array(z$1.object({
|
|
1809
|
+
name: resourceName,
|
|
1810
|
+
collections: z$1.array(resourceName).min(1).max(100).optional()
|
|
1811
|
+
}).strict()).min(1).max(100).optional()
|
|
1812
|
+
}).strict()).min(1).max(100) }).strict()
|
|
1813
|
+
}).strict();
|
|
1814
|
+
const couchbaseTargetConfigSchema = z$1.object({
|
|
1815
|
+
provider: z$1.literal("couchbase"),
|
|
1816
|
+
executor: z$1.literal("in_do"),
|
|
1817
|
+
organizationId: z$1.string().uuid(),
|
|
1818
|
+
projectId: z$1.string().uuid(),
|
|
1819
|
+
clusterId: z$1.string().uuid(),
|
|
1820
|
+
credentialType: z$1.enum(["basic", "advanced"]).default("basic"),
|
|
1821
|
+
access: z$1.array(access).min(1).max(100).optional(),
|
|
1822
|
+
userRoles: z$1.array(resourceName).min(1).max(100).optional(),
|
|
1823
|
+
maxTtlSeconds: managedTtlSchema
|
|
1824
|
+
}).strict().superRefine((value, ctx) => {
|
|
1825
|
+
if (value.credentialType === "basic" ? !value.access || !!value.userRoles : !value.userRoles || !!value.access) ctx.addIssue({
|
|
1826
|
+
code: "custom",
|
|
1827
|
+
message: "basic credentials require access only; advanced credentials require userRoles only"
|
|
1828
|
+
});
|
|
1829
|
+
});
|
|
1830
|
+
// ../../packages/core/src/managed-providers/mongodb-atlas.ts
|
|
1831
|
+
const mongoAtlasTargetConfigSchema = z$1.object({
|
|
1832
|
+
provider: z$1.literal("mongodb-atlas"),
|
|
1833
|
+
executor: z$1.literal("in_do"),
|
|
1834
|
+
projectId: z$1.string().regex(/^[a-f0-9]{24}$/),
|
|
1835
|
+
roles: z$1.array(z$1.object({
|
|
1836
|
+
roleName: z$1.string().min(1).max(128),
|
|
1837
|
+
databaseName: z$1.string().min(1).max(64),
|
|
1838
|
+
collectionName: z$1.string().min(1).max(128).optional()
|
|
1839
|
+
}).strict()).min(1).max(100),
|
|
1840
|
+
clusters: z$1.array(z$1.string().min(1).max(64)).min(1).max(100),
|
|
1841
|
+
maxTtlSeconds: managedTtlSchema
|
|
1842
|
+
}).strict();
|
|
1843
|
+
// ../../packages/core/src/managed-providers/milvus.ts
|
|
1844
|
+
const milvusTargetConfigSchema = z$1.object({
|
|
1845
|
+
provider: z$1.literal("milvus"),
|
|
1846
|
+
executor: z$1.literal("in_do"),
|
|
1847
|
+
endpoint: managedEndpointSchema,
|
|
1848
|
+
roles: z$1.array(z$1.string().regex(/^[a-zA-Z][a-zA-Z0-9_]{0,127}$/)).min(1).max(50),
|
|
1849
|
+
maxTtlSeconds: managedTtlSchema
|
|
1850
|
+
}).strict();
|
|
1851
|
+
// ../../packages/core/src/managed-providers/rabbitmq.ts
|
|
1852
|
+
const rabbitmqTargetConfigSchema = z$1.object({
|
|
1853
|
+
provider: z$1.literal("rabbitmq"),
|
|
1854
|
+
executor: z$1.literal("in_do"),
|
|
1855
|
+
endpoint: managedEndpointSchema,
|
|
1856
|
+
vhost: z$1.string().min(1).max(255),
|
|
1857
|
+
configure: z$1.string().max(2048).default("^$"),
|
|
1858
|
+
write: z$1.string().max(2048).default("^$"),
|
|
1859
|
+
read: z$1.string().max(2048).default("^$"),
|
|
1860
|
+
maxTtlSeconds: managedTtlSchema
|
|
1861
|
+
}).strict();
|
|
1862
|
+
// ../../packages/core/src/managed-providers/clickhouse.ts
|
|
1863
|
+
const clickhouseTargetConfigSchema = z$1.object({
|
|
1864
|
+
provider: z$1.literal("clickhouse"),
|
|
1865
|
+
executor: z$1.literal("in_do"),
|
|
1866
|
+
endpoint: managedEndpointSchema,
|
|
1867
|
+
roles: z$1.array(nameSchema$1).min(1).max(50),
|
|
1868
|
+
database: nameSchema$1.optional(),
|
|
1869
|
+
cluster: nameSchema$1.optional(),
|
|
1870
|
+
maxTtlSeconds: managedTtlSchema
|
|
1871
|
+
}).strict();
|
|
1872
|
+
// ../../packages/core/src/managed-providers/elasticsearch.ts
|
|
1873
|
+
const role = z$1.object({
|
|
1874
|
+
cluster: z$1.array(z$1.string().min(1).max(100)).max(100).default([]),
|
|
1875
|
+
indices: z$1.array(z$1.object({
|
|
1876
|
+
names: z$1.array(z$1.string().min(1).max(256)).min(1).max(100),
|
|
1877
|
+
privileges: z$1.array(z$1.string().min(1).max(100)).min(1).max(100),
|
|
1878
|
+
allow_restricted_indices: z$1.literal(false).optional()
|
|
1879
|
+
}).strict()).max(100).default([])
|
|
1880
|
+
}).strict();
|
|
1881
|
+
const elasticsearchTargetConfigSchema = z$1.object({
|
|
1882
|
+
provider: z$1.literal("elasticsearch"),
|
|
1883
|
+
executor: z$1.literal("in_do"),
|
|
1884
|
+
endpoint: managedEndpointSchema,
|
|
1885
|
+
credentialType: z$1.enum(["user", "api_key"]).default("user"),
|
|
1886
|
+
roles: z$1.array(z$1.string().min(1).max(128)).min(1).max(100).optional(),
|
|
1887
|
+
roleDescriptors: z$1.record(z$1.string().min(1).max(128), role).refine((value) => Object.keys(value).length > 0 && Object.keys(value).length <= 20, "supply explicit key permissions").optional(),
|
|
1888
|
+
maxTtlSeconds: managedTtlSchema
|
|
1889
|
+
}).strict().superRefine((value, ctx) => {
|
|
1890
|
+
if (value.credentialType === "user" && !value.roles?.length) ctx.addIssue({
|
|
1891
|
+
code: "custom",
|
|
1892
|
+
path: ["roles"],
|
|
1893
|
+
message: "select at least one user role"
|
|
1894
|
+
});
|
|
1895
|
+
if (value.credentialType === "api_key" && !value.roleDescriptors) ctx.addIssue({
|
|
1896
|
+
code: "custom",
|
|
1897
|
+
path: ["roleDescriptors"],
|
|
1898
|
+
message: "supply explicit key permissions"
|
|
1899
|
+
});
|
|
1900
|
+
});
|
|
1901
|
+
// ../../packages/core/src/managed-leases.ts
|
|
1902
|
+
/** Providers whose credential is generated by the broker or upstream service. */
|
|
1903
|
+
const MANAGED_LEASE_KINDS = [
|
|
1904
|
+
"openrouter",
|
|
1905
|
+
"tailscale",
|
|
1906
|
+
"elasticsearch",
|
|
1907
|
+
"clickhouse",
|
|
1908
|
+
"rabbitmq",
|
|
1909
|
+
"milvus",
|
|
1910
|
+
"mongodb-atlas",
|
|
1911
|
+
"couchbase",
|
|
1912
|
+
"snowflake",
|
|
1913
|
+
"elasticache",
|
|
1914
|
+
"memorydb"
|
|
1915
|
+
];
|
|
1916
|
+
const openRouterTargetConfigSchema = z$1.object({
|
|
1917
|
+
provider: z$1.literal("openrouter"),
|
|
1918
|
+
executor: z$1.literal("in_do"),
|
|
1919
|
+
workspaceId: z$1.string().uuid().optional(),
|
|
1920
|
+
creditLimit: z$1.number().finite().nonnegative(),
|
|
1921
|
+
limitReset: z$1.enum([
|
|
1922
|
+
"daily",
|
|
1923
|
+
"weekly",
|
|
1924
|
+
"monthly"
|
|
1925
|
+
]).optional(),
|
|
1926
|
+
includeByokInLimit: z$1.boolean().optional(),
|
|
1927
|
+
maxTtlSeconds: z$1.number().int().min(60).max(604800).optional()
|
|
1928
|
+
}).strict();
|
|
1929
|
+
const tailscaleTargetConfigSchema = z$1.object({
|
|
1930
|
+
provider: z$1.literal("tailscale"),
|
|
1931
|
+
executor: z$1.literal("in_do"),
|
|
1932
|
+
tailnet: z$1.string().min(1).max(253).regex(/^[a-zA-Z0-9@._-]+$/),
|
|
1933
|
+
credentialType: z$1.enum([
|
|
1934
|
+
"auth",
|
|
1935
|
+
"client",
|
|
1936
|
+
"federated"
|
|
1937
|
+
]),
|
|
1938
|
+
tags: z$1.array(z$1.string().regex(/^tag:[a-zA-Z0-9_-]+$/)).max(100).default([]),
|
|
1939
|
+
reusable: z$1.boolean().default(false),
|
|
1940
|
+
ephemeral: z$1.boolean().default(true),
|
|
1941
|
+
preauthorized: z$1.boolean().default(false),
|
|
1942
|
+
scopes: z$1.array(z$1.string().min(1).max(128)).max(100).default([]),
|
|
1943
|
+
issuer: z$1.string().url().optional(),
|
|
1944
|
+
audience: z$1.string().min(1).max(2048).optional(),
|
|
1945
|
+
subject: z$1.string().min(1).max(2048).optional(),
|
|
1946
|
+
maxTtlSeconds: z$1.number().int().min(60).max(604800).optional()
|
|
1947
|
+
}).strict().superRefine((value, ctx) => {
|
|
1948
|
+
if (value.credentialType !== "auth" && !value.scopes.length) ctx.addIssue({
|
|
1949
|
+
code: "custom",
|
|
1950
|
+
path: ["scopes"],
|
|
1951
|
+
message: "select at least one scope"
|
|
1952
|
+
});
|
|
1953
|
+
if (value.credentialType === "federated" && (!value.issuer?.startsWith("https://") || !value.audience || !value.subject)) ctx.addIssue({
|
|
1954
|
+
code: "custom",
|
|
1955
|
+
path: ["issuer"],
|
|
1956
|
+
message: "federation requires HTTPS issuer, audience, and subject"
|
|
1957
|
+
});
|
|
1958
|
+
});
|
|
1959
|
+
const managedTargetConfigSchema = z$1.discriminatedUnion("provider", [
|
|
1960
|
+
clickhouseTargetConfigSchema,
|
|
1961
|
+
rabbitmqTargetConfigSchema,
|
|
1962
|
+
milvusTargetConfigSchema,
|
|
1963
|
+
mongoAtlasTargetConfigSchema,
|
|
1964
|
+
couchbaseTargetConfigSchema,
|
|
1965
|
+
snowflakeTargetConfigSchema,
|
|
1966
|
+
elasticacheTargetConfigSchema,
|
|
1967
|
+
memorydbTargetConfigSchema,
|
|
1968
|
+
openRouterTargetConfigSchema,
|
|
1969
|
+
tailscaleTargetConfigSchema,
|
|
1970
|
+
elasticsearchTargetConfigSchema
|
|
1971
|
+
]);
|
|
1972
|
+
function isManagedLeaseKind(value) {
|
|
1973
|
+
return MANAGED_LEASE_KINDS.includes(value);
|
|
1974
|
+
}
|
|
1975
|
+
function isManagedTarget(value) {
|
|
1976
|
+
return isManagedLeaseKind(value.provider);
|
|
1977
|
+
}
|
|
1978
|
+
z$1.enum([
|
|
1756
1979
|
"postgres",
|
|
1757
1980
|
"mysql",
|
|
1758
1981
|
"ssh",
|
|
1759
1982
|
"redis",
|
|
1760
1983
|
"aws",
|
|
1761
1984
|
"gcp",
|
|
1762
|
-
"mongodb"
|
|
1985
|
+
"mongodb",
|
|
1986
|
+
...MANAGED_LEASE_KINDS
|
|
1763
1987
|
]);
|
|
1764
|
-
const executorModeSchema = z.enum(["in_do", "remote"]);
|
|
1988
|
+
const executorModeSchema = z$1.enum(["in_do", "remote"]);
|
|
1765
1989
|
/**
|
|
1766
1990
|
* A Postgres role name we are willing to create. Deliberately strict — this
|
|
1767
1991
|
* value is interpolated into a SQL template, so it must be a bare identifier
|
|
1768
1992
|
* with no way to break out of quoting (no quotes, whitespace, or semicolons).
|
|
1769
1993
|
*/
|
|
1770
|
-
const postgresRoleNameSchema = z.string().regex(/^[a-z_][a-z0-9_]{2,62}$/, "must be 3–63 chars, lowercase letters/digits/underscore, starting with a letter or underscore");
|
|
1994
|
+
const postgresRoleNameSchema = z$1.string().regex(/^[a-z_][a-z0-9_]{2,62}$/, "must be 3–63 chars, lowercase letters/digits/underscore, starting with a letter or underscore");
|
|
1771
1995
|
/**
|
|
1772
1996
|
* A SCRAM-SHA-256 verifier string as produced by @seekrit/crypto. Validated so
|
|
1773
1997
|
* it, too, is safe to interpolate into a quoted SQL literal (the alphabet is
|
|
1774
1998
|
* base64 + the fixed structural characters, none of which is a single quote).
|
|
1775
1999
|
*/
|
|
1776
|
-
const scramVerifierSchema = z.string().regex(/^SCRAM-SHA-256\$\d{3,}:[A-Za-z0-9+/=]+\$[A-Za-z0-9+/=]+:[A-Za-z0-9+/=]+$/, "must be a SCRAM-SHA-256 verifier");
|
|
2000
|
+
const scramVerifierSchema = z$1.string().regex(/^SCRAM-SHA-256\$\d{3,}:[A-Za-z0-9+/=]+\$[A-Za-z0-9+/=]+:[A-Za-z0-9+/=]+$/, "must be a SCRAM-SHA-256 verifier");
|
|
1777
2001
|
/**
|
|
1778
2002
|
* An SSH login principal (a Unix-style username the certificate authorizes).
|
|
1779
2003
|
* Bounded and restricted to a safe charset — principals are SSH-wire-encoded,
|
|
1780
2004
|
* not shell-interpolated, so this is sanity/DoS hardening, not an injection gate.
|
|
1781
2005
|
*/
|
|
1782
|
-
const sshPrincipalSchema = z.string().regex(/^[A-Za-z0-9._-]{1,64}$/, "must be 1–64 chars of letters, digits, dot, dash, underscore");
|
|
2006
|
+
const sshPrincipalSchema = z$1.string().regex(/^[A-Za-z0-9._-]{1,64}$/, "must be 1–64 chars of letters, digits, dot, dash, underscore");
|
|
1783
2007
|
/** An `ssh-ed25519 <base64> [comment]` public key line (deep-validated on sign). */
|
|
1784
|
-
const sshPublicKeySchema = z.string().max(2048).regex(/^ssh-ed25519 [A-Za-z0-9+/=]+( .*)?$/, "must be an ssh-ed25519 public key");
|
|
2008
|
+
const sshPublicKeySchema = z$1.string().max(2048).regex(/^ssh-ed25519 [A-Za-z0-9+/=]+( .*)?$/, "must be an ssh-ed25519 public key");
|
|
1785
2009
|
/** An SSH certificate extension name, e.g. `permit-pty`. */
|
|
1786
|
-
const sshExtensionSchema = z.string().regex(/^[a-z0-9-]{1,64}$/);
|
|
2010
|
+
const sshExtensionSchema = z$1.string().regex(/^[a-z0-9-]{1,64}$/);
|
|
1787
2011
|
/**
|
|
1788
2012
|
* A MySQL/MariaDB user name we are willing to create. Interpolated into a
|
|
1789
2013
|
* quoted SQL literal (`'{{name}}'@'%'`), so it is kept strict — plain
|
|
1790
2014
|
* alphanumerics/underscore, no quotes/whitespace/semicolons to break out.
|
|
1791
2015
|
*/
|
|
1792
|
-
const mysqlUserNameSchema = z.string().regex(/^[A-Za-z0-9_]{3,32}$/, "must be 3–32 chars, letters/digits/underscore");
|
|
2016
|
+
const mysqlUserNameSchema = z$1.string().regex(/^[A-Za-z0-9_]{3,32}$/, "must be 3–32 chars, letters/digits/underscore");
|
|
1793
2017
|
/**
|
|
1794
2018
|
* A `mysql_native_password` authentication string — `*` followed by 40 upper
|
|
1795
2019
|
* hex chars (`UPPER(HEX(SHA1(SHA1(password))))`), as produced by
|
|
@@ -1797,14 +2021,14 @@ const mysqlUserNameSchema = z.string().regex(/^[A-Za-z0-9_]{3,32}$/, "must be 3
|
|
|
1797
2021
|
* `CREATE USER … IDENTIFIED WITH mysql_native_password AS '<str>'`, and its
|
|
1798
2022
|
* alphabet contains no single quote, so it is safe in a quoted SQL literal.
|
|
1799
2023
|
*/
|
|
1800
|
-
const mysqlNativeVerifierSchema = z.string().regex(/^\*[0-9A-F]{40}$/, "must be a mysql_native_password hash (*<40 hex>)");
|
|
2024
|
+
const mysqlNativeVerifierSchema = z$1.string().regex(/^\*[0-9A-F]{40}$/, "must be a mysql_native_password hash (*<40 hex>)");
|
|
1801
2025
|
/**
|
|
1802
2026
|
* A Redis ACL user name we are willing to create. Interpolated into a Redis
|
|
1803
2027
|
* command line as a bare token (`ACL SETUSER <name> …`), so it is kept strict —
|
|
1804
2028
|
* plain alphanumerics/underscore, no whitespace to split the arg or ACL rule
|
|
1805
2029
|
* characters (`~ + @ # & %`) that could be read as a permission.
|
|
1806
2030
|
*/
|
|
1807
|
-
const redisUserNameSchema = z.string().regex(/^[A-Za-z0-9_]{3,32}$/, "must be 3–32 chars, letters/digits/underscore");
|
|
2031
|
+
const redisUserNameSchema = z$1.string().regex(/^[A-Za-z0-9_]{3,32}$/, "must be 3–32 chars, letters/digits/underscore");
|
|
1808
2032
|
/**
|
|
1809
2033
|
* A Redis password verifier — the lowercase-hex SHA-256 of the password, as
|
|
1810
2034
|
* produced by @seekrit/crypto `redisSha256Verifier`. `ACL SETUSER … on #<hex>`
|
|
@@ -1813,22 +2037,22 @@ const redisUserNameSchema = z.string().regex(/^[A-Za-z0-9_]{3,32}$/, "must be 3
|
|
|
1813
2037
|
* is preimage-resistant (the password is high-entropy and machine-generated).
|
|
1814
2038
|
* The alphabet is bare hex, so it is a safe bare command token.
|
|
1815
2039
|
*/
|
|
1816
|
-
const redisSha256VerifierSchema = z.string().regex(/^[0-9a-f]{64}$/, "must be a lowercase-hex SHA-256 digest (64 chars)");
|
|
2040
|
+
const redisSha256VerifierSchema = z$1.string().regex(/^[0-9a-f]{64}$/, "must be a lowercase-hex SHA-256 digest (64 chars)");
|
|
1817
2041
|
/**
|
|
1818
2042
|
* An IAM role ARN the broker is allowed to assume. Bounded and structurally
|
|
1819
2043
|
* validated: `arn:<partition>:iam::<account>:role/<path-and-name>`. Partition
|
|
1820
2044
|
* covers commercial (`aws`), GovCloud (`aws-us-gov`), and China (`aws-cn`).
|
|
1821
2045
|
*/
|
|
1822
|
-
const awsRoleArnSchema = z.string().regex(/^arn:aws(?:-us-gov|-cn)?:iam::\d{12}:role\/[\w+=,.@/-]{1,512}$/, "must be an IAM role ARN (arn:aws:iam::<account>:role/<name>)");
|
|
2046
|
+
const awsRoleArnSchema = z$1.string().regex(/^arn:aws(?:-us-gov|-cn)?:iam::\d{12}:role\/[\w+=,.@/-]{1,512}$/, "must be an IAM role ARN (arn:aws:iam::<account>:role/<name>)");
|
|
1823
2047
|
/** An AWS region id, e.g. `us-east-1`, `eu-west-2`, `us-gov-west-1`. */
|
|
1824
|
-
const awsRegionSchema$1 = z.string().regex(/^[a-z]{2}(?:-[a-z]+)+-\d$/, "must be an AWS region id (e.g. us-east-1)");
|
|
2048
|
+
const awsRegionSchema$1 = z$1.string().regex(/^[a-z]{2}(?:-[a-z]+)+-\d$/, "must be an AWS region id (e.g. us-east-1)");
|
|
1825
2049
|
/**
|
|
1826
2050
|
* An STS external id — the shared string a role's trust policy can require so a
|
|
1827
2051
|
* confused-deputy can't assume it. AWS allows a broad charset; we keep to the
|
|
1828
2052
|
* documented safe set and bound the length.
|
|
1829
2053
|
*/
|
|
1830
|
-
const awsExternalIdSchema = z.string().regex(/^[\w+=,.@:/-]{2,1224}$/, "must be a valid STS external id");
|
|
1831
|
-
z.string().regex(/^[\w+=,.@-]{2,64}$/, "must be 2–64 chars of [A-Za-z0-9_+=,.@-]");
|
|
2054
|
+
const awsExternalIdSchema = z$1.string().regex(/^[\w+=,.@:/-]{2,1224}$/, "must be a valid STS external id");
|
|
2055
|
+
z$1.string().regex(/^[\w+=,.@-]{2,64}$/, "must be 2–64 chars of [A-Za-z0-9_+=,.@-]");
|
|
1832
2056
|
/**
|
|
1833
2057
|
* A GCP service-account email the broker is allowed to impersonate (or that
|
|
1834
2058
|
* appears in a delegation chain). Structurally validated and bounded: it is
|
|
@@ -1837,21 +2061,21 @@ z.string().regex(/^[\w+=,.@-]{2,64}$/, "must be 2–64 chars of [A-Za-z0-9_+=,.@
|
|
|
1837
2061
|
* (`name@<project>.iam.gserviceaccount.com`) and Google-managed
|
|
1838
2062
|
* (`<project-number>-compute@developer.gserviceaccount.com`) forms.
|
|
1839
2063
|
*/
|
|
1840
|
-
const gcpServiceAccountEmailSchema = z.string().max(256).regex(/^[a-z0-9-]+@[a-z0-9.-]+\.gserviceaccount\.com$/, "must be a service-account email (…@….gserviceaccount.com)");
|
|
2064
|
+
const gcpServiceAccountEmailSchema = z$1.string().max(256).regex(/^[a-z0-9-]+@[a-z0-9.-]+\.gserviceaccount\.com$/, "must be a service-account email (…@….gserviceaccount.com)");
|
|
1841
2065
|
/**
|
|
1842
2066
|
* An OAuth 2.0 scope granted to the minted access token, e.g.
|
|
1843
2067
|
* `https://www.googleapis.com/auth/cloud-platform`. Bounded and whitespace-free
|
|
1844
2068
|
* (scopes are space-delimited); passed to the IAM Credentials API in a JSON body
|
|
1845
2069
|
* array, not a URL, so this is sanity/DoS hardening rather than an injection gate.
|
|
1846
2070
|
*/
|
|
1847
|
-
const gcpOauthScopeSchema = z.string().min(1).max(256).regex(/^\S+$/, "must be a single OAuth scope with no whitespace");
|
|
2071
|
+
const gcpOauthScopeSchema = z$1.string().min(1).max(256).regex(/^\S+$/, "must be a single OAuth scope with no whitespace");
|
|
1848
2072
|
/**
|
|
1849
2073
|
* The consumer's ephemeral P-256 public key (JWK-serialized) that a tier-2
|
|
1850
2074
|
* credential is wrapped to before it is returned. Validated structurally here;
|
|
1851
2075
|
* the executor imports it defensively before wrapping. Bounded so a giant blob
|
|
1852
2076
|
* can't be pushed through the control plane.
|
|
1853
2077
|
*/
|
|
1854
|
-
const p256PublicKeyJwkSchema = z.string().max(2048).refine((s) => {
|
|
2078
|
+
const p256PublicKeyJwkSchema = z$1.string().max(2048).refine((s) => {
|
|
1855
2079
|
try {
|
|
1856
2080
|
const jwk = JSON.parse(s);
|
|
1857
2081
|
return jwk.kty === "EC" && jwk.crv === "P-256" && !!jwk.x && !!jwk.y;
|
|
@@ -1859,23 +2083,23 @@ const p256PublicKeyJwkSchema = z.string().max(2048).refine((s) => {
|
|
|
1859
2083
|
return false;
|
|
1860
2084
|
}
|
|
1861
2085
|
}, "must be a JWK-serialized P-256 public key");
|
|
1862
|
-
z.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "must be 1–64 chars of [A-Za-z0-9_-]");
|
|
2086
|
+
z$1.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "must be 1–64 chars of [A-Za-z0-9_-]");
|
|
1863
2087
|
/**
|
|
1864
2088
|
* A MongoDB database name — the db a preset role is granted on, or the
|
|
1865
2089
|
* authentication database a leased user is created in. MongoDB forbids
|
|
1866
2090
|
* `/\. "$*<>:|?` and the empty string in db names; we keep to a safe subset.
|
|
1867
2091
|
*/
|
|
1868
|
-
const mongoDatabaseNameSchema = z.string().regex(/^[A-Za-z0-9_-]{1,63}$/, "must be 1–63 chars of [A-Za-z0-9_-]");
|
|
2092
|
+
const mongoDatabaseNameSchema = z$1.string().regex(/^[A-Za-z0-9_-]{1,63}$/, "must be 1–63 chars of [A-Za-z0-9_-]");
|
|
1869
2093
|
/**
|
|
1870
2094
|
* A single MongoDB role grant `{ role, db }` for a `custom` target — e.g.
|
|
1871
2095
|
* `{ role: "readWrite", db: "app" }` or a user-defined role. Admin-supplied
|
|
1872
2096
|
* trusted input, set once at registration; still bounded structurally.
|
|
1873
2097
|
*/
|
|
1874
|
-
const mongoRoleSchema = z.object({
|
|
1875
|
-
role: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "must be a role name"),
|
|
2098
|
+
const mongoRoleSchema = z$1.object({
|
|
2099
|
+
role: z$1.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "must be a role name"),
|
|
1876
2100
|
db: mongoDatabaseNameSchema
|
|
1877
2101
|
});
|
|
1878
|
-
const postgresAccessLevelSchema = z.enum([
|
|
2102
|
+
const postgresAccessLevelSchema = z$1.enum([
|
|
1879
2103
|
"readonly",
|
|
1880
2104
|
"readwrite",
|
|
1881
2105
|
"custom"
|
|
@@ -1885,137 +2109,138 @@ const POSTGRES_GROUP_ROLES = {
|
|
|
1885
2109
|
readonly: "seekrit_readonly",
|
|
1886
2110
|
readwrite: "seekrit_readwrite"
|
|
1887
2111
|
};
|
|
1888
|
-
const mysqlAccessLevelSchema = z.enum([
|
|
2112
|
+
const mysqlAccessLevelSchema = z$1.enum([
|
|
1889
2113
|
"readonly",
|
|
1890
2114
|
"readwrite",
|
|
1891
2115
|
"custom"
|
|
1892
2116
|
]);
|
|
1893
|
-
const redisAccessLevelSchema = z.enum([
|
|
2117
|
+
const redisAccessLevelSchema = z$1.enum([
|
|
1894
2118
|
"readonly",
|
|
1895
2119
|
"readwrite",
|
|
1896
2120
|
"custom"
|
|
1897
2121
|
]);
|
|
1898
|
-
const mongoAccessLevelSchema = z.enum([
|
|
2122
|
+
const mongoAccessLevelSchema = z$1.enum([
|
|
1899
2123
|
"readonly",
|
|
1900
2124
|
"readwrite",
|
|
1901
2125
|
"custom"
|
|
1902
2126
|
]);
|
|
1903
|
-
const connectionSchema = z.object({
|
|
1904
|
-
host: z.string().min(1),
|
|
1905
|
-
port: z.number().int().min(1).max(65535),
|
|
1906
|
-
database: z.string().min(1)
|
|
2127
|
+
const connectionSchema = z$1.object({
|
|
2128
|
+
host: z$1.string().min(1),
|
|
2129
|
+
port: z$1.number().int().min(1).max(65535),
|
|
2130
|
+
database: z$1.string().min(1)
|
|
1907
2131
|
});
|
|
1908
2132
|
/** A `{{name}}`/`{{verifier}}`/`{{valid_until}}` templated SQL statement. */
|
|
1909
|
-
const statementSchema$1 = z.string().min(1).max(4e3);
|
|
2133
|
+
const statementSchema$1 = z$1.string().min(1).max(4e3);
|
|
1910
2134
|
/** A bare SQL identifier (schema name) — no quotes/whitespace/semicolons. */
|
|
1911
|
-
const identifierSchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,62}$/, "must be an identifier");
|
|
1912
|
-
const postgresTargetConfigSchema = z.object({
|
|
1913
|
-
provider: z.literal("postgres"),
|
|
2135
|
+
const identifierSchema = z$1.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,62}$/, "must be an identifier");
|
|
2136
|
+
const postgresTargetConfigSchema = z$1.object({
|
|
2137
|
+
provider: z$1.literal("postgres"),
|
|
1914
2138
|
executor: executorModeSchema,
|
|
1915
2139
|
accessLevel: postgresAccessLevelSchema.optional(),
|
|
1916
2140
|
schema: identifierSchema.optional(),
|
|
1917
2141
|
connection: connectionSchema,
|
|
1918
|
-
provisionerUrl: z.url().optional(),
|
|
1919
|
-
createStatements: z.array(statementSchema$1).max(16).optional(),
|
|
1920
|
-
revokeStatements: z.array(statementSchema$1).max(16).optional()
|
|
2142
|
+
provisionerUrl: z$1.url().optional(),
|
|
2143
|
+
createStatements: z$1.array(statementSchema$1).max(16).optional(),
|
|
2144
|
+
revokeStatements: z$1.array(statementSchema$1).max(16).optional()
|
|
1921
2145
|
});
|
|
1922
2146
|
/** A MySQL account host part (`'name'@'<host>'`) — no quotes/whitespace. */
|
|
1923
|
-
const mysqlHostSchema$1 = z.string().regex(/^[A-Za-z0-9_.%:-]{1,255}$/, "must be a host pattern");
|
|
1924
|
-
const mysqlTargetConfigSchema = z.object({
|
|
1925
|
-
provider: z.literal("mysql"),
|
|
2147
|
+
const mysqlHostSchema$1 = z$1.string().regex(/^[A-Za-z0-9_.%:-]{1,255}$/, "must be a host pattern");
|
|
2148
|
+
const mysqlTargetConfigSchema = z$1.object({
|
|
2149
|
+
provider: z$1.literal("mysql"),
|
|
1926
2150
|
executor: executorModeSchema,
|
|
1927
2151
|
accessLevel: mysqlAccessLevelSchema.optional(),
|
|
1928
2152
|
connection: connectionSchema,
|
|
1929
2153
|
userHost: mysqlHostSchema$1.optional(),
|
|
1930
|
-
provisionerUrl: z.url().optional(),
|
|
1931
|
-
createStatements: z.array(statementSchema$1).max(16).optional(),
|
|
1932
|
-
revokeStatements: z.array(statementSchema$1).max(16).optional()
|
|
2154
|
+
provisionerUrl: z$1.url().optional(),
|
|
2155
|
+
createStatements: z$1.array(statementSchema$1).max(16).optional(),
|
|
2156
|
+
revokeStatements: z$1.array(statementSchema$1).max(16).optional()
|
|
1933
2157
|
});
|
|
1934
|
-
const redisConnectionSchema = z.object({
|
|
1935
|
-
host: z.string().min(1),
|
|
1936
|
-
port: z.number().int().min(1).max(65535),
|
|
2158
|
+
const redisConnectionSchema = z$1.object({
|
|
2159
|
+
host: z$1.string().min(1),
|
|
2160
|
+
port: z$1.number().int().min(1).max(65535),
|
|
1937
2161
|
/** Redis logical database index (the `/<n>` in a connection URL). */
|
|
1938
|
-
db: z.number().int().min(0).max(15).optional()
|
|
2162
|
+
db: z$1.number().int().min(0).max(15).optional()
|
|
1939
2163
|
});
|
|
1940
|
-
const redisTargetConfigSchema = z.object({
|
|
1941
|
-
provider: z.literal("redis"),
|
|
2164
|
+
const redisTargetConfigSchema = z$1.object({
|
|
2165
|
+
provider: z$1.literal("redis"),
|
|
1942
2166
|
executor: executorModeSchema,
|
|
1943
2167
|
accessLevel: redisAccessLevelSchema.optional(),
|
|
1944
2168
|
connection: redisConnectionSchema,
|
|
1945
|
-
provisionerUrl: z.url().optional(),
|
|
1946
|
-
createStatements: z.array(statementSchema$1).max(16).optional(),
|
|
1947
|
-
revokeStatements: z.array(statementSchema$1).max(16).optional()
|
|
2169
|
+
provisionerUrl: z$1.url().optional(),
|
|
2170
|
+
createStatements: z$1.array(statementSchema$1).max(16).optional(),
|
|
2171
|
+
revokeStatements: z$1.array(statementSchema$1).max(16).optional()
|
|
1948
2172
|
});
|
|
1949
|
-
const sshTargetConfigSchema = z.object({
|
|
1950
|
-
provider: z.literal("ssh"),
|
|
1951
|
-
executor: z.literal("in_do"),
|
|
2173
|
+
const sshTargetConfigSchema = z$1.object({
|
|
2174
|
+
provider: z$1.literal("ssh"),
|
|
2175
|
+
executor: z$1.literal("in_do"),
|
|
1952
2176
|
caPublicKey: sshPublicKeySchema,
|
|
1953
|
-
allowedPrincipals: z.array(sshPrincipalSchema).max(64).optional(),
|
|
1954
|
-
extensions: z.array(sshExtensionSchema).max(16).optional(),
|
|
1955
|
-
maxTtlSeconds: z.number().int().min(60).max(604800).optional(),
|
|
1956
|
-
connection: z.object({
|
|
1957
|
-
host: z.string().min(1).optional(),
|
|
2177
|
+
allowedPrincipals: z$1.array(sshPrincipalSchema).max(64).optional(),
|
|
2178
|
+
extensions: z$1.array(sshExtensionSchema).max(16).optional(),
|
|
2179
|
+
maxTtlSeconds: z$1.number().int().min(60).max(604800).optional(),
|
|
2180
|
+
connection: z$1.object({
|
|
2181
|
+
host: z$1.string().min(1).optional(),
|
|
1958
2182
|
user: sshPrincipalSchema.optional()
|
|
1959
2183
|
}).optional()
|
|
1960
2184
|
});
|
|
1961
2185
|
const AWS_MAX_TTL_SECONDS = 43200;
|
|
1962
|
-
const awsTargetConfigSchema = z.object({
|
|
1963
|
-
provider: z.literal("aws"),
|
|
1964
|
-
executor: z.literal("in_do"),
|
|
2186
|
+
const awsTargetConfigSchema = z$1.object({
|
|
2187
|
+
provider: z$1.literal("aws"),
|
|
2188
|
+
executor: z$1.literal("in_do"),
|
|
1965
2189
|
roleArn: awsRoleArnSchema,
|
|
1966
2190
|
region: awsRegionSchema$1,
|
|
1967
2191
|
externalId: awsExternalIdSchema.optional(),
|
|
1968
|
-
sessionPolicy: z.string().min(1).max(4e3).optional(),
|
|
1969
|
-
maxTtlSeconds: z.number().int().min(900).max(AWS_MAX_TTL_SECONDS).optional()
|
|
2192
|
+
sessionPolicy: z$1.string().min(1).max(4e3).optional(),
|
|
2193
|
+
maxTtlSeconds: z$1.number().int().min(900).max(AWS_MAX_TTL_SECONDS).optional()
|
|
1970
2194
|
});
|
|
1971
2195
|
const GCP_MAX_TTL_SECONDS = 43200;
|
|
1972
|
-
const gcpTargetConfigSchema = z.object({
|
|
1973
|
-
provider: z.literal("gcp"),
|
|
1974
|
-
executor: z.literal("in_do"),
|
|
2196
|
+
const gcpTargetConfigSchema = z$1.object({
|
|
2197
|
+
provider: z$1.literal("gcp"),
|
|
2198
|
+
executor: z$1.literal("in_do"),
|
|
1975
2199
|
serviceAccount: gcpServiceAccountEmailSchema,
|
|
1976
|
-
scopes: z.array(gcpOauthScopeSchema).min(1).max(32).optional(),
|
|
1977
|
-
delegates: z.array(gcpServiceAccountEmailSchema).max(8).optional(),
|
|
1978
|
-
maxTtlSeconds: z.number().int().min(60).max(GCP_MAX_TTL_SECONDS).optional()
|
|
2200
|
+
scopes: z$1.array(gcpOauthScopeSchema).min(1).max(32).optional(),
|
|
2201
|
+
delegates: z$1.array(gcpServiceAccountEmailSchema).max(8).optional(),
|
|
2202
|
+
maxTtlSeconds: z$1.number().int().min(60).max(GCP_MAX_TTL_SECONDS).optional()
|
|
1979
2203
|
});
|
|
1980
|
-
const mongoTargetConfigSchema = z.object({
|
|
1981
|
-
provider: z.literal("mongodb"),
|
|
1982
|
-
executor: z.literal("in_do"),
|
|
2204
|
+
const mongoTargetConfigSchema = z$1.object({
|
|
2205
|
+
provider: z$1.literal("mongodb"),
|
|
2206
|
+
executor: z$1.literal("in_do"),
|
|
1983
2207
|
accessLevel: mongoAccessLevelSchema.optional(),
|
|
1984
2208
|
connection: connectionSchema,
|
|
1985
2209
|
authSource: mongoDatabaseNameSchema.optional(),
|
|
1986
|
-
roles: z.array(mongoRoleSchema).min(1).max(32).optional(),
|
|
1987
|
-
tls: z.boolean().optional(),
|
|
1988
|
-
maxTtlSeconds: z.number().int().min(60).max(604800).optional()
|
|
2210
|
+
roles: z$1.array(mongoRoleSchema).min(1).max(32).optional(),
|
|
2211
|
+
tls: z$1.boolean().optional(),
|
|
2212
|
+
maxTtlSeconds: z$1.number().int().min(60).max(604800).optional()
|
|
1989
2213
|
});
|
|
1990
|
-
const leaseTargetConfigSchema = z.discriminatedUnion("provider", [
|
|
2214
|
+
const leaseTargetConfigSchema = z$1.discriminatedUnion("provider", [
|
|
1991
2215
|
postgresTargetConfigSchema,
|
|
1992
2216
|
mysqlTargetConfigSchema,
|
|
1993
2217
|
redisTargetConfigSchema,
|
|
1994
2218
|
sshTargetConfigSchema,
|
|
1995
2219
|
awsTargetConfigSchema,
|
|
1996
2220
|
gcpTargetConfigSchema,
|
|
1997
|
-
mongoTargetConfigSchema
|
|
2221
|
+
mongoTargetConfigSchema,
|
|
2222
|
+
...managedTargetConfigSchema.options
|
|
1998
2223
|
]);
|
|
1999
|
-
z.object({
|
|
2000
|
-
name: z.string().trim().min(1).max(128),
|
|
2224
|
+
z$1.object({
|
|
2225
|
+
name: z$1.string().trim().min(1).max(128),
|
|
2001
2226
|
config: leaseTargetConfigSchema,
|
|
2002
2227
|
/**
|
|
2003
2228
|
* The admin/provisioning credential (e.g. a Postgres connection string),
|
|
2004
2229
|
* encrypted client-side to the broker's public key (a `wd1.` wrap). The
|
|
2005
2230
|
* control plane stores only this ciphertext — it never sees the plaintext.
|
|
2006
2231
|
*/
|
|
2007
|
-
wrappedAdminSecret: z.string().min(1)
|
|
2232
|
+
wrappedAdminSecret: z$1.string().min(1)
|
|
2008
2233
|
});
|
|
2009
2234
|
/** Requested lease lifetime, shared by all providers. */
|
|
2010
|
-
const ttlSecondsSchema = z.number().int().min(60).max(604800);
|
|
2235
|
+
const ttlSecondsSchema = z$1.number().int().min(60).max(604800);
|
|
2011
2236
|
/**
|
|
2012
2237
|
* Client → API: mint a Postgres lease. The client generates the password and
|
|
2013
2238
|
* its SCRAM verifier locally and sends only the verifier — the plaintext
|
|
2014
2239
|
* password never leaves the requesting machine.
|
|
2015
2240
|
*/
|
|
2016
|
-
const mintPostgresLeaseSchema = z.object({
|
|
2017
|
-
provider: z.literal("postgres"),
|
|
2018
|
-
targetId: z.string().min(1),
|
|
2241
|
+
const mintPostgresLeaseSchema = z$1.object({
|
|
2242
|
+
provider: z$1.literal("postgres"),
|
|
2243
|
+
targetId: z$1.string().min(1),
|
|
2019
2244
|
roleName: postgresRoleNameSchema,
|
|
2020
2245
|
verifier: scramVerifierSchema,
|
|
2021
2246
|
ttlSeconds: ttlSecondsSchema
|
|
@@ -2025,9 +2250,9 @@ const mintPostgresLeaseSchema = z.object({
|
|
|
2025
2250
|
* and its `mysql_native_password` hash locally and sends only the hash — the
|
|
2026
2251
|
* plaintext password never leaves the requesting machine.
|
|
2027
2252
|
*/
|
|
2028
|
-
const mintMysqlLeaseSchema = z.object({
|
|
2029
|
-
provider: z.literal("mysql"),
|
|
2030
|
-
targetId: z.string().min(1),
|
|
2253
|
+
const mintMysqlLeaseSchema = z$1.object({
|
|
2254
|
+
provider: z$1.literal("mysql"),
|
|
2255
|
+
targetId: z$1.string().min(1),
|
|
2031
2256
|
roleName: mysqlUserNameSchema,
|
|
2032
2257
|
verifier: mysqlNativeVerifierSchema,
|
|
2033
2258
|
ttlSeconds: ttlSecondsSchema
|
|
@@ -2037,9 +2262,9 @@ const mintMysqlLeaseSchema = z.object({
|
|
|
2037
2262
|
* SHA-256 hex digest locally and sends only the digest — the plaintext password
|
|
2038
2263
|
* never leaves the requesting machine.
|
|
2039
2264
|
*/
|
|
2040
|
-
const mintRedisLeaseSchema = z.object({
|
|
2041
|
-
provider: z.literal("redis"),
|
|
2042
|
-
targetId: z.string().min(1),
|
|
2265
|
+
const mintRedisLeaseSchema = z$1.object({
|
|
2266
|
+
provider: z$1.literal("redis"),
|
|
2267
|
+
targetId: z$1.string().min(1),
|
|
2043
2268
|
roleName: redisUserNameSchema,
|
|
2044
2269
|
verifier: redisSha256VerifierSchema,
|
|
2045
2270
|
ttlSeconds: ttlSecondsSchema
|
|
@@ -2049,11 +2274,11 @@ const mintRedisLeaseSchema = z.object({
|
|
|
2049
2274
|
* locally and sends only the public key; the signed certificate comes back in
|
|
2050
2275
|
* the response. The private key never leaves the requesting machine.
|
|
2051
2276
|
*/
|
|
2052
|
-
const mintSshLeaseSchema = z.object({
|
|
2053
|
-
provider: z.literal("ssh"),
|
|
2054
|
-
targetId: z.string().min(1),
|
|
2277
|
+
const mintSshLeaseSchema = z$1.object({
|
|
2278
|
+
provider: z$1.literal("ssh"),
|
|
2279
|
+
targetId: z$1.string().min(1),
|
|
2055
2280
|
publicKey: sshPublicKeySchema,
|
|
2056
|
-
principals: z.array(sshPrincipalSchema).min(1).max(32),
|
|
2281
|
+
principals: z$1.array(sshPrincipalSchema).min(1).max(32),
|
|
2057
2282
|
ttlSeconds: ttlSecondsSchema
|
|
2058
2283
|
});
|
|
2059
2284
|
/**
|
|
@@ -2065,11 +2290,11 @@ const mintSshLeaseSchema = z.object({
|
|
|
2065
2290
|
* TTL bounds are STS's own `DurationSeconds` limits (15 min – 12 h), not the
|
|
2066
2291
|
* generic lease bounds — STS rejects anything below 900 seconds.
|
|
2067
2292
|
*/
|
|
2068
|
-
const mintAwsLeaseSchema = z.object({
|
|
2069
|
-
provider: z.literal("aws"),
|
|
2070
|
-
targetId: z.string().min(1),
|
|
2293
|
+
const mintAwsLeaseSchema = z$1.object({
|
|
2294
|
+
provider: z$1.literal("aws"),
|
|
2295
|
+
targetId: z$1.string().min(1),
|
|
2071
2296
|
recipientPublicKey: p256PublicKeyJwkSchema,
|
|
2072
|
-
ttlSeconds: z.number().int().min(900).max(AWS_MAX_TTL_SECONDS)
|
|
2297
|
+
ttlSeconds: z$1.number().int().min(900).max(AWS_MAX_TTL_SECONDS)
|
|
2073
2298
|
});
|
|
2074
2299
|
/**
|
|
2075
2300
|
* Client → API: mint a GCP lease. Like AWS (tier 2): the client generates an
|
|
@@ -2081,11 +2306,11 @@ const mintAwsLeaseSchema = z.object({
|
|
|
2081
2306
|
* TTL bounds are GCP's `generateAccessToken` limits (1 min – 12 h); tokens over
|
|
2082
2307
|
* 1 h require the credential-lifetime-extension org policy.
|
|
2083
2308
|
*/
|
|
2084
|
-
const mintGcpLeaseSchema = z.object({
|
|
2085
|
-
provider: z.literal("gcp"),
|
|
2086
|
-
targetId: z.string().min(1),
|
|
2309
|
+
const mintGcpLeaseSchema = z$1.object({
|
|
2310
|
+
provider: z$1.literal("gcp"),
|
|
2311
|
+
targetId: z$1.string().min(1),
|
|
2087
2312
|
recipientPublicKey: p256PublicKeyJwkSchema,
|
|
2088
|
-
ttlSeconds: z.number().int().min(60).max(GCP_MAX_TTL_SECONDS)
|
|
2313
|
+
ttlSeconds: z$1.number().int().min(60).max(GCP_MAX_TTL_SECONDS)
|
|
2089
2314
|
});
|
|
2090
2315
|
/**
|
|
2091
2316
|
* Client → API: mint a MongoDB lease. Like AWS (tier 2), the client generates
|
|
@@ -2094,22 +2319,34 @@ const mintGcpLeaseSchema = z.object({
|
|
|
2094
2319
|
* to that key. The private key never leaves the requesting machine, so the
|
|
2095
2320
|
* plaintext credential is only decryptable there.
|
|
2096
2321
|
*/
|
|
2097
|
-
const mintMongoLeaseSchema = z.object({
|
|
2098
|
-
provider: z.literal("mongodb"),
|
|
2099
|
-
targetId: z.string().min(1),
|
|
2322
|
+
const mintMongoLeaseSchema = z$1.object({
|
|
2323
|
+
provider: z$1.literal("mongodb"),
|
|
2324
|
+
targetId: z$1.string().min(1),
|
|
2100
2325
|
recipientPublicKey: p256PublicKeyJwkSchema,
|
|
2101
2326
|
ttlSeconds: ttlSecondsSchema
|
|
2102
2327
|
});
|
|
2103
|
-
z.
|
|
2328
|
+
const mintManagedLeaseSchema = z$1.object({
|
|
2329
|
+
provider: z$1.enum(MANAGED_LEASE_KINDS),
|
|
2330
|
+
targetId: z$1.string().min(1),
|
|
2331
|
+
recipientPublicKey: p256PublicKeyJwkSchema.refine((value) => {
|
|
2332
|
+
try {
|
|
2333
|
+
return !("d" in JSON.parse(value));
|
|
2334
|
+
} catch {
|
|
2335
|
+
return false;
|
|
2336
|
+
}
|
|
2337
|
+
}, "send only a public key"),
|
|
2338
|
+
ttlSeconds: ttlSecondsSchema
|
|
2339
|
+
});
|
|
2340
|
+
z$1.discriminatedUnion("provider", [
|
|
2104
2341
|
mintPostgresLeaseSchema,
|
|
2105
2342
|
mintMysqlLeaseSchema,
|
|
2106
2343
|
mintRedisLeaseSchema,
|
|
2107
2344
|
mintSshLeaseSchema,
|
|
2108
2345
|
mintAwsLeaseSchema,
|
|
2109
2346
|
mintGcpLeaseSchema,
|
|
2110
|
-
mintMongoLeaseSchema
|
|
2347
|
+
mintMongoLeaseSchema,
|
|
2348
|
+
mintManagedLeaseSchema
|
|
2111
2349
|
]);
|
|
2112
|
-
// ../../packages/core/src/providers/aws.ts
|
|
2113
2350
|
/**
|
|
2114
2351
|
* The one-time IAM setup an admin performs so seekrit can assume the target
|
|
2115
2352
|
* role. Analogue of `sshHostSetupInstructions` / `postgresGroupBootstrapSql` —
|
|
@@ -2136,7 +2373,6 @@ function awsTrustPolicyInstructions(config) {
|
|
|
2136
2373
|
`# on ${config.roleArn}. seekrit only ever calls AssumeRole on this role.`
|
|
2137
2374
|
].join("\n");
|
|
2138
2375
|
}
|
|
2139
|
-
// ../../packages/core/src/providers/gcp.ts
|
|
2140
2376
|
/**
|
|
2141
2377
|
* The one-time IAM setup an admin performs so seekrit can impersonate the target
|
|
2142
2378
|
* service account. Analogue of `awsTrustPolicyInstructions` — printed for the
|
|
@@ -2158,7 +2394,6 @@ function gcpSetupInstructions(config) {
|
|
|
2158
2394
|
"gcloud services enable iamcredentials.googleapis.com"
|
|
2159
2395
|
].join("\n");
|
|
2160
2396
|
}
|
|
2161
|
-
// ../../packages/core/src/providers/mongodb.ts
|
|
2162
2397
|
/**
|
|
2163
2398
|
* The one-time setup an admin performs so seekrit can provision users: create a
|
|
2164
2399
|
* dedicated provisioning user with `userAdmin` on the target database (or
|
|
@@ -2183,7 +2418,6 @@ function mongoAdminSetupInstructions(config) {
|
|
|
2183
2418
|
`// --uri "mongodb://seekrit_provisioner:<pwd>@${config.connection.host}:${config.connection.port}/?authSource=${authSource}"`
|
|
2184
2419
|
].join("\n");
|
|
2185
2420
|
}
|
|
2186
|
-
// ../../packages/core/src/providers/postgres.ts
|
|
2187
2421
|
/**
|
|
2188
2422
|
* The one-time setup SQL an admin runs to create the shared group role that a
|
|
2189
2423
|
* read-only / read-write target's leased credentials inherit. Idempotent (safe
|
|
@@ -2214,7 +2448,6 @@ function postgresGroupBootstrapSql(config) {
|
|
|
2214
2448
|
if (config.accessLevel === "readwrite") lines.push(`GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA "${schema}" TO ${group};`, `ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT USAGE, SELECT ON SEQUENCES TO ${group};`);
|
|
2215
2449
|
return lines.join("\n");
|
|
2216
2450
|
}
|
|
2217
|
-
// ../../packages/core/src/providers/ssh.ts
|
|
2218
2451
|
/**
|
|
2219
2452
|
* The one-time host setup an admin runs so a target's certificates are accepted.
|
|
2220
2453
|
* Analogue of `postgresGroupBootstrapSql` — displayed for the admin to run, not
|
|
@@ -2234,6 +2467,13 @@ function sshHostSetupInstructions(config) {
|
|
|
2234
2467
|
"sudo systemctl reload sshd"
|
|
2235
2468
|
].join("\n");
|
|
2236
2469
|
}
|
|
2470
|
+
// ../../packages/core/src/slug.ts
|
|
2471
|
+
/**
|
|
2472
|
+
* The slug rule, with no dependencies — so a caller that cannot afford zod (the
|
|
2473
|
+
* resolve gateway's bundle, see apps/api/src/gateway) validates with the same
|
|
2474
|
+
* pattern `slugSchema` uses rather than a copy of it.
|
|
2475
|
+
*/
|
|
2476
|
+
const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2237
2477
|
// ../../packages/core/src/types.ts
|
|
2238
2478
|
const AUDIT_ACTIONS = [
|
|
2239
2479
|
"workload.created",
|
|
@@ -2468,94 +2708,94 @@ const NOTIFICATION_TYPE_META = {
|
|
|
2468
2708
|
};
|
|
2469
2709
|
// ../../packages/core/src/schemas.ts
|
|
2470
2710
|
/** URL-safe identifier segment: `my-app`, `production`, … */
|
|
2471
|
-
const slugSchema = z.string().min(1).max(64).regex(
|
|
2472
|
-
const nameSchema = z.string().trim().min(1).max(128);
|
|
2711
|
+
const slugSchema = z$1.string().min(1).max(64).regex(SLUG_PATTERN, "must be lowercase alphanumeric with hyphens");
|
|
2712
|
+
const nameSchema = z$1.string().trim().min(1).max(128);
|
|
2473
2713
|
/** An email address, normalized to trimmed lowercase before validation. */
|
|
2474
|
-
const emailSchema = z.string().trim().toLowerCase().pipe(z.email().max(320));
|
|
2714
|
+
const emailSchema = z$1.string().trim().toLowerCase().pipe(z$1.email().max(320));
|
|
2475
2715
|
/** Env-var style secret name: FOO, DATABASE_URL, apiKey2 … */
|
|
2476
|
-
const secretNameSchema = z.string().min(1).max(256).regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "must be a valid environment variable name");
|
|
2477
|
-
const orgRoleSchema = z.enum([
|
|
2716
|
+
const secretNameSchema = z$1.string().min(1).max(256).regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "must be a valid environment variable name");
|
|
2717
|
+
const orgRoleSchema = z$1.enum([
|
|
2478
2718
|
"owner",
|
|
2479
2719
|
"admin",
|
|
2480
2720
|
"member"
|
|
2481
2721
|
]);
|
|
2482
2722
|
/** Role a person may be invited at — never `owner` (ownership isn't invitable). */
|
|
2483
|
-
const inviteRoleSchema = z.enum(["admin", "member"]);
|
|
2484
|
-
const principalTypeSchema = z.enum(["user", "service_token"]);
|
|
2723
|
+
const inviteRoleSchema = z$1.enum(["admin", "member"]);
|
|
2724
|
+
const principalTypeSchema = z$1.enum(["user", "service_token"]);
|
|
2485
2725
|
/** Org-level capability a service token can hold (never `owner`). */
|
|
2486
|
-
const serviceTokenRoleSchema = z.enum(["admin", "member"]);
|
|
2487
|
-
z.object({
|
|
2726
|
+
const serviceTokenRoleSchema = z$1.enum(["admin", "member"]);
|
|
2727
|
+
z$1.object({
|
|
2488
2728
|
/** Include the full append-only ciphertext history of every secret. */
|
|
2489
|
-
includeVersions: z.boolean().optional(),
|
|
2490
|
-
includeAudit: z.boolean().optional(),
|
|
2729
|
+
includeVersions: z$1.boolean().optional(),
|
|
2730
|
+
includeAudit: z$1.boolean().optional(),
|
|
2491
2731
|
/** Newest audit rows to keep. Exceeding the server cap is a 400, not a silent trim. */
|
|
2492
|
-
auditLimit: z.number().int().min(0).optional()
|
|
2732
|
+
auditLimit: z$1.number().int().min(0).optional()
|
|
2493
2733
|
});
|
|
2494
|
-
z.object({
|
|
2734
|
+
z$1.object({
|
|
2495
2735
|
name: nameSchema,
|
|
2496
2736
|
slug: slugSchema
|
|
2497
2737
|
});
|
|
2498
|
-
z.object({
|
|
2738
|
+
z$1.object({
|
|
2499
2739
|
name: nameSchema,
|
|
2500
2740
|
slug: slugSchema
|
|
2501
2741
|
});
|
|
2502
|
-
z.object({ name: nameSchema });
|
|
2503
|
-
z.object({ name: nameSchema });
|
|
2504
|
-
z.object({ name: nameSchema });
|
|
2505
|
-
z.object({ name: nameSchema });
|
|
2506
|
-
z.object({ name: nameSchema });
|
|
2507
|
-
z.object({ required: z.boolean() });
|
|
2508
|
-
z.object({
|
|
2742
|
+
z$1.object({ name: nameSchema });
|
|
2743
|
+
z$1.object({ name: nameSchema });
|
|
2744
|
+
z$1.object({ name: nameSchema });
|
|
2745
|
+
z$1.object({ name: nameSchema });
|
|
2746
|
+
z$1.object({ name: nameSchema });
|
|
2747
|
+
z$1.object({ required: z$1.boolean() });
|
|
2748
|
+
z$1.object({
|
|
2509
2749
|
email: emailSchema,
|
|
2510
2750
|
role: inviteRoleSchema.default("member")
|
|
2511
2751
|
});
|
|
2512
|
-
z.object({
|
|
2752
|
+
z$1.object({
|
|
2513
2753
|
name: nameSchema,
|
|
2514
2754
|
slug: slugSchema
|
|
2515
2755
|
});
|
|
2516
2756
|
/** Compose a group into an application environment (matched by env slug). */
|
|
2517
|
-
const appAccessKeySchema = z.object({
|
|
2518
|
-
environmentId: z.string().min(1),
|
|
2519
|
-
userId: z.string().min(1),
|
|
2520
|
-
wrappedDek: z.string().min(1).max(8192)
|
|
2757
|
+
const appAccessKeySchema = z$1.object({
|
|
2758
|
+
environmentId: z$1.string().min(1),
|
|
2759
|
+
userId: z$1.string().min(1),
|
|
2760
|
+
wrappedDek: z$1.string().min(1).max(8192)
|
|
2521
2761
|
});
|
|
2522
|
-
const appAccessGrantsSchema = z.array(appAccessKeySchema).max(5e3).optional();
|
|
2523
|
-
z.object({
|
|
2524
|
-
userId: z.string().min(1),
|
|
2525
|
-
grants: z.array(appAccessKeySchema).max(5e3)
|
|
2762
|
+
const appAccessGrantsSchema = z$1.array(appAccessKeySchema).max(5e3).optional();
|
|
2763
|
+
z$1.object({
|
|
2764
|
+
userId: z$1.string().min(1),
|
|
2765
|
+
grants: z$1.array(appAccessKeySchema).max(5e3)
|
|
2526
2766
|
});
|
|
2527
|
-
z.object({ role: orgRoleSchema });
|
|
2528
|
-
z.object({
|
|
2767
|
+
z$1.object({ role: orgRoleSchema });
|
|
2768
|
+
z$1.object({
|
|
2529
2769
|
appAccessGrants: appAccessGrantsSchema,
|
|
2530
|
-
groupId: z.string().min(1),
|
|
2770
|
+
groupId: z$1.string().min(1),
|
|
2531
2771
|
/** Precedence among an env's groups (higher wins). Appended if omitted. */
|
|
2532
|
-
position: z.number().int().min(0).optional(),
|
|
2772
|
+
position: z$1.number().int().min(0).optional(),
|
|
2533
2773
|
/** Client-wrapped group DEK for each sync connection newly reading this group. */
|
|
2534
|
-
syncGrants: z.array(z.object({
|
|
2535
|
-
connectionId: z.string().min(1),
|
|
2536
|
-
wrappedDek: z.string().min(1)
|
|
2774
|
+
syncGrants: z$1.array(z$1.object({
|
|
2775
|
+
connectionId: z$1.string().min(1),
|
|
2776
|
+
wrappedDek: z$1.string().min(1)
|
|
2537
2777
|
})).optional()
|
|
2538
2778
|
});
|
|
2539
|
-
z.object({
|
|
2779
|
+
z$1.object({
|
|
2540
2780
|
/** Wrap the new DEK to all persistent app recipients. */
|
|
2541
|
-
userGrants: z.array(z.object({
|
|
2542
|
-
userId: z.string().min(1),
|
|
2543
|
-
wrappedDek: z.string().min(1).max(8192)
|
|
2781
|
+
userGrants: z$1.array(z$1.object({
|
|
2782
|
+
userId: z$1.string().min(1),
|
|
2783
|
+
wrappedDek: z$1.string().min(1).max(8192)
|
|
2544
2784
|
})).max(5e3).optional(),
|
|
2545
2785
|
name: nameSchema,
|
|
2546
2786
|
slug: slugSchema,
|
|
2547
2787
|
/** Environment DEK wrapped to the creator's public key — created client-side. */
|
|
2548
|
-
wrappedDek: z.string().min(1),
|
|
2788
|
+
wrappedDek: z$1.string().min(1),
|
|
2549
2789
|
/**
|
|
2550
2790
|
* When the org has recovery enabled, the same DEK additionally wrapped to the
|
|
2551
2791
|
* org recovery public key, so the environment is recovery-protected from
|
|
2552
2792
|
* creation. Omitted when recovery is off (backfilled later by `recovery sync`).
|
|
2553
2793
|
*/
|
|
2554
|
-
recoveryWrappedDek: z.string().min(1).nullish()
|
|
2794
|
+
recoveryWrappedDek: z$1.string().min(1).nullish()
|
|
2555
2795
|
});
|
|
2556
|
-
z.object({
|
|
2796
|
+
z$1.object({
|
|
2557
2797
|
/** Opaque versioned ciphertext blob from @seekrit/crypto. */
|
|
2558
|
-
ciphertext: z.string().min(1).max(65536),
|
|
2798
|
+
ciphertext: z$1.string().min(1).max(65536),
|
|
2559
2799
|
/**
|
|
2560
2800
|
* Compare-and-swap: write only if the secret is currently at this version
|
|
2561
2801
|
* (`0` = only if it does not exist yet), else 412 `precondition_failed`.
|
|
@@ -2563,77 +2803,77 @@ z.object({
|
|
|
2563
2803
|
* changed, and wrote back — a refreshed agent login, say — find out that
|
|
2564
2804
|
* someone else wrote in between instead of silently erasing their value.
|
|
2565
2805
|
*/
|
|
2566
|
-
expectedVersion: z.number().int().min(0).optional()
|
|
2806
|
+
expectedVersion: z$1.number().int().min(0).optional()
|
|
2567
2807
|
});
|
|
2568
|
-
z.object({ version: z.number().int().positive() });
|
|
2569
|
-
z.object({ limit: z.coerce.number().int().min(1).max(200).default(50) });
|
|
2570
|
-
z.object({
|
|
2571
|
-
requiredApprovals: z.number().int().min(1).max(10).default(1),
|
|
2572
|
-
allowSelfApproval: z.boolean().default(false)
|
|
2808
|
+
z$1.object({ version: z$1.number().int().positive() });
|
|
2809
|
+
z$1.object({ limit: z$1.coerce.number().int().min(1).max(200).default(50) });
|
|
2810
|
+
z$1.object({
|
|
2811
|
+
requiredApprovals: z$1.number().int().min(1).max(10).default(1),
|
|
2812
|
+
allowSelfApproval: z$1.boolean().default(false)
|
|
2573
2813
|
});
|
|
2574
|
-
z.object({ comment: z.string().trim().max(1e3).optional() });
|
|
2575
|
-
z.string().trim().min(1).max(1e3);
|
|
2576
|
-
z.object({
|
|
2577
|
-
status: z.enum(CHANGE_REQUEST_STATUSES).optional(),
|
|
2578
|
-
envId: z.string().min(1).optional(),
|
|
2579
|
-
limit: z.coerce.number().int().min(1).max(200).default(50)
|
|
2814
|
+
z$1.object({ comment: z$1.string().trim().max(1e3).optional() });
|
|
2815
|
+
z$1.string().trim().min(1).max(1e3);
|
|
2816
|
+
z$1.object({
|
|
2817
|
+
status: z$1.enum(CHANGE_REQUEST_STATUSES).optional(),
|
|
2818
|
+
envId: z$1.string().min(1).optional(),
|
|
2819
|
+
limit: z$1.coerce.number().int().min(1).max(200).default(50)
|
|
2580
2820
|
});
|
|
2581
|
-
z.object({
|
|
2582
|
-
publicKeyJwk: z.string().min(1),
|
|
2821
|
+
z$1.object({
|
|
2822
|
+
publicKeyJwk: z$1.string().min(1),
|
|
2583
2823
|
/**
|
|
2584
2824
|
* Private key encrypted with a passphrase-derived KEK; opaque to the
|
|
2585
2825
|
* server. Self-contained blob (embeds KDF salt + iterations).
|
|
2586
2826
|
*/
|
|
2587
|
-
encryptedPrivateKey: z.string().min(1)
|
|
2827
|
+
encryptedPrivateKey: z$1.string().min(1)
|
|
2588
2828
|
});
|
|
2589
|
-
z.object({
|
|
2829
|
+
z$1.object({
|
|
2590
2830
|
/** Base64url WebAuthn credential id — what `allowCredentials` is built from. */
|
|
2591
|
-
credentialId: z.string().min(1).max(512),
|
|
2831
|
+
credentialId: z$1.string().min(1).max(512),
|
|
2592
2832
|
/** Human label for the device, e.g. "MacBook Touch ID". Display only. */
|
|
2593
|
-
label: z.string().min(1).max(64),
|
|
2833
|
+
label: z$1.string().min(1).max(64),
|
|
2594
2834
|
/** Base64url PRF evaluation input for this credential; not a secret. */
|
|
2595
|
-
prfInput: z.string().min(1).max(256),
|
|
2835
|
+
prfInput: z$1.string().min(1).max(256),
|
|
2596
2836
|
/** The private key wrapped to this passkey's PRF output. */
|
|
2597
|
-
encryptedPrivateKey: z.string().min(1).max(8192).refine((v) => v.startsWith("pk2."), { message: "expected a pk2. passkey wrap" })
|
|
2837
|
+
encryptedPrivateKey: z$1.string().min(1).max(8192).refine((v) => v.startsWith("pk2."), { message: "expected a pk2. passkey wrap" })
|
|
2598
2838
|
});
|
|
2599
|
-
const grantEnvironmentKeySchema = z.object({
|
|
2839
|
+
const grantEnvironmentKeySchema = z$1.object({
|
|
2600
2840
|
principalType: principalTypeSchema,
|
|
2601
|
-
principalId: z.string().min(1),
|
|
2602
|
-
wrappedDek: z.string().min(1)
|
|
2841
|
+
principalId: z$1.string().min(1),
|
|
2842
|
+
wrappedDek: z$1.string().min(1)
|
|
2603
2843
|
});
|
|
2604
|
-
z.object({
|
|
2844
|
+
z$1.object({
|
|
2605
2845
|
slug: slugSchema,
|
|
2606
2846
|
/** Display name; defaults to the slug. */
|
|
2607
2847
|
name: nameSchema.optional(),
|
|
2608
|
-
ttlSeconds: z.number().int().min(60).max(MAX_BRANCH_TTL_SECONDS).nullish(),
|
|
2848
|
+
ttlSeconds: z$1.number().int().min(60).max(MAX_BRANCH_TTL_SECONDS).nullish(),
|
|
2609
2849
|
/** The branch's own DEK, wrapped to the creator — generated client-side. */
|
|
2610
|
-
wrappedDek: z.string().min(1),
|
|
2611
|
-
recoveryWrappedDek: z.string().min(1).nullish(),
|
|
2850
|
+
wrappedDek: z$1.string().min(1),
|
|
2851
|
+
recoveryWrappedDek: z$1.string().min(1).nullish(),
|
|
2612
2852
|
/** The same DEK wrapped to each of the parent's existing grant-holders. */
|
|
2613
|
-
grants: z.array(grantEnvironmentKeySchema).max(500).default([])
|
|
2853
|
+
grants: z$1.array(grantEnvironmentKeySchema).max(500).default([])
|
|
2614
2854
|
});
|
|
2615
2855
|
/**
|
|
2616
2856
|
* Token creation is client-driven so the server never sees the token secret:
|
|
2617
2857
|
* the client generates the keypair + token string and submits only the hash
|
|
2618
2858
|
* and public key.
|
|
2619
2859
|
*/
|
|
2620
|
-
const environmentGrantBatchSchema = z.array(z.object({
|
|
2621
|
-
environmentId: z.string().min(1),
|
|
2622
|
-
wrappedDek: z.string().min(1).max(8192)
|
|
2860
|
+
const environmentGrantBatchSchema = z$1.array(z$1.object({
|
|
2861
|
+
environmentId: z$1.string().min(1),
|
|
2862
|
+
wrappedDek: z$1.string().min(1).max(8192)
|
|
2623
2863
|
})).max(5e3);
|
|
2624
|
-
z.object({
|
|
2864
|
+
z$1.object({
|
|
2625
2865
|
principalType: principalTypeSchema,
|
|
2626
|
-
principalId: z.string().min(1),
|
|
2866
|
+
principalId: z$1.string().min(1),
|
|
2627
2867
|
grants: environmentGrantBatchSchema
|
|
2628
2868
|
});
|
|
2629
|
-
z.object({
|
|
2869
|
+
z$1.object({
|
|
2630
2870
|
/** Commit the token and all default resolve keys together, when supplied. */
|
|
2631
2871
|
initialGrants: environmentGrantBatchSchema.optional(),
|
|
2632
2872
|
name: nameSchema,
|
|
2633
|
-
tokenId: z.string().regex(/^skt_[0-9A-Za-z]+$/),
|
|
2873
|
+
tokenId: z$1.string().regex(/^skt_[0-9A-Za-z]+$/),
|
|
2634
2874
|
/** SHA-256 hash (base64url) of the full token string. */
|
|
2635
|
-
tokenHash: z.string().min(1),
|
|
2636
|
-
publicKeyJwk: z.string().min(1),
|
|
2875
|
+
tokenHash: z$1.string().min(1),
|
|
2876
|
+
publicKeyJwk: z$1.string().min(1),
|
|
2637
2877
|
/**
|
|
2638
2878
|
* Org-level capability. Defaults to `member` (a runtime credential); pass
|
|
2639
2879
|
* `admin` to mint a headless provisioning token. Only an admin caller may
|
|
@@ -2645,35 +2885,35 @@ z.object({
|
|
|
2645
2885
|
* Optional so org-admin tokens can exist, but required for runtime tokens
|
|
2646
2886
|
* that resolve secrets via `GET /v1/resolve`.
|
|
2647
2887
|
*/
|
|
2648
|
-
environmentId: z.string().min(1).nullish(),
|
|
2649
|
-
expiresAt: z.iso.datetime().nullish()
|
|
2888
|
+
environmentId: z$1.string().min(1).nullish(),
|
|
2889
|
+
expiresAt: z$1.iso.datetime().nullish()
|
|
2650
2890
|
});
|
|
2651
|
-
z.object({
|
|
2891
|
+
z$1.object({
|
|
2652
2892
|
name: nameSchema,
|
|
2653
|
-
tokenId: z.string().regex(/^skt_[0-9A-Za-z]+$/),
|
|
2893
|
+
tokenId: z$1.string().regex(/^skt_[0-9A-Za-z]+$/),
|
|
2654
2894
|
/** SHA-256 hash (base64url) of the full token string. */
|
|
2655
|
-
tokenHash: z.string().min(1),
|
|
2895
|
+
tokenHash: z$1.string().min(1),
|
|
2656
2896
|
/** Where the decoy was planted, as a reminder for whoever reads the alert. */
|
|
2657
|
-
placement: z.string().max(200).nullish()
|
|
2897
|
+
placement: z$1.string().max(200).nullish()
|
|
2658
2898
|
});
|
|
2659
|
-
const kmsKeyPurposeSchema = z.enum(["encrypt", "sign"]);
|
|
2660
|
-
const kmsKeySpecSchema = z.enum(["aes-256-gcm", "ecdsa-p256"]);
|
|
2899
|
+
const kmsKeyPurposeSchema = z$1.enum(["encrypt", "sign"]);
|
|
2900
|
+
const kmsKeySpecSchema = z$1.enum(["aes-256-gcm", "ecdsa-p256"]);
|
|
2661
2901
|
/** A wrapped key grant supplied by the client (server never sees plaintext material). */
|
|
2662
|
-
const kmsGrantInputSchema = z.object({
|
|
2902
|
+
const kmsGrantInputSchema = z$1.object({
|
|
2663
2903
|
principalType: principalTypeSchema,
|
|
2664
|
-
principalId: z.string().min(1),
|
|
2904
|
+
principalId: z$1.string().min(1),
|
|
2665
2905
|
/** Key material wrapped to the principal's public key (`wd1.` blob). */
|
|
2666
|
-
wrappedKey: z.string().min(1)
|
|
2906
|
+
wrappedKey: z$1.string().min(1)
|
|
2667
2907
|
});
|
|
2668
|
-
z.object({
|
|
2908
|
+
z$1.object({
|
|
2669
2909
|
name: nameSchema,
|
|
2670
2910
|
purpose: kmsKeyPurposeSchema,
|
|
2671
2911
|
spec: kmsKeySpecSchema,
|
|
2672
|
-
applicationId: z.string().min(1).nullish(),
|
|
2673
|
-
groupId: z.string().min(1).nullish(),
|
|
2912
|
+
applicationId: z$1.string().min(1).nullish(),
|
|
2913
|
+
groupId: z$1.string().min(1).nullish(),
|
|
2674
2914
|
/** ECDSA P-256 public key (JWK) for `sign` keys; omit for `encrypt` keys. */
|
|
2675
|
-
publicKeyJwk: z.string().min(1).nullish(),
|
|
2676
|
-
grants: z.array(kmsGrantInputSchema).min(1)
|
|
2915
|
+
publicKeyJwk: z$1.string().min(1).nullish(),
|
|
2916
|
+
grants: z$1.array(kmsGrantInputSchema).min(1)
|
|
2677
2917
|
}).refine((v) => !(v.applicationId && v.groupId), {
|
|
2678
2918
|
message: "a key may be scoped to an application or a group, not both",
|
|
2679
2919
|
path: ["groupId"]
|
|
@@ -2684,15 +2924,15 @@ z.object({
|
|
|
2684
2924
|
message: "sign keys require a publicKeyJwk; encrypt keys must omit it",
|
|
2685
2925
|
path: ["publicKeyJwk"]
|
|
2686
2926
|
});
|
|
2687
|
-
z.object({
|
|
2927
|
+
z$1.object({
|
|
2688
2928
|
principalType: principalTypeSchema,
|
|
2689
|
-
principalId: z.string().min(1),
|
|
2929
|
+
principalId: z$1.string().min(1),
|
|
2690
2930
|
/** Current-version key material wrapped to the principal's public key. */
|
|
2691
|
-
wrappedKey: z.string().min(1)
|
|
2931
|
+
wrappedKey: z$1.string().min(1)
|
|
2692
2932
|
});
|
|
2693
|
-
z.object({
|
|
2694
|
-
publicKeyJwk: z.string().min(1).nullish(),
|
|
2695
|
-
grants: z.array(kmsGrantInputSchema).min(1)
|
|
2933
|
+
z$1.object({
|
|
2934
|
+
publicKeyJwk: z$1.string().min(1).nullish(),
|
|
2935
|
+
grants: z$1.array(kmsGrantInputSchema).min(1)
|
|
2696
2936
|
});
|
|
2697
2937
|
/**
|
|
2698
2938
|
* One custodian's wrapped Shamir share of the org recovery private key. The
|
|
@@ -2700,25 +2940,25 @@ z.object({
|
|
|
2700
2940
|
* wraps each share to a custodian's public key — the server stores only the
|
|
2701
2941
|
* opaque `wrappedShare` and can reconstruct nothing.
|
|
2702
2942
|
*/
|
|
2703
|
-
const recoveryShareInputSchema = z.object({
|
|
2943
|
+
const recoveryShareInputSchema = z$1.object({
|
|
2704
2944
|
principalType: principalTypeSchema,
|
|
2705
|
-
principalId: z.string().min(1),
|
|
2945
|
+
principalId: z$1.string().min(1),
|
|
2706
2946
|
/** Shamir x-coordinate carried by the share (1..255). */
|
|
2707
|
-
shareIndex: z.number().int().min(1).max(255),
|
|
2947
|
+
shareIndex: z$1.number().int().min(1).max(255),
|
|
2708
2948
|
/** The recovery-key share wrapped to the custodian's public key (`wd1.`). */
|
|
2709
|
-
wrappedShare: z.string().min(1)
|
|
2949
|
+
wrappedShare: z$1.string().min(1)
|
|
2710
2950
|
});
|
|
2711
2951
|
/** An environment DEK additionally wrapped to the org recovery public key. */
|
|
2712
|
-
const recoveryEnvGrantSchema = z.object({
|
|
2713
|
-
environmentId: z.string().min(1),
|
|
2952
|
+
const recoveryEnvGrantSchema = z$1.object({
|
|
2953
|
+
environmentId: z$1.string().min(1),
|
|
2714
2954
|
/** The environment's DEK wrapped to the recovery public key (`wd1.`). */
|
|
2715
|
-
wrappedDek: z.string().min(1)
|
|
2955
|
+
wrappedDek: z$1.string().min(1)
|
|
2716
2956
|
});
|
|
2717
|
-
z.object({
|
|
2718
|
-
recoveryPublicKeyJwk: z.string().min(1),
|
|
2719
|
-
threshold: z.number().int().min(1).max(255),
|
|
2720
|
-
shares: z.array(recoveryShareInputSchema).min(1).max(255),
|
|
2721
|
-
grants: z.array(recoveryEnvGrantSchema).default([])
|
|
2957
|
+
z$1.object({
|
|
2958
|
+
recoveryPublicKeyJwk: z$1.string().min(1),
|
|
2959
|
+
threshold: z$1.number().int().min(1).max(255),
|
|
2960
|
+
shares: z$1.array(recoveryShareInputSchema).min(1).max(255),
|
|
2961
|
+
grants: z$1.array(recoveryEnvGrantSchema).default([])
|
|
2722
2962
|
}).refine((v) => v.threshold <= v.shares.length, {
|
|
2723
2963
|
message: "threshold cannot exceed the number of custodians",
|
|
2724
2964
|
path: ["threshold"]
|
|
@@ -2726,104 +2966,104 @@ z.object({
|
|
|
2726
2966
|
message: "custodians must be distinct",
|
|
2727
2967
|
path: ["shares"]
|
|
2728
2968
|
});
|
|
2729
|
-
z.object({ grants: z.array(recoveryEnvGrantSchema).min(1).max(500) });
|
|
2730
|
-
z.object({
|
|
2731
|
-
targetPublicKeyJwk: z.string().min(1),
|
|
2969
|
+
z$1.object({ grants: z$1.array(recoveryEnvGrantSchema).min(1).max(500) });
|
|
2970
|
+
z$1.object({
|
|
2971
|
+
targetPublicKeyJwk: z$1.string().min(1),
|
|
2732
2972
|
targetType: principalTypeSchema.nullish(),
|
|
2733
|
-
targetId: z.string().min(1).nullish(),
|
|
2734
|
-
reason: z.string().max(500).nullish()
|
|
2973
|
+
targetId: z$1.string().min(1).nullish(),
|
|
2974
|
+
reason: z$1.string().max(500).nullish()
|
|
2735
2975
|
});
|
|
2736
|
-
z.object({
|
|
2737
|
-
shareIndex: z.number().int().min(1).max(255),
|
|
2976
|
+
z$1.object({
|
|
2977
|
+
shareIndex: z$1.number().int().min(1).max(255),
|
|
2738
2978
|
/** The custodian's share re-wrapped to the target public key (`wd1.`). */
|
|
2739
|
-
contributedShare: z.string().min(1)
|
|
2979
|
+
contributedShare: z$1.string().min(1)
|
|
2740
2980
|
});
|
|
2741
|
-
z.object({
|
|
2981
|
+
z$1.object({
|
|
2742
2982
|
principalType: principalTypeSchema,
|
|
2743
|
-
principalId: z.string().min(1),
|
|
2744
|
-
grants: z.array(recoveryEnvGrantSchema).min(1).max(500)
|
|
2983
|
+
principalId: z$1.string().min(1),
|
|
2984
|
+
grants: z$1.array(recoveryEnvGrantSchema).min(1).max(500)
|
|
2745
2985
|
});
|
|
2746
|
-
z.object({ prefs: z.partialRecord(z.enum(NOTIFICATION_TYPES), z.boolean()) });
|
|
2747
|
-
z.object({
|
|
2748
|
-
endpoint: z.url().max(2048),
|
|
2749
|
-
headers: z.record(z.string().min(1).max(256), z.string().max(4096)).optional(),
|
|
2750
|
-
enabled: z.boolean().default(true)
|
|
2986
|
+
z$1.object({ prefs: z$1.partialRecord(z$1.enum(NOTIFICATION_TYPES), z$1.boolean()) });
|
|
2987
|
+
z$1.object({
|
|
2988
|
+
endpoint: z$1.url().max(2048),
|
|
2989
|
+
headers: z$1.record(z$1.string().min(1).max(256), z$1.string().max(4096)).optional(),
|
|
2990
|
+
enabled: z$1.boolean().default(true)
|
|
2751
2991
|
});
|
|
2752
|
-
const planFamilySchema = z.enum(PLAN_FAMILY_IDS);
|
|
2753
|
-
const subscriptionStatusSchema = z.enum(SUBSCRIPTION_STATUSES);
|
|
2754
|
-
z.enum(ENTITLEMENT_KEYS);
|
|
2992
|
+
const planFamilySchema = z$1.enum(PLAN_FAMILY_IDS);
|
|
2993
|
+
const subscriptionStatusSchema = z$1.enum(SUBSCRIPTION_STATUSES);
|
|
2994
|
+
z$1.enum(ENTITLEMENT_KEYS);
|
|
2755
2995
|
/** An entitlement value: boolean (features), number or null/unlimited (limits, metered). */
|
|
2756
|
-
const entitlementValueSchema = z.union([
|
|
2757
|
-
z.boolean(),
|
|
2758
|
-
z.number(),
|
|
2759
|
-
z.null()
|
|
2996
|
+
const entitlementValueSchema = z$1.union([
|
|
2997
|
+
z$1.boolean(),
|
|
2998
|
+
z$1.number(),
|
|
2999
|
+
z$1.null()
|
|
2760
3000
|
]);
|
|
2761
|
-
z.object({
|
|
3001
|
+
z$1.object({
|
|
2762
3002
|
family: planFamilySchema,
|
|
2763
|
-
version: z.number().int().positive().optional(),
|
|
3003
|
+
version: z$1.number().int().positive().optional(),
|
|
2764
3004
|
status: subscriptionStatusSchema.default("active")
|
|
2765
3005
|
});
|
|
2766
|
-
z.object({
|
|
3006
|
+
z$1.object({
|
|
2767
3007
|
value: entitlementValueSchema,
|
|
2768
|
-
note: z.string().max(500).nullish(),
|
|
2769
|
-
expiresAt: z.iso.datetime().nullish()
|
|
3008
|
+
note: z$1.string().max(500).nullish(),
|
|
3009
|
+
expiresAt: z$1.iso.datetime().nullish()
|
|
2770
3010
|
});
|
|
2771
|
-
z.object({
|
|
2772
|
-
code: z.string().min(4).max(40),
|
|
3011
|
+
z$1.object({
|
|
3012
|
+
code: z$1.string().min(4).max(40),
|
|
2773
3013
|
family: planFamilySchema,
|
|
2774
|
-
version: z.number().int().positive().optional(),
|
|
2775
|
-
durationDays: z.number().int().positive().max(3650).nullish(),
|
|
2776
|
-
maxRedemptions: z.number().int().positive().nullish(),
|
|
2777
|
-
startsAt: z.iso.datetime().nullish(),
|
|
2778
|
-
endsAt: z.iso.datetime().nullish(),
|
|
2779
|
-
note: z.string().max(500).nullish()
|
|
3014
|
+
version: z$1.number().int().positive().optional(),
|
|
3015
|
+
durationDays: z$1.number().int().positive().max(3650).nullish(),
|
|
3016
|
+
maxRedemptions: z$1.number().int().positive().nullish(),
|
|
3017
|
+
startsAt: z$1.iso.datetime().nullish(),
|
|
3018
|
+
endsAt: z$1.iso.datetime().nullish(),
|
|
3019
|
+
note: z$1.string().max(500).nullish()
|
|
2780
3020
|
});
|
|
2781
|
-
z.object({
|
|
2782
|
-
maxRedemptions: z.number().int().positive().nullish(),
|
|
2783
|
-
startsAt: z.iso.datetime().nullish(),
|
|
2784
|
-
endsAt: z.iso.datetime().nullish(),
|
|
2785
|
-
note: z.string().max(500).nullish(),
|
|
3021
|
+
z$1.object({
|
|
3022
|
+
maxRedemptions: z$1.number().int().positive().nullish(),
|
|
3023
|
+
startsAt: z$1.iso.datetime().nullish(),
|
|
3024
|
+
endsAt: z$1.iso.datetime().nullish(),
|
|
3025
|
+
note: z$1.string().max(500).nullish(),
|
|
2786
3026
|
/** Kill switch. Disabling stops new redemptions; live grants are untouched. */
|
|
2787
|
-
disabled: z.boolean().optional()
|
|
3027
|
+
disabled: z$1.boolean().optional()
|
|
2788
3028
|
});
|
|
2789
|
-
z.object({ code: z.string().min(1).max(40) });
|
|
2790
|
-
z.object({ family: planFamilySchema });
|
|
2791
|
-
z.object({
|
|
2792
|
-
sessionId: z.string().regex(/^skc_[0-9A-Za-z]+$/),
|
|
3029
|
+
z$1.object({ code: z$1.string().min(1).max(40) });
|
|
3030
|
+
z$1.object({ family: planFamilySchema });
|
|
3031
|
+
z$1.object({
|
|
3032
|
+
sessionId: z$1.string().regex(/^skc_[0-9A-Za-z]+$/),
|
|
2793
3033
|
/** SHA-256 hash (base64url) of the full session token string. */
|
|
2794
|
-
tokenHash: z.string().min(1).max(128),
|
|
3034
|
+
tokenHash: z$1.string().min(1).max(128),
|
|
2795
3035
|
/** Display-only, e.g. `miles@studio.local`. */
|
|
2796
|
-
deviceLabel: z.string().trim().min(1).max(120),
|
|
3036
|
+
deviceLabel: z$1.string().trim().min(1).max(120),
|
|
2797
3037
|
/** Display-only, e.g. `cli/0.4.2`. */
|
|
2798
|
-
client: z.string().trim().max(60).optional()
|
|
3038
|
+
client: z$1.string().trim().max(60).optional()
|
|
2799
3039
|
});
|
|
2800
|
-
z.object({ code: z.string().trim().min(1).max(32) });
|
|
2801
|
-
z.object({
|
|
2802
|
-
cursor: z.string().optional(),
|
|
2803
|
-
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
2804
|
-
action: z.string().optional(),
|
|
2805
|
-
resourceType: z.string().optional()
|
|
3040
|
+
z$1.object({ code: z$1.string().trim().min(1).max(32) });
|
|
3041
|
+
z$1.object({
|
|
3042
|
+
cursor: z$1.string().optional(),
|
|
3043
|
+
limit: z$1.coerce.number().int().min(1).max(200).default(50),
|
|
3044
|
+
action: z$1.string().optional(),
|
|
3045
|
+
resourceType: z$1.string().optional()
|
|
2806
3046
|
});
|
|
2807
|
-
z.enum([
|
|
3047
|
+
z$1.enum([
|
|
2808
3048
|
"generated",
|
|
2809
3049
|
"postgres",
|
|
2810
3050
|
"mysql",
|
|
2811
3051
|
"redis"
|
|
2812
3052
|
]);
|
|
2813
|
-
z.enum([
|
|
3053
|
+
z$1.enum([
|
|
2814
3054
|
"active",
|
|
2815
3055
|
"paused",
|
|
2816
3056
|
"failed"
|
|
2817
3057
|
]);
|
|
2818
3058
|
/** Statuses an admin may set directly (`failed` is only reached by the sweep). */
|
|
2819
|
-
const settableRotationStatusSchema = z.enum(["active", "paused"]);
|
|
2820
|
-
const rotationAlphabetSchema = z.enum([
|
|
3059
|
+
const settableRotationStatusSchema = z$1.enum(["active", "paused"]);
|
|
3060
|
+
const rotationAlphabetSchema = z$1.enum([
|
|
2821
3061
|
"alphanumeric",
|
|
2822
3062
|
"hex",
|
|
2823
3063
|
"base64url",
|
|
2824
3064
|
"printable"
|
|
2825
3065
|
]);
|
|
2826
|
-
const rotationIntervalSchema = z.number().int().min(300).max(31536e3);
|
|
3066
|
+
const rotationIntervalSchema = z$1.number().int().min(300).max(31536e3);
|
|
2827
3067
|
/**
|
|
2828
3068
|
* A database user name we are willing to *re-key*. Deliberately more permissive
|
|
2829
3069
|
* than the lease providers' name schemas — those name accounts seekrit creates,
|
|
@@ -2836,44 +3076,44 @@ const rotationIntervalSchema = z.number().int().min(300).max(31536e3);
|
|
|
2836
3076
|
* `;`, so there is no way out of the surrounding quoting, and no whitespace to
|
|
2837
3077
|
* split one Redis argument into two.
|
|
2838
3078
|
*/
|
|
2839
|
-
const rotationUsernameSchema = z.string().regex(/^[A-Za-z0-9_$.-]{1,63}$/, "must be 1–63 chars of letters, digits, underscore, dollar, dot or dash");
|
|
3079
|
+
const rotationUsernameSchema = z$1.string().regex(/^[A-Za-z0-9_$.-]{1,63}$/, "must be 1–63 chars of letters, digits, underscore, dollar, dot or dash");
|
|
2840
3080
|
/** A `{{name}}`/`{{host}}`/`{{verifier}}` templated statement or command line. */
|
|
2841
|
-
const statementSchema = z.string().min(1).max(4e3);
|
|
2842
|
-
const passwordLengthSchema = z.number().int().min(16).max(256);
|
|
3081
|
+
const statementSchema = z$1.string().min(1).max(4e3);
|
|
3082
|
+
const passwordLengthSchema = z$1.number().int().min(16).max(256);
|
|
2843
3083
|
/** A MySQL account host part (`'name'@'<host>'`) — no quotes/whitespace. */
|
|
2844
|
-
const mysqlHostSchema = z.string().regex(/^[A-Za-z0-9_.%:-]{1,255}$/, "must be a host pattern");
|
|
2845
|
-
const generatedRotationConfigSchema = z.object({
|
|
2846
|
-
kind: z.literal("generated"),
|
|
3084
|
+
const mysqlHostSchema = z$1.string().regex(/^[A-Za-z0-9_.%:-]{1,255}$/, "must be a host pattern");
|
|
3085
|
+
const generatedRotationConfigSchema = z$1.object({
|
|
3086
|
+
kind: z$1.literal("generated"),
|
|
2847
3087
|
length: passwordLengthSchema.optional(),
|
|
2848
3088
|
alphabet: rotationAlphabetSchema.optional()
|
|
2849
3089
|
});
|
|
2850
|
-
const postgresRotationConfigSchema = z.object({
|
|
2851
|
-
kind: z.literal("postgres"),
|
|
3090
|
+
const postgresRotationConfigSchema = z$1.object({
|
|
3091
|
+
kind: z$1.literal("postgres"),
|
|
2852
3092
|
username: rotationUsernameSchema,
|
|
2853
3093
|
passwordLength: passwordLengthSchema.optional(),
|
|
2854
|
-
statements: z.array(statementSchema).max(16).optional()
|
|
3094
|
+
statements: z$1.array(statementSchema).max(16).optional()
|
|
2855
3095
|
});
|
|
2856
|
-
const mysqlRotationConfigSchema = z.object({
|
|
2857
|
-
kind: z.literal("mysql"),
|
|
3096
|
+
const mysqlRotationConfigSchema = z$1.object({
|
|
3097
|
+
kind: z$1.literal("mysql"),
|
|
2858
3098
|
username: rotationUsernameSchema,
|
|
2859
3099
|
userHost: mysqlHostSchema.optional(),
|
|
2860
3100
|
passwordLength: passwordLengthSchema.optional(),
|
|
2861
|
-
statements: z.array(statementSchema).max(16).optional()
|
|
3101
|
+
statements: z$1.array(statementSchema).max(16).optional()
|
|
2862
3102
|
});
|
|
2863
|
-
const redisRotationConfigSchema = z.object({
|
|
2864
|
-
kind: z.literal("redis"),
|
|
3103
|
+
const redisRotationConfigSchema = z$1.object({
|
|
3104
|
+
kind: z$1.literal("redis"),
|
|
2865
3105
|
username: rotationUsernameSchema,
|
|
2866
3106
|
passwordLength: passwordLengthSchema.optional(),
|
|
2867
|
-
statements: z.array(statementSchema).max(16).optional()
|
|
3107
|
+
statements: z$1.array(statementSchema).max(16).optional()
|
|
2868
3108
|
});
|
|
2869
|
-
const rotationConfigSchema = z.discriminatedUnion("kind", [
|
|
3109
|
+
const rotationConfigSchema = z$1.discriminatedUnion("kind", [
|
|
2870
3110
|
generatedRotationConfigSchema,
|
|
2871
3111
|
postgresRotationConfigSchema,
|
|
2872
3112
|
mysqlRotationConfigSchema,
|
|
2873
3113
|
redisRotationConfigSchema
|
|
2874
3114
|
]);
|
|
2875
|
-
z.object({
|
|
2876
|
-
environmentId: z.string().min(1),
|
|
3115
|
+
z$1.object({
|
|
3116
|
+
environmentId: z$1.string().min(1),
|
|
2877
3117
|
/** The secret whose value rotates. It must already exist. */
|
|
2878
3118
|
secretName: secretNameSchema,
|
|
2879
3119
|
config: rotationConfigSchema,
|
|
@@ -2882,13 +3122,13 @@ z.object({
|
|
|
2882
3122
|
* The registered lease target supplying the connection, executor mode, and
|
|
2883
3123
|
* wrapped admin credential. Required for every kind but `generated`.
|
|
2884
3124
|
*/
|
|
2885
|
-
targetId: z.string().min(1).optional(),
|
|
3125
|
+
targetId: z$1.string().min(1).optional(),
|
|
2886
3126
|
/** Environment DEK wrapped to the rotator public key (`wd1.` blob). */
|
|
2887
|
-
wrappedDek: z.string().min(1).optional(),
|
|
3127
|
+
wrappedDek: z$1.string().min(1).optional(),
|
|
2888
3128
|
/** Rotate once immediately instead of waiting for the first interval. */
|
|
2889
|
-
rotateNow: z.boolean().optional()
|
|
3129
|
+
rotateNow: z$1.boolean().optional()
|
|
2890
3130
|
});
|
|
2891
|
-
z.object({
|
|
3131
|
+
z$1.object({
|
|
2892
3132
|
intervalSeconds: rotationIntervalSchema.optional(),
|
|
2893
3133
|
config: rotationConfigSchema.optional(),
|
|
2894
3134
|
/**
|
|
@@ -2948,7 +3188,7 @@ const SYNC_PROVIDER_KINDS = [
|
|
|
2948
3188
|
"openai-vault",
|
|
2949
3189
|
"claude-managed-agents"
|
|
2950
3190
|
];
|
|
2951
|
-
z.enum(SYNC_PROVIDER_KINDS);
|
|
3191
|
+
z$1.enum(SYNC_PROVIDER_KINDS);
|
|
2952
3192
|
/**
|
|
2953
3193
|
* Vercel account scope. The API token itself is never here — it is wrapped to
|
|
2954
3194
|
* the connection's public key and stored as ciphertext.
|
|
@@ -2957,10 +3197,10 @@ z.enum(SYNC_PROVIDER_KINDS);
|
|
|
2957
3197
|
* tokens omit it. Vercel rejects team-owned project calls that lack it with a
|
|
2958
3198
|
* bare 403, so we pass it through as `?teamId=` on every request.
|
|
2959
3199
|
*/
|
|
2960
|
-
const vercelConnectionConfigSchema = z.object({
|
|
2961
|
-
provider: z.literal("vercel"),
|
|
3200
|
+
const vercelConnectionConfigSchema = z$1.object({
|
|
3201
|
+
provider: z$1.literal("vercel"),
|
|
2962
3202
|
/** Vercel Team id (`team_…`). Omit for a personal account. */
|
|
2963
|
-
teamId: z.string().trim().min(1).max(128).optional()
|
|
3203
|
+
teamId: z$1.string().trim().min(1).max(128).optional()
|
|
2964
3204
|
});
|
|
2965
3205
|
/**
|
|
2966
3206
|
* A Cloudflare account id — 32 lowercase hex characters, found in the sidebar
|
|
@@ -2971,7 +3211,7 @@ const vercelConnectionConfigSchema = z.object({
|
|
|
2971
3211
|
* hours later inside an alarm, with nobody watching. It does not catch pasting
|
|
2972
3212
|
* a *zone* id, which has the same shape — only the API can tell those apart.
|
|
2973
3213
|
*/
|
|
2974
|
-
const cloudflareAccountIdSchema = z.string().trim().regex(/^[0-9a-f]{32}$/, "must be a 32-character Cloudflare account ID (lowercase hex)");
|
|
3214
|
+
const cloudflareAccountIdSchema = z$1.string().trim().regex(/^[0-9a-f]{32}$/, "must be a 32-character Cloudflare account ID (lowercase hex)");
|
|
2975
3215
|
/**
|
|
2976
3216
|
* Cloudflare account scope, shared by all three Cloudflare providers. The API
|
|
2977
3217
|
* token is never here — it is wrapped to the connection's public key and
|
|
@@ -2982,16 +3222,16 @@ const cloudflareAccountIdSchema = z.string().trim().regex(/^[0-9a-f]{32}$/, "mus
|
|
|
2982
3222
|
* destinations, and fail in different ways. Splitting them keeps the
|
|
2983
3223
|
* exhaustiveness guard in `connectorFor` meaningful.
|
|
2984
3224
|
*/
|
|
2985
|
-
const cloudflareWorkersConnectionConfigSchema = z.object({
|
|
2986
|
-
provider: z.literal("cloudflare-workers"),
|
|
3225
|
+
const cloudflareWorkersConnectionConfigSchema = z$1.object({
|
|
3226
|
+
provider: z$1.literal("cloudflare-workers"),
|
|
2987
3227
|
accountId: cloudflareAccountIdSchema
|
|
2988
3228
|
});
|
|
2989
|
-
const cloudflarePagesConnectionConfigSchema = z.object({
|
|
2990
|
-
provider: z.literal("cloudflare-pages"),
|
|
3229
|
+
const cloudflarePagesConnectionConfigSchema = z$1.object({
|
|
3230
|
+
provider: z$1.literal("cloudflare-pages"),
|
|
2991
3231
|
accountId: cloudflareAccountIdSchema
|
|
2992
3232
|
});
|
|
2993
|
-
const cloudflareSecretsStoreConnectionConfigSchema = z.object({
|
|
2994
|
-
provider: z.literal("cloudflare-secrets-store"),
|
|
3233
|
+
const cloudflareSecretsStoreConnectionConfigSchema = z$1.object({
|
|
3234
|
+
provider: z$1.literal("cloudflare-secrets-store"),
|
|
2995
3235
|
accountId: cloudflareAccountIdSchema
|
|
2996
3236
|
});
|
|
2997
3237
|
/**
|
|
@@ -3000,7 +3240,7 @@ const cloudflareSecretsStoreConnectionConfigSchema = z.object({
|
|
|
3000
3240
|
* bare GraphQL "Problem processing request" hours later inside an alarm, with
|
|
3001
3241
|
* nobody watching.
|
|
3002
3242
|
*/
|
|
3003
|
-
const railwayIdSchema = z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a Railway UUID");
|
|
3243
|
+
const railwayIdSchema = z$1.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a Railway UUID");
|
|
3004
3244
|
/**
|
|
3005
3245
|
* Which kind of Railway API token the connection holds. This is connection
|
|
3006
3246
|
* scope rather than a destination detail because it decides the *header* the
|
|
@@ -3024,9 +3264,9 @@ const RAILWAY_TOKEN_KINDS = ["account", "project"];
|
|
|
3024
3264
|
* a destination names its project outright, so the token plus the destination
|
|
3025
3265
|
* is the whole address.
|
|
3026
3266
|
*/
|
|
3027
|
-
const railwayConnectionConfigSchema = z.object({
|
|
3028
|
-
provider: z.literal("railway"),
|
|
3029
|
-
tokenKind: z.enum(RAILWAY_TOKEN_KINDS).default("account")
|
|
3267
|
+
const railwayConnectionConfigSchema = z$1.object({
|
|
3268
|
+
provider: z$1.literal("railway"),
|
|
3269
|
+
tokenKind: z$1.enum(RAILWAY_TOKEN_KINDS).default("account")
|
|
3030
3270
|
});
|
|
3031
3271
|
/**
|
|
3032
3272
|
* An AWS region id (`us-east-1`, `eu-central-1`, `us-gov-west-1`).
|
|
@@ -3036,7 +3276,7 @@ const railwayConnectionConfigSchema = z.object({
|
|
|
3036
3276
|
* would otherwise surface as a DNS failure inside an alarm with nobody
|
|
3037
3277
|
* watching — which is a much worse place to learn about it than this form.
|
|
3038
3278
|
*/
|
|
3039
|
-
const awsRegionSchema = z.string().trim().regex(/^[a-z]{2}(-[a-z]+)+-\d$/, "must be an AWS region ID, e.g. us-east-1");
|
|
3279
|
+
const awsRegionSchema = z$1.string().trim().regex(/^[a-z]{2}(-[a-z]+)+-\d$/, "must be an AWS region ID, e.g. us-east-1");
|
|
3040
3280
|
/**
|
|
3041
3281
|
* The IAM access key id seekrit signs with.
|
|
3042
3282
|
*
|
|
@@ -3051,7 +3291,7 @@ const awsRegionSchema = z.string().trim().regex(/^[a-z]{2}(-[a-z]+)+-\d$/, "must
|
|
|
3051
3291
|
* Long-lived IAM user keys only. `ASIA…` session credentials from STS expire
|
|
3052
3292
|
* within hours, and a sync connection has to keep working unattended.
|
|
3053
3293
|
*/
|
|
3054
|
-
const awsAccessKeyIdSchema = z.string().trim().regex(/^[A-Z0-9]{16,128}$/, "must be an AWS access key ID, e.g. AKIAIOSFODNN7EXAMPLE");
|
|
3294
|
+
const awsAccessKeyIdSchema = z$1.string().trim().regex(/^[A-Z0-9]{16,128}$/, "must be an AWS access key ID, e.g. AKIAIOSFODNN7EXAMPLE");
|
|
3055
3295
|
/**
|
|
3056
3296
|
* AWS account scope, shared by both AWS providers: which region to call and
|
|
3057
3297
|
* which key to sign with. There is no account id — every endpoint seekrit calls
|
|
@@ -3062,13 +3302,13 @@ const awsAccessKeyIdSchema = z.string().trim().regex(/^[A-Z0-9]{16,128}$/, "must
|
|
|
3062
3302
|
* the three Cloudflare kinds are separate: different APIs, different
|
|
3063
3303
|
* destinations, different IAM actions.
|
|
3064
3304
|
*/
|
|
3065
|
-
const awsSecretsManagerConnectionConfigSchema = z.object({
|
|
3066
|
-
provider: z.literal("aws-secrets-manager"),
|
|
3305
|
+
const awsSecretsManagerConnectionConfigSchema = z$1.object({
|
|
3306
|
+
provider: z$1.literal("aws-secrets-manager"),
|
|
3067
3307
|
region: awsRegionSchema,
|
|
3068
3308
|
accessKeyId: awsAccessKeyIdSchema
|
|
3069
3309
|
});
|
|
3070
|
-
const awsParameterStoreConnectionConfigSchema = z.object({
|
|
3071
|
-
provider: z.literal("aws-parameter-store"),
|
|
3310
|
+
const awsParameterStoreConnectionConfigSchema = z$1.object({
|
|
3311
|
+
provider: z$1.literal("aws-parameter-store"),
|
|
3072
3312
|
region: awsRegionSchema,
|
|
3073
3313
|
accessKeyId: awsAccessKeyIdSchema
|
|
3074
3314
|
});
|
|
@@ -3082,7 +3322,7 @@ const awsParameterStoreConnectionConfigSchema = z.object({
|
|
|
3082
3322
|
* nothing is stored. The connection's `name` is what tells an operator which Render
|
|
3083
3323
|
* workspace it belongs to.
|
|
3084
3324
|
*/
|
|
3085
|
-
const renderConnectionConfigSchema = z.object({ provider: z.literal("render") });
|
|
3325
|
+
const renderConnectionConfigSchema = z$1.object({ provider: z$1.literal("render") });
|
|
3086
3326
|
/**
|
|
3087
3327
|
* Fly.io account scope — empty, as Render's is.
|
|
3088
3328
|
*
|
|
@@ -3092,7 +3332,7 @@ const renderConnectionConfigSchema = z.object({ provider: z.literal("render") })
|
|
|
3092
3332
|
* is — Fly's two token shapes do take different auth schemes, but
|
|
3093
3333
|
* `flyAuthorization` in the connector reads which one from the token itself.
|
|
3094
3334
|
*/
|
|
3095
|
-
const flyConnectionConfigSchema = z.object({ provider: z.literal("fly") });
|
|
3335
|
+
const flyConnectionConfigSchema = z$1.object({ provider: z$1.literal("fly") });
|
|
3096
3336
|
/**
|
|
3097
3337
|
* A Northflank id — projects and secret groups are both slugs derived from the
|
|
3098
3338
|
* name they were created with (`default-project`, `example-secret-group`), and
|
|
@@ -3100,7 +3340,7 @@ const flyConnectionConfigSchema = z.object({ provider: z.literal("fly") });
|
|
|
3100
3340
|
* so the common slip — pasting the *display name*, spaces and all — fails here
|
|
3101
3341
|
* rather than as a bare 404 inside an alarm with nobody watching.
|
|
3102
3342
|
*/
|
|
3103
|
-
const northflankIdSchema = z.string().trim().min(3).max(100).regex(/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/, "must be a Northflank ID — the slug in the resource's URL, not its display name");
|
|
3343
|
+
const northflankIdSchema = z$1.string().trim().min(3).max(100).regex(/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/, "must be a Northflank ID — the slug in the resource's URL, not its display name");
|
|
3104
3344
|
/**
|
|
3105
3345
|
* Northflank account scope — deliberately empty.
|
|
3106
3346
|
*
|
|
@@ -3110,7 +3350,7 @@ const northflankIdSchema = z.string().trim().min(3).max(100).regex(/^[a-zA-Z0-9]
|
|
|
3110
3350
|
* Cloudflare does: the token plus the destination's project is the whole
|
|
3111
3351
|
* address. The kind exists so the discriminated union stays uniform.
|
|
3112
3352
|
*/
|
|
3113
|
-
const northflankConnectionConfigSchema = z.object({ provider: z.literal("northflank") });
|
|
3353
|
+
const northflankConnectionConfigSchema = z$1.object({ provider: z$1.literal("northflank") });
|
|
3114
3354
|
/**
|
|
3115
3355
|
* DigitalOcean account scope — deliberately empty, as Render's and Fly's are.
|
|
3116
3356
|
*
|
|
@@ -3120,7 +3360,7 @@ const northflankConnectionConfigSchema = z.object({ provider: z.literal("northfl
|
|
|
3120
3360
|
* disambiguate the way Vercel needs one: the token plus the destination's app
|
|
3121
3361
|
* id is the whole address.
|
|
3122
3362
|
*/
|
|
3123
|
-
const digitalOceanConnectionConfigSchema = z.object({ provider: z.literal("digitalocean") });
|
|
3363
|
+
const digitalOceanConnectionConfigSchema = z$1.object({ provider: z$1.literal("digitalocean") });
|
|
3124
3364
|
/**
|
|
3125
3365
|
* Heroku account scope — empty, as Fly's and Render's are.
|
|
3126
3366
|
*
|
|
@@ -3131,7 +3371,7 @@ const digitalOceanConnectionConfigSchema = z.object({ provider: z.literal("digit
|
|
|
3131
3371
|
* `/apps/{app_id_or_name}` against everything the token can see, team-owned or
|
|
3132
3372
|
* not.
|
|
3133
3373
|
*/
|
|
3134
|
-
const herokuConnectionConfigSchema = z.object({ provider: z.literal("heroku") });
|
|
3374
|
+
const herokuConnectionConfigSchema = z$1.object({ provider: z$1.literal("heroku") });
|
|
3135
3375
|
/**
|
|
3136
3376
|
* Netlify team scope — the one thing a Netlify token cannot tell us itself.
|
|
3137
3377
|
*
|
|
@@ -3146,10 +3386,10 @@ const herokuConnectionConfigSchema = z.object({ provider: z.literal("heroku") })
|
|
|
3146
3386
|
* can find without an API call — it is in the dashboard URL
|
|
3147
3387
|
* (`app.netlify.com/teams/<slug>`) and under Team settings → General.
|
|
3148
3388
|
*/
|
|
3149
|
-
const netlifyConnectionConfigSchema = z.object({
|
|
3150
|
-
provider: z.literal("netlify"),
|
|
3389
|
+
const netlifyConnectionConfigSchema = z$1.object({
|
|
3390
|
+
provider: z$1.literal("netlify"),
|
|
3151
3391
|
/** Netlify team slug (`acme`) or account id — `{account_id}` accepts either. */
|
|
3152
|
-
accountId: z.string().trim().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "must be a Netlify team slug or account ID — no slashes or spaces")
|
|
3392
|
+
accountId: z$1.string().trim().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "must be a Netlify team slug or account ID — no slashes or spaces")
|
|
3153
3393
|
});
|
|
3154
3394
|
/**
|
|
3155
3395
|
* Bunnyshell account scope — empty, as Fly's, Heroku's, and Northflank's are.
|
|
@@ -3163,7 +3403,7 @@ const netlifyConnectionConfigSchema = z.object({
|
|
|
3163
3403
|
* id is the whole address. The API offers an `organization` filter, but it
|
|
3164
3404
|
* narrows a listing — it is not part of an address.
|
|
3165
3405
|
*/
|
|
3166
|
-
const bunnyshellConnectionConfigSchema = z.object({ provider: z.literal("bunnyshell") });
|
|
3406
|
+
const bunnyshellConnectionConfigSchema = z$1.object({ provider: z$1.literal("bunnyshell") });
|
|
3167
3407
|
/**
|
|
3168
3408
|
* GitHub account scope — empty for github.com, which is the whole point.
|
|
3169
3409
|
*
|
|
@@ -3177,13 +3417,13 @@ const bunnyshellConnectionConfigSchema = z.object({ provider: z.literal("bunnysh
|
|
|
3177
3417
|
* with a different plan behind it). Set only for a self-hosted GHES appliance,
|
|
3178
3418
|
* where the REST API is at `https://<host>/api/v3`.
|
|
3179
3419
|
*/
|
|
3180
|
-
const githubActionsConnectionConfigSchema = z.object({
|
|
3181
|
-
provider: z.literal("github-actions"),
|
|
3420
|
+
const githubActionsConnectionConfigSchema = z$1.object({
|
|
3421
|
+
provider: z$1.literal("github-actions"),
|
|
3182
3422
|
/**
|
|
3183
3423
|
* GitHub Enterprise Server API root, e.g. `https://github.acme.com/api/v3`.
|
|
3184
3424
|
* Omit for github.com. Must be `https:` — this URL carries the token.
|
|
3185
3425
|
*/
|
|
3186
|
-
baseUrl: z.string().trim().max(300).refine((value) => {
|
|
3426
|
+
baseUrl: z$1.string().trim().max(300).refine((value) => {
|
|
3187
3427
|
let parsed;
|
|
3188
3428
|
try {
|
|
3189
3429
|
parsed = new URL(value);
|
|
@@ -3203,7 +3443,7 @@ const githubActionsConnectionConfigSchema = z.object({
|
|
|
3203
3443
|
* Manager URL is built from this string, and a typo would otherwise surface as
|
|
3204
3444
|
* a 403 from Google hours later inside an alarm, with nobody watching.
|
|
3205
3445
|
*/
|
|
3206
|
-
const gcpProjectSchema = z.string().trim().refine((value) => /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(value) || /^\d{1,20}$/.test(value), "must be a Google Cloud project ID (e.g. acme-prod) or project number");
|
|
3446
|
+
const gcpProjectSchema = z$1.string().trim().refine((value) => /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(value) || /^\d{1,20}$/.test(value), "must be a Google Cloud project ID (e.g. acme-prod) or project number");
|
|
3207
3447
|
/**
|
|
3208
3448
|
* Google Cloud project scope — which project's Secret Manager to write.
|
|
3209
3449
|
*
|
|
@@ -3222,8 +3462,8 @@ const gcpProjectSchema = z.string().trim().refine((value) => /^[a-z][a-z0-9-]{4,
|
|
|
3222
3462
|
* regional secrets live behind. Data residency is expressed instead through the
|
|
3223
3463
|
* destination's user-managed replication.
|
|
3224
3464
|
*/
|
|
3225
|
-
const gcpSecretManagerConnectionConfigSchema = z.object({
|
|
3226
|
-
provider: z.literal("gcp-secret-manager"),
|
|
3465
|
+
const gcpSecretManagerConnectionConfigSchema = z$1.object({
|
|
3466
|
+
provider: z$1.literal("gcp-secret-manager"),
|
|
3227
3467
|
/** Project ID (`acme-prod`) or project number. */
|
|
3228
3468
|
projectId: gcpProjectSchema
|
|
3229
3469
|
});
|
|
@@ -3263,16 +3503,16 @@ const LANGGRAPH_PLATFORM_REGIONS = [
|
|
|
3263
3503
|
* Vercel's `teamId` sets — so it is passed through as `X-Tenant-Id` whenever
|
|
3264
3504
|
* it is present.
|
|
3265
3505
|
*/
|
|
3266
|
-
const langgraphPlatformConnectionConfigSchema = z.object({
|
|
3267
|
-
provider: z.literal("langgraph-platform"),
|
|
3506
|
+
const langgraphPlatformConnectionConfigSchema = z$1.object({
|
|
3507
|
+
provider: z$1.literal("langgraph-platform"),
|
|
3268
3508
|
/** Control-plane region. Omit for `us`. Mutually exclusive with `baseUrl`. */
|
|
3269
|
-
region: z.enum(LANGGRAPH_PLATFORM_REGIONS).optional(),
|
|
3509
|
+
region: z$1.enum(LANGGRAPH_PLATFORM_REGIONS).optional(),
|
|
3270
3510
|
/**
|
|
3271
3511
|
* Self-hosted LangSmith control-plane root, e.g.
|
|
3272
3512
|
* `https://langsmith.acme.com/api-host`. Omit for LangChain's own hosts.
|
|
3273
3513
|
* Must be `https:` — this URL carries the API key.
|
|
3274
3514
|
*/
|
|
3275
|
-
baseUrl: z.string().trim().max(300).refine((value) => {
|
|
3515
|
+
baseUrl: z$1.string().trim().max(300).refine((value) => {
|
|
3276
3516
|
let parsed;
|
|
3277
3517
|
try {
|
|
3278
3518
|
parsed = new URL(value);
|
|
@@ -3282,7 +3522,7 @@ const langgraphPlatformConnectionConfigSchema = z.object({
|
|
|
3282
3522
|
return parsed.protocol === "https:" && !parsed.username && !parsed.password;
|
|
3283
3523
|
}, "must be an https:// URL — the self-hosted control-plane root, e.g. https://langsmith.acme.com/api-host").optional(),
|
|
3284
3524
|
/** LangSmith workspace (tenant) UUID, sent as `X-Tenant-Id`. */
|
|
3285
|
-
tenantId: z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a LangSmith workspace UUID").optional()
|
|
3525
|
+
tenantId: z$1.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a LangSmith workspace UUID").optional()
|
|
3286
3526
|
}).refine((c) => !(c.baseUrl !== void 0 && c.region !== void 0), {
|
|
3287
3527
|
message: "set region for a LangChain-hosted account or baseUrl for a self-hosted one, not both",
|
|
3288
3528
|
path: ["baseUrl"]
|
|
@@ -3309,7 +3549,7 @@ const AZURE_CLOUDS = [
|
|
|
3309
3549
|
* keeps one rule — a tenant's GUID is on the same admin-center page as the
|
|
3310
3550
|
* client id it pairs with, so nothing is harder to find.
|
|
3311
3551
|
*/
|
|
3312
|
-
const azureGuidSchema = z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a GUID, as the Entra admin center shows it");
|
|
3552
|
+
const azureGuidSchema = z$1.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a GUID, as the Entra admin center shows it");
|
|
3313
3553
|
/**
|
|
3314
3554
|
* Azure account scope: which directory to authenticate against, as which app.
|
|
3315
3555
|
*
|
|
@@ -3326,14 +3566,14 @@ const azureGuidSchema = z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-
|
|
|
3326
3566
|
* has nowhere to keep, and Entra caps a client secret at 24 months, which is a
|
|
3327
3567
|
* rotation the connection's `lastError` will make loud.
|
|
3328
3568
|
*/
|
|
3329
|
-
const azureKeyVaultConnectionConfigSchema = z.object({
|
|
3330
|
-
provider: z.literal("azure-key-vault"),
|
|
3569
|
+
const azureKeyVaultConnectionConfigSchema = z$1.object({
|
|
3570
|
+
provider: z$1.literal("azure-key-vault"),
|
|
3331
3571
|
/** Entra directory (tenant) ID. */
|
|
3332
3572
|
tenantId: azureGuidSchema,
|
|
3333
3573
|
/** Application (client) ID of the service principal seekrit signs in as. */
|
|
3334
3574
|
clientId: azureGuidSchema,
|
|
3335
3575
|
/** Which Azure cloud the tenant and its vaults live in. */
|
|
3336
|
-
cloud: z.enum(AZURE_CLOUDS).default("public")
|
|
3576
|
+
cloud: z$1.enum(AZURE_CLOUDS).default("public")
|
|
3337
3577
|
});
|
|
3338
3578
|
/**
|
|
3339
3579
|
* Hugging Face account scope — empty, as Render's and Fly's are.
|
|
@@ -3349,9 +3589,9 @@ const azureKeyVaultConnectionConfigSchema = z.object({
|
|
|
3349
3589
|
* not something a mirror is expected to accept a write on. Adding the field
|
|
3350
3590
|
* would invite pointing a connection at a host that silently swallows secrets.
|
|
3351
3591
|
*/
|
|
3352
|
-
const huggingfaceSpacesConnectionConfigSchema = z.object({ provider: z.literal("huggingface-spaces") });
|
|
3592
|
+
const huggingfaceSpacesConnectionConfigSchema = z$1.object({ provider: z$1.literal("huggingface-spaces") });
|
|
3353
3593
|
/** The wrapped credential is an OpenAI project API key with vault read/write access. */
|
|
3354
|
-
const openaiVaultConnectionConfigSchema = z.object({ provider: z.literal("openai-vault") });
|
|
3594
|
+
const openaiVaultConnectionConfigSchema = z$1.object({ provider: z$1.literal("openai-vault") });
|
|
3355
3595
|
/**
|
|
3356
3596
|
* Claude Managed Agents account scope — empty, for Hugging Face's reason.
|
|
3357
3597
|
*
|
|
@@ -3368,8 +3608,8 @@ const openaiVaultConnectionConfigSchema = z.object({ provider: z.literal("openai
|
|
|
3368
3608
|
* credential shape and a different connector (one that would reuse
|
|
3369
3609
|
* `lib/leases/aws-sigv4.ts`), not a different host for this one.
|
|
3370
3610
|
*/
|
|
3371
|
-
const claudeManagedAgentsConnectionConfigSchema = z.object({ provider: z.literal("claude-managed-agents") });
|
|
3372
|
-
const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
|
|
3611
|
+
const claudeManagedAgentsConnectionConfigSchema = z$1.object({ provider: z$1.literal("claude-managed-agents") });
|
|
3612
|
+
const syncConnectionConfigSchema = z$1.discriminatedUnion("provider", [
|
|
3373
3613
|
vercelConnectionConfigSchema,
|
|
3374
3614
|
cloudflareWorkersConnectionConfigSchema,
|
|
3375
3615
|
cloudflarePagesConnectionConfigSchema,
|
|
@@ -3398,17 +3638,17 @@ const VERCEL_TARGETS = [
|
|
|
3398
3638
|
"preview",
|
|
3399
3639
|
"development"
|
|
3400
3640
|
];
|
|
3401
|
-
const vercelDestinationSchema = z.object({
|
|
3402
|
-
provider: z.literal("vercel"),
|
|
3641
|
+
const vercelDestinationSchema = z$1.object({
|
|
3642
|
+
provider: z$1.literal("vercel"),
|
|
3403
3643
|
/** Vercel project id (`prj_…`) or project name. */
|
|
3404
|
-
projectId: z.string().trim().min(1).max(128),
|
|
3644
|
+
projectId: z$1.string().trim().min(1).max(128),
|
|
3405
3645
|
/** Which deployment targets receive these values. At least one. */
|
|
3406
|
-
targets: z.array(z.enum(VERCEL_TARGETS)).min(1),
|
|
3646
|
+
targets: z$1.array(z$1.enum(VERCEL_TARGETS)).min(1),
|
|
3407
3647
|
/**
|
|
3408
3648
|
* Restrict `preview` writes to one git branch. Vercel only honors this when
|
|
3409
3649
|
* `targets` includes `preview`; ignored otherwise.
|
|
3410
3650
|
*/
|
|
3411
|
-
gitBranch: z.string().trim().min(1).max(255).optional()
|
|
3651
|
+
gitBranch: z$1.string().trim().min(1).max(255).optional()
|
|
3412
3652
|
});
|
|
3413
3653
|
/**
|
|
3414
3654
|
* The Worker whose secrets a binding owns. Wrangler *environments* are not a
|
|
@@ -3416,19 +3656,19 @@ const vercelDestinationSchema = z.object({
|
|
|
3416
3656
|
* `my-api` with `--env staging` creates a Worker literally named
|
|
3417
3657
|
* `my-api-staging`, so pointing at an environment means naming that script.
|
|
3418
3658
|
*/
|
|
3419
|
-
const cloudflareWorkersDestinationSchema = z.object({
|
|
3420
|
-
provider: z.literal("cloudflare-workers"),
|
|
3659
|
+
const cloudflareWorkersDestinationSchema = z$1.object({
|
|
3660
|
+
provider: z$1.literal("cloudflare-workers"),
|
|
3421
3661
|
/** Worker script name, as shown in the dashboard (`my-api`). */
|
|
3422
|
-
scriptName: z.string().trim().min(1).max(63).regex(/^[A-Za-z0-9_][A-Za-z0-9_-]*$/, "must be a Worker script name")
|
|
3662
|
+
scriptName: z$1.string().trim().min(1).max(63).regex(/^[A-Za-z0-9_][A-Za-z0-9_-]*$/, "must be a Worker script name")
|
|
3423
3663
|
});
|
|
3424
3664
|
/** The two deployment configs a Pages project keeps env vars under. */
|
|
3425
3665
|
const CLOUDFLARE_PAGES_ENVIRONMENTS = ["production", "preview"];
|
|
3426
|
-
const cloudflarePagesDestinationSchema = z.object({
|
|
3427
|
-
provider: z.literal("cloudflare-pages"),
|
|
3666
|
+
const cloudflarePagesDestinationSchema = z$1.object({
|
|
3667
|
+
provider: z$1.literal("cloudflare-pages"),
|
|
3428
3668
|
/** Pages project name (`my-site`) — Pages has no separate project id. */
|
|
3429
|
-
projectName: z.string().trim().min(1).max(58).regex(/^[A-Za-z0-9][A-Za-z0-9-]*$/, "must be a Pages project name"),
|
|
3669
|
+
projectName: z$1.string().trim().min(1).max(58).regex(/^[A-Za-z0-9][A-Za-z0-9-]*$/, "must be a Pages project name"),
|
|
3430
3670
|
/** Which deployment configs receive these values. At least one. */
|
|
3431
|
-
environments: z.array(z.enum(CLOUDFLARE_PAGES_ENVIRONMENTS)).min(1)
|
|
3671
|
+
environments: z$1.array(z$1.enum(CLOUDFLARE_PAGES_ENVIRONMENTS)).min(1)
|
|
3432
3672
|
});
|
|
3433
3673
|
/**
|
|
3434
3674
|
* Scopes a Secrets Store secret may be used by. Cloudflare requires at least
|
|
@@ -3442,12 +3682,12 @@ const CLOUDFLARE_SECRETS_STORE_SCOPES = [
|
|
|
3442
3682
|
"containers",
|
|
3443
3683
|
"websearch"
|
|
3444
3684
|
];
|
|
3445
|
-
const cloudflareSecretsStoreDestinationSchema = z.object({
|
|
3446
|
-
provider: z.literal("cloudflare-secrets-store"),
|
|
3685
|
+
const cloudflareSecretsStoreDestinationSchema = z$1.object({
|
|
3686
|
+
provider: z$1.literal("cloudflare-secrets-store"),
|
|
3447
3687
|
/** Store id (32 hex). An account has exactly one store today. */
|
|
3448
|
-
storeId: z.string().trim().regex(/^[0-9a-f]{32}$/, "must be a 32-character Secrets Store ID (lowercase hex)"),
|
|
3688
|
+
storeId: z$1.string().trim().regex(/^[0-9a-f]{32}$/, "must be a 32-character Secrets Store ID (lowercase hex)"),
|
|
3449
3689
|
/** Scopes applied to secrets this binding creates. At least one. */
|
|
3450
|
-
scopes: z.array(z.enum(CLOUDFLARE_SECRETS_STORE_SCOPES)).min(1)
|
|
3690
|
+
scopes: z$1.array(z$1.enum(CLOUDFLARE_SECRETS_STORE_SCOPES)).min(1)
|
|
3451
3691
|
});
|
|
3452
3692
|
/**
|
|
3453
3693
|
* Where inside Railway a binding writes.
|
|
@@ -3462,8 +3702,8 @@ const cloudflareSecretsStoreDestinationSchema = z.object({
|
|
|
3462
3702
|
* genuinely different destination from any one service's variables, so it is an
|
|
3463
3703
|
* absent field rather than a sentinel.
|
|
3464
3704
|
*/
|
|
3465
|
-
const railwayDestinationSchema = z.object({
|
|
3466
|
-
provider: z.literal("railway"),
|
|
3705
|
+
const railwayDestinationSchema = z$1.object({
|
|
3706
|
+
provider: z$1.literal("railway"),
|
|
3467
3707
|
/** Railway project id (a UUID, from the project's Settings page or URL). */
|
|
3468
3708
|
projectId: railwayIdSchema,
|
|
3469
3709
|
/** Railway environment id (a UUID) — the deployment environment to write. */
|
|
@@ -3479,7 +3719,7 @@ const railwayDestinationSchema = z.object({
|
|
|
3479
3719
|
* gated behind a release process and a secrets push must not start one; the
|
|
3480
3720
|
* values then sit staged until the next deploy.
|
|
3481
3721
|
*/
|
|
3482
|
-
skipDeploys: z.boolean().optional()
|
|
3722
|
+
skipDeploys: z$1.boolean().optional()
|
|
3483
3723
|
});
|
|
3484
3724
|
/**
|
|
3485
3725
|
* A customer-managed KMS key to encrypt with, as a key id, ARN, or alias
|
|
@@ -3490,7 +3730,7 @@ const railwayDestinationSchema = z.object({
|
|
|
3490
3730
|
* cross-account ARNs, and rejecting a valid one here would be worse than
|
|
3491
3731
|
* letting KMS give its own (very clear) error.
|
|
3492
3732
|
*/
|
|
3493
|
-
const awsKmsKeyIdSchema = z.string().trim().min(1).max(2048);
|
|
3733
|
+
const awsKmsKeyIdSchema = z$1.string().trim().min(1).max(2048);
|
|
3494
3734
|
/**
|
|
3495
3735
|
* How a binding lays its secrets out in Secrets Manager.
|
|
3496
3736
|
*
|
|
@@ -3510,18 +3750,18 @@ const AWS_SECRETS_MANAGER_LAYOUTS = ["secret-per-name", "json-bundle"];
|
|
|
3510
3750
|
* *variable name* (`[A-Za-z0-9_]`, no slashes), while this produces a
|
|
3511
3751
|
* *namespace* — `prod/storefront/` — and slashes are the whole point of it.
|
|
3512
3752
|
*/
|
|
3513
|
-
const awsSecretsManagerDestinationSchema = z.object({
|
|
3514
|
-
provider: z.literal("aws-secrets-manager"),
|
|
3515
|
-
layout: z.enum(AWS_SECRETS_MANAGER_LAYOUTS).default("secret-per-name"),
|
|
3753
|
+
const awsSecretsManagerDestinationSchema = z$1.object({
|
|
3754
|
+
provider: z$1.literal("aws-secrets-manager"),
|
|
3755
|
+
layout: z$1.enum(AWS_SECRETS_MANAGER_LAYOUTS).default("secret-per-name"),
|
|
3516
3756
|
/**
|
|
3517
3757
|
* `secret-per-name` only: prepended to every secret's name, e.g.
|
|
3518
3758
|
* `prod/storefront/`. Optional, but strongly advised in an account that
|
|
3519
3759
|
* holds anything else — without it a binding writes at the root of a
|
|
3520
3760
|
* namespace it does not own.
|
|
3521
3761
|
*/
|
|
3522
|
-
pathPrefix: z.string().trim().max(400).regex(/^[A-Za-z0-9/_+=.@-]*$/, "may contain letters, digits, and / _ + = . @ -").optional(),
|
|
3762
|
+
pathPrefix: z$1.string().trim().max(400).regex(/^[A-Za-z0-9/_+=.@-]*$/, "may contain letters, digits, and / _ + = . @ -").optional(),
|
|
3523
3763
|
/** `json-bundle` only: the one secret that holds every value, e.g. `prod/storefront/env`. */
|
|
3524
|
-
secretName: z.string().trim().min(1).max(512).regex(/^[A-Za-z0-9/_+=.@-]+$/, "may contain letters, digits, and / _ + = . @ -").optional(),
|
|
3764
|
+
secretName: z$1.string().trim().min(1).max(512).regex(/^[A-Za-z0-9/_+=.@-]+$/, "may contain letters, digits, and / _ + = . @ -").optional(),
|
|
3525
3765
|
kmsKeyId: awsKmsKeyIdSchema.optional()
|
|
3526
3766
|
}).refine((d) => d.layout !== "json-bundle" || d.secretName !== void 0, {
|
|
3527
3767
|
message: "a json-bundle destination needs the name of the secret to write",
|
|
@@ -3544,17 +3784,17 @@ const AWS_PARAMETER_TIERS = [
|
|
|
3544
3784
|
* and trailing slashes are required so the binding's names concatenate
|
|
3545
3785
|
* unambiguously — `/prod/storefront/` + `DB_URL`.
|
|
3546
3786
|
*/
|
|
3547
|
-
const awsParameterStoreDestinationSchema = z.object({
|
|
3548
|
-
provider: z.literal("aws-parameter-store"),
|
|
3787
|
+
const awsParameterStoreDestinationSchema = z$1.object({
|
|
3788
|
+
provider: z$1.literal("aws-parameter-store"),
|
|
3549
3789
|
/** Must start and end with `/`. `aws`/`ssm` are reserved by AWS as the first segment. */
|
|
3550
|
-
path: z.string().trim().max(1011).regex(/^\/([A-Za-z0-9_.-]+\/)*$/, "must be a parameter path like /prod/storefront/"),
|
|
3551
|
-
type: z.enum(AWS_PARAMETER_TYPES).default("SecureString"),
|
|
3790
|
+
path: z$1.string().trim().max(1011).regex(/^\/([A-Za-z0-9_.-]+\/)*$/, "must be a parameter path like /prod/storefront/"),
|
|
3791
|
+
type: z$1.enum(AWS_PARAMETER_TYPES).default("SecureString"),
|
|
3552
3792
|
/**
|
|
3553
3793
|
* Standard caps a value at 4KB and costs nothing; Advanced raises that to 8KB
|
|
3554
3794
|
* and is billed per parameter per month. `Intelligent-Tiering` lets AWS pick,
|
|
3555
3795
|
* upgrading only the parameters that need it.
|
|
3556
3796
|
*/
|
|
3557
|
-
tier: z.enum(AWS_PARAMETER_TIERS).default("Standard"),
|
|
3797
|
+
tier: z$1.enum(AWS_PARAMETER_TIERS).default("Standard"),
|
|
3558
3798
|
kmsKeyId: awsKmsKeyIdSchema.optional()
|
|
3559
3799
|
});
|
|
3560
3800
|
/**
|
|
@@ -3569,12 +3809,12 @@ const awsParameterStoreDestinationSchema = z.object({
|
|
|
3569
3809
|
* the day Render introduces a new resource prefix, which is not our call to
|
|
3570
3810
|
* make.
|
|
3571
3811
|
*/
|
|
3572
|
-
const renderServiceIdSchema = z.string().trim().regex(/^(?!evg-)[A-Za-z0-9_-]{1,64}$/, "must be a Render service ID (`srv-…` or `crn-…`), not an environment group");
|
|
3573
|
-
const renderEnvGroupIdSchema = z.string().trim().regex(/^(?!srv-|crn-)[A-Za-z0-9_-]{1,64}$/, "must be a Render environment group ID (`evg-…`), not a service");
|
|
3812
|
+
const renderServiceIdSchema = z$1.string().trim().regex(/^(?!evg-)[A-Za-z0-9_-]{1,64}$/, "must be a Render service ID (`srv-…` or `crn-…`), not an environment group");
|
|
3813
|
+
const renderEnvGroupIdSchema = z$1.string().trim().regex(/^(?!srv-|crn-)[A-Za-z0-9_-]{1,64}$/, "must be a Render environment group ID (`evg-…`), not a service");
|
|
3574
3814
|
/** Environment variables set directly on one service. */
|
|
3575
|
-
const renderServiceDestinationSchema = z.object({
|
|
3576
|
-
provider: z.literal("render"),
|
|
3577
|
-
kind: z.literal("service"),
|
|
3815
|
+
const renderServiceDestinationSchema = z$1.object({
|
|
3816
|
+
provider: z$1.literal("render"),
|
|
3817
|
+
kind: z$1.literal("service"),
|
|
3578
3818
|
/** Service id (`srv-…`, or `crn-…` for a cron job), from its dashboard URL. */
|
|
3579
3819
|
serviceId: renderServiceIdSchema
|
|
3580
3820
|
});
|
|
@@ -3584,13 +3824,13 @@ const renderServiceDestinationSchema = z.object({
|
|
|
3584
3824
|
* worth thinking about twice: its blast radius is the link list, not one
|
|
3585
3825
|
* service.
|
|
3586
3826
|
*/
|
|
3587
|
-
const renderEnvGroupDestinationSchema = z.object({
|
|
3588
|
-
provider: z.literal("render"),
|
|
3589
|
-
kind: z.literal("env-group"),
|
|
3827
|
+
const renderEnvGroupDestinationSchema = z$1.object({
|
|
3828
|
+
provider: z$1.literal("render"),
|
|
3829
|
+
kind: z$1.literal("env-group"),
|
|
3590
3830
|
/** Environment group id (`evg-…`), from its dashboard URL. */
|
|
3591
3831
|
envGroupId: renderEnvGroupIdSchema
|
|
3592
3832
|
});
|
|
3593
|
-
const renderDestinationSchema = z.discriminatedUnion("kind", [renderServiceDestinationSchema, renderEnvGroupDestinationSchema]);
|
|
3833
|
+
const renderDestinationSchema = z$1.discriminatedUnion("kind", [renderServiceDestinationSchema, renderEnvGroupDestinationSchema]);
|
|
3594
3834
|
/**
|
|
3595
3835
|
* The Fly app whose secret set a binding owns.
|
|
3596
3836
|
*
|
|
@@ -3607,10 +3847,10 @@ const renderDestinationSchema = z.discriminatedUnion("kind", [renderServiceDesti
|
|
|
3607
3847
|
* after the push get them straight away. The connector deliberately restarts
|
|
3608
3848
|
* nothing — see the note in `apps/api/src/lib/sync/connectors/fly.ts`.
|
|
3609
3849
|
*/
|
|
3610
|
-
const flyDestinationSchema = z.object({
|
|
3611
|
-
provider: z.literal("fly"),
|
|
3850
|
+
const flyDestinationSchema = z$1.object({
|
|
3851
|
+
provider: z$1.literal("fly"),
|
|
3612
3852
|
/** Fly app name, as `fly apps list` prints it. */
|
|
3613
|
-
appName: z.string().trim().min(1).max(63).regex(/^[a-z0-9][a-z0-9-]*$/, "must be a Fly app name (lowercase letters, numbers, and dashes)")
|
|
3853
|
+
appName: z$1.string().trim().min(1).max(63).regex(/^[a-z0-9][a-z0-9-]*$/, "must be a Fly app name (lowercase letters, numbers, and dashes)")
|
|
3614
3854
|
});
|
|
3615
3855
|
/**
|
|
3616
3856
|
* Where inside Northflank a binding writes: one **secret group** in one
|
|
@@ -3627,8 +3867,8 @@ const flyDestinationSchema = z.object({
|
|
|
3627
3867
|
* separate environments are separate projects (or separate groups restricted to
|
|
3628
3868
|
* a stage), so the binding's seekrit environment maps to a group, one to one.
|
|
3629
3869
|
*/
|
|
3630
|
-
const northflankDestinationSchema = z.object({
|
|
3631
|
-
provider: z.literal("northflank"),
|
|
3870
|
+
const northflankDestinationSchema = z$1.object({
|
|
3871
|
+
provider: z$1.literal("northflank"),
|
|
3632
3872
|
/** Project id — the slug in the project URL (`default-project`). */
|
|
3633
3873
|
projectId: northflankIdSchema,
|
|
3634
3874
|
/** Secret group id — the slug in the group's URL (`example-secret-group`). */
|
|
@@ -3660,13 +3900,13 @@ const DIGITALOCEAN_ENV_SCOPES = [
|
|
|
3660
3900
|
* from a typo: pasting the app's *name* (`storefront`), which `GET /v2/apps/{id}`
|
|
3661
3901
|
* answers with a flat 404 hours later inside an alarm, with nobody watching.
|
|
3662
3902
|
*/
|
|
3663
|
-
const digitalOceanAppIdSchema = z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a DigitalOcean app ID (the UUID in the app's URL), not its name");
|
|
3903
|
+
const digitalOceanAppIdSchema = z$1.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a DigitalOcean app ID (the UUID in the app's URL), not its name");
|
|
3664
3904
|
/**
|
|
3665
3905
|
* A component name, matching App Platform's own pattern for one. Names are
|
|
3666
3906
|
* unique within an app, which is what makes a name — rather than an index into
|
|
3667
3907
|
* `services` — the stable way to address a component's variables.
|
|
3668
3908
|
*/
|
|
3669
|
-
const digitalOceanComponentNameSchema = z.string().trim().regex(/^[a-z][a-z0-9-]{0,30}[a-z0-9]$/, "must be an App Platform component name (lowercase letters, numbers, and dashes)");
|
|
3909
|
+
const digitalOceanComponentNameSchema = z$1.string().trim().regex(/^[a-z][a-z0-9-]{0,30}[a-z0-9]$/, "must be an App Platform component name (lowercase letters, numbers, and dashes)");
|
|
3670
3910
|
/**
|
|
3671
3911
|
* Where inside a DigitalOcean app a binding writes.
|
|
3672
3912
|
*
|
|
@@ -3693,27 +3933,27 @@ const digitalOceanComponentNameSchema = z.string().trim().regex(/^[a-z][a-z0-9-]
|
|
|
3693
3933
|
* about to write is already there — see
|
|
3694
3934
|
* `apps/api/src/lib/sync/connectors/digitalocean.ts`.
|
|
3695
3935
|
*/
|
|
3696
|
-
const digitalOceanAppDestinationSchema = z.object({
|
|
3697
|
-
provider: z.literal("digitalocean"),
|
|
3698
|
-
kind: z.literal("app"),
|
|
3936
|
+
const digitalOceanAppDestinationSchema = z$1.object({
|
|
3937
|
+
provider: z$1.literal("digitalocean"),
|
|
3938
|
+
kind: z$1.literal("app"),
|
|
3699
3939
|
/** App id — the UUID in `cloud.digitalocean.com/apps/<id>`. */
|
|
3700
3940
|
appId: digitalOceanAppIdSchema,
|
|
3701
|
-
scope: z.enum(DIGITALOCEAN_ENV_SCOPES).default("RUN_TIME")
|
|
3941
|
+
scope: z$1.enum(DIGITALOCEAN_ENV_SCOPES).default("RUN_TIME")
|
|
3702
3942
|
});
|
|
3703
3943
|
/**
|
|
3704
3944
|
* One component's own environment variables. Narrower than the app-level list:
|
|
3705
3945
|
* only this service, worker, job, static site, or function sees them, and a key
|
|
3706
3946
|
* here wins over the same key at app level.
|
|
3707
3947
|
*/
|
|
3708
|
-
const digitalOceanComponentDestinationSchema = z.object({
|
|
3709
|
-
provider: z.literal("digitalocean"),
|
|
3710
|
-
kind: z.literal("component"),
|
|
3948
|
+
const digitalOceanComponentDestinationSchema = z$1.object({
|
|
3949
|
+
provider: z$1.literal("digitalocean"),
|
|
3950
|
+
kind: z$1.literal("component"),
|
|
3711
3951
|
appId: digitalOceanAppIdSchema,
|
|
3712
3952
|
/** Component name, as it appears in the app spec — not its type. */
|
|
3713
3953
|
componentName: digitalOceanComponentNameSchema,
|
|
3714
|
-
scope: z.enum(DIGITALOCEAN_ENV_SCOPES).default("RUN_TIME")
|
|
3954
|
+
scope: z$1.enum(DIGITALOCEAN_ENV_SCOPES).default("RUN_TIME")
|
|
3715
3955
|
});
|
|
3716
|
-
const digitalOceanDestinationSchema = z.discriminatedUnion("kind", [digitalOceanAppDestinationSchema, digitalOceanComponentDestinationSchema]);
|
|
3956
|
+
const digitalOceanDestinationSchema = z$1.discriminatedUnion("kind", [digitalOceanAppDestinationSchema, digitalOceanComponentDestinationSchema]);
|
|
3717
3957
|
/**
|
|
3718
3958
|
* A Heroku app, named the way `/apps/{app_id_or_name}` names one: either the
|
|
3719
3959
|
* app name or its UUID id. Both are accepted because both work, and the id is
|
|
@@ -3726,7 +3966,7 @@ const digitalOceanDestinationSchema = z.discriminatedUnion("kind", [digitalOcean
|
|
|
3726
3966
|
* capitals — into a message at the form rather than a bare 404 from an alarm
|
|
3727
3967
|
* with nobody watching.
|
|
3728
3968
|
*/
|
|
3729
|
-
const herokuAppSchema = z.string().trim().refine((value) => /^[a-z][a-z0-9-]{1,28}[a-z0-9]$/.test(value) || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value), "must be a Heroku app name (lowercase letters, numbers, and dashes) or its UUID");
|
|
3969
|
+
const herokuAppSchema = z$1.string().trim().refine((value) => /^[a-z][a-z0-9-]{1,28}[a-z0-9]$/.test(value) || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value), "must be a Heroku app name (lowercase letters, numbers, and dashes) or its UUID");
|
|
3730
3970
|
/**
|
|
3731
3971
|
* The Heroku app whose config vars a binding owns.
|
|
3732
3972
|
*
|
|
@@ -3740,8 +3980,8 @@ const herokuAppSchema = z.string().trim().refine((value) => /^[a-z][a-z0-9-]{1,2
|
|
|
3740
3980
|
* release and restarts the app's dynos, which is why a run sends exactly one
|
|
3741
3981
|
* request — see the note in `apps/api/src/lib/sync/connectors/heroku.ts`.
|
|
3742
3982
|
*/
|
|
3743
|
-
const herokuDestinationSchema = z.object({
|
|
3744
|
-
provider: z.literal("heroku"),
|
|
3983
|
+
const herokuDestinationSchema = z$1.object({
|
|
3984
|
+
provider: z$1.literal("heroku"),
|
|
3745
3985
|
/** App name as `heroku apps` prints it, or the app's UUID. */
|
|
3746
3986
|
app: herokuAppSchema
|
|
3747
3987
|
});
|
|
@@ -3779,7 +4019,7 @@ const NETLIFY_CONTEXTS = [
|
|
|
3779
4019
|
* by every site in it. Refusing anything but the UUID keeps a slip from turning
|
|
3780
4020
|
* into a much wider blast radius than the operator asked for.
|
|
3781
4021
|
*/
|
|
3782
|
-
const netlifySiteIdSchema = z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be the site's API ID (a UUID), not its name or URL");
|
|
4022
|
+
const netlifySiteIdSchema = z$1.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be the site's API ID (a UUID), not its name or URL");
|
|
3783
4023
|
/**
|
|
3784
4024
|
* The Netlify site, and which of its deploy contexts a binding owns.
|
|
3785
4025
|
*
|
|
@@ -3801,16 +4041,16 @@ const netlifySiteIdSchema = z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0
|
|
|
3801
4041
|
* existing variable, or removed from one ever — and it needs a plan that
|
|
3802
4042
|
* includes Secrets Controller.
|
|
3803
4043
|
*/
|
|
3804
|
-
const netlifyDestinationSchema = z.object({
|
|
3805
|
-
provider: z.literal("netlify"),
|
|
4044
|
+
const netlifyDestinationSchema = z$1.object({
|
|
4045
|
+
provider: z$1.literal("netlify"),
|
|
3806
4046
|
/** Site API ID (a UUID), from Project configuration → General. */
|
|
3807
4047
|
siteId: netlifySiteIdSchema,
|
|
3808
4048
|
/** Which deploy contexts receive these values. At least one. */
|
|
3809
|
-
contexts: z.array(z.enum(NETLIFY_CONTEXTS)).min(1),
|
|
4049
|
+
contexts: z$1.array(z$1.enum(NETLIFY_CONTEXTS)).min(1),
|
|
3810
4050
|
/** Branch name, required when `contexts` includes `branch`; ignored otherwise. */
|
|
3811
|
-
branch: z.string().trim().min(1).max(255).optional(),
|
|
4051
|
+
branch: z$1.string().trim().min(1).max(255).optional(),
|
|
3812
4052
|
/** Create variables as Netlify secrets (default true). */
|
|
3813
|
-
secret: z.boolean().optional()
|
|
4053
|
+
secret: z$1.boolean().optional()
|
|
3814
4054
|
}).refine((d) => !d.contexts.includes("branch") || d.branch !== void 0, {
|
|
3815
4055
|
message: "a branch context needs the branch name it applies to",
|
|
3816
4056
|
path: ["branch"]
|
|
@@ -3832,7 +4072,7 @@ const netlifyDestinationSchema = z.object({
|
|
|
3832
4072
|
* before it touches it, so a filter that failed to bite cannot turn into an
|
|
3833
4073
|
* edit of a neighbouring environment's variables.
|
|
3834
4074
|
*/
|
|
3835
|
-
const bunnyshellIdSchema = z.string().trim().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "must be a Bunnyshell ID — no slashes or spaces");
|
|
4075
|
+
const bunnyshellIdSchema = z$1.string().trim().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "must be a Bunnyshell ID — no slashes or spaces");
|
|
3836
4076
|
/**
|
|
3837
4077
|
* Variables on one Bunnyshell **environment** — the set every component in it
|
|
3838
4078
|
* inherits, and the closest match to a seekrit environment.
|
|
@@ -3840,13 +4080,13 @@ const bunnyshellIdSchema = z.string().trim().min(1).max(64).regex(/^[A-Za-z0-9][
|
|
|
3840
4080
|
* This is the destination for an environment that already exists and stays
|
|
3841
4081
|
* around: a primary environment, or a long-lived ephemeral one.
|
|
3842
4082
|
*/
|
|
3843
|
-
const bunnyshellEnvironmentDestinationSchema = z.object({
|
|
3844
|
-
provider: z.literal("bunnyshell"),
|
|
3845
|
-
kind: z.literal("environment"),
|
|
4083
|
+
const bunnyshellEnvironmentDestinationSchema = z$1.object({
|
|
4084
|
+
provider: z$1.literal("bunnyshell"),
|
|
4085
|
+
kind: z$1.literal("environment"),
|
|
3846
4086
|
/** Environment ID, as `bns environments list` prints it. */
|
|
3847
4087
|
environmentId: bunnyshellIdSchema,
|
|
3848
4088
|
/** Mark what seekrit creates as a Bunnyshell secret (default true). */
|
|
3849
|
-
secret: z.boolean().optional()
|
|
4089
|
+
secret: z$1.boolean().optional()
|
|
3850
4090
|
});
|
|
3851
4091
|
/**
|
|
3852
4092
|
* Variables on a Bunnyshell **project** — inherited by every environment
|
|
@@ -3863,13 +4103,13 @@ const bunnyshellEnvironmentDestinationSchema = z.object({
|
|
|
3863
4103
|
* overridden at its own scope — so a project binding does not fight an
|
|
3864
4104
|
* environment binding pointed at the same name, it loses to it.
|
|
3865
4105
|
*/
|
|
3866
|
-
const bunnyshellProjectDestinationSchema = z.object({
|
|
3867
|
-
provider: z.literal("bunnyshell"),
|
|
3868
|
-
kind: z.literal("project"),
|
|
4106
|
+
const bunnyshellProjectDestinationSchema = z$1.object({
|
|
4107
|
+
provider: z$1.literal("bunnyshell"),
|
|
4108
|
+
kind: z$1.literal("project"),
|
|
3869
4109
|
/** Project ID, as `bns projects list` prints it. */
|
|
3870
4110
|
projectId: bunnyshellIdSchema,
|
|
3871
4111
|
/** Mark what seekrit creates as a Bunnyshell secret (default true). */
|
|
3872
|
-
secret: z.boolean().optional()
|
|
4112
|
+
secret: z$1.boolean().optional()
|
|
3873
4113
|
});
|
|
3874
4114
|
/**
|
|
3875
4115
|
* Where in Bunnyshell a binding writes.
|
|
@@ -3889,7 +4129,7 @@ const bunnyshellProjectDestinationSchema = z.object({
|
|
|
3889
4129
|
* variables seekrit **creates**: an update never sends the flag, so a variable
|
|
3890
4130
|
* an operator deliberately un-secreted stays that way.
|
|
3891
4131
|
*/
|
|
3892
|
-
const bunnyshellDestinationSchema = z.discriminatedUnion("kind", [bunnyshellEnvironmentDestinationSchema, bunnyshellProjectDestinationSchema]);
|
|
4132
|
+
const bunnyshellDestinationSchema = z$1.discriminatedUnion("kind", [bunnyshellEnvironmentDestinationSchema, bunnyshellProjectDestinationSchema]);
|
|
3893
4133
|
/**
|
|
3894
4134
|
* Which repositories in an organization can read an org-level secret.
|
|
3895
4135
|
*
|
|
@@ -3912,14 +4152,14 @@ const GITHUB_ACTIONS_VISIBILITIES = [
|
|
|
3912
4152
|
* owner field — fails at the form rather than as a 404 from an alarm with nobody
|
|
3913
4153
|
* watching.
|
|
3914
4154
|
*/
|
|
3915
|
-
const githubOwnerSchema = z.string().trim().min(1).max(39).regex(/^[A-Za-z0-9](?:-?[A-Za-z0-9])*$/, "must be a GitHub user or organization login — not a URL or an owner/repo pair");
|
|
4155
|
+
const githubOwnerSchema = z$1.string().trim().min(1).max(39).regex(/^[A-Za-z0-9](?:-?[A-Za-z0-9])*$/, "must be a GitHub user or organization login — not a URL or an owner/repo pair");
|
|
3916
4156
|
/**
|
|
3917
4157
|
* A repository name. GitHub's rules are looser than an owner's: letters,
|
|
3918
4158
|
* numbers, hyphens, underscores, and periods, up to 100 characters. `.` and `..`
|
|
3919
4159
|
* are refused outright — they would traverse the API path rather than name a
|
|
3920
4160
|
* repository.
|
|
3921
4161
|
*/
|
|
3922
|
-
const githubRepoSchema = z.string().trim().min(1).max(100).regex(/^[A-Za-z0-9._-]+$/, "must be a repository name alone (`api`), not `owner/repo` or a URL").refine((value) => value !== "." && value !== "..", "is not a repository name");
|
|
4162
|
+
const githubRepoSchema = z$1.string().trim().min(1).max(100).regex(/^[A-Za-z0-9._-]+$/, "must be a repository name alone (`api`), not `owner/repo` or a URL").refine((value) => value !== "." && value !== "..", "is not a repository name");
|
|
3923
4163
|
/**
|
|
3924
4164
|
* A deployment environment name.
|
|
3925
4165
|
*
|
|
@@ -3928,7 +4168,7 @@ const githubRepoSchema = z.string().trim().min(1).max(100).regex(/^[A-Za-z0-9._-
|
|
|
3928
4168
|
* break the request are refused — an empty name, and the path separators that
|
|
3929
4169
|
* would let a name escape its URL segment. Everything else is GitHub's to reject.
|
|
3930
4170
|
*/
|
|
3931
|
-
const githubEnvironmentSchema = z.string().trim().min(1).max(255).refine((value) => !value.includes("/") && !value.includes("\\"), "cannot contain a slash — that is a path separator, not part of an environment name");
|
|
4171
|
+
const githubEnvironmentSchema = z$1.string().trim().min(1).max(255).refine((value) => !value.includes("/") && !value.includes("\\"), "cannot contain a slash — that is a path separator, not part of an environment name");
|
|
3932
4172
|
/**
|
|
3933
4173
|
* One repository's Actions secrets.
|
|
3934
4174
|
*
|
|
@@ -3937,9 +4177,9 @@ const githubEnvironmentSchema = z.string().trim().min(1).max(255).refine((value)
|
|
|
3937
4177
|
* choice this connector makes — but it is the reason `environment` exists below,
|
|
3938
4178
|
* and the reason to prefer it for anything that touches production.
|
|
3939
4179
|
*/
|
|
3940
|
-
const githubActionsRepoDestinationSchema = z.object({
|
|
3941
|
-
provider: z.literal("github-actions"),
|
|
3942
|
-
kind: z.literal("repo"),
|
|
4180
|
+
const githubActionsRepoDestinationSchema = z$1.object({
|
|
4181
|
+
provider: z$1.literal("github-actions"),
|
|
4182
|
+
kind: z$1.literal("repo"),
|
|
3943
4183
|
/** Repository owner — a user or organization login. */
|
|
3944
4184
|
owner: githubOwnerSchema,
|
|
3945
4185
|
/** Repository name, without the owner. */
|
|
@@ -3959,9 +4199,9 @@ const githubActionsRepoDestinationSchema = z.object({
|
|
|
3959
4199
|
* because a name was misspelled would quietly remove the protection the operator
|
|
3960
4200
|
* was relying on.
|
|
3961
4201
|
*/
|
|
3962
|
-
const githubActionsEnvironmentDestinationSchema = z.object({
|
|
3963
|
-
provider: z.literal("github-actions"),
|
|
3964
|
-
kind: z.literal("environment"),
|
|
4202
|
+
const githubActionsEnvironmentDestinationSchema = z$1.object({
|
|
4203
|
+
provider: z$1.literal("github-actions"),
|
|
4204
|
+
kind: z$1.literal("environment"),
|
|
3965
4205
|
owner: githubOwnerSchema,
|
|
3966
4206
|
repo: githubRepoSchema,
|
|
3967
4207
|
/** Environment name, exactly as the repository's Settings → Environments shows it. */
|
|
@@ -3981,15 +4221,15 @@ const githubActionsEnvironmentDestinationSchema = z.object({
|
|
|
3981
4221
|
* resolution would mean this connector picking which repository an ambiguous
|
|
3982
4222
|
* name meant, and getting that wrong widens a secret's reach silently.
|
|
3983
4223
|
*/
|
|
3984
|
-
const githubActionsOrgDestinationSchema = z.object({
|
|
3985
|
-
provider: z.literal("github-actions"),
|
|
3986
|
-
kind: z.literal("org"),
|
|
4224
|
+
const githubActionsOrgDestinationSchema = z$1.object({
|
|
4225
|
+
provider: z$1.literal("github-actions"),
|
|
4226
|
+
kind: z$1.literal("org"),
|
|
3987
4227
|
/** Organization login. */
|
|
3988
4228
|
org: githubOwnerSchema,
|
|
3989
4229
|
/** Which repositories may read these secrets. Stated, never defaulted. */
|
|
3990
|
-
visibility: z.enum(GITHUB_ACTIONS_VISIBILITIES),
|
|
4230
|
+
visibility: z$1.enum(GITHUB_ACTIONS_VISIBILITIES),
|
|
3991
4231
|
/** Numeric repository ids, required when `visibility` is `selected`. */
|
|
3992
|
-
selectedRepositoryIds: z.array(z.number().int().positive()).max(500).optional()
|
|
4232
|
+
selectedRepositoryIds: z$1.array(z$1.number().int().positive()).max(500).optional()
|
|
3993
4233
|
}).refine((dest) => dest.visibility !== "selected" || dest.selectedRepositoryIds !== void 0 && dest.selectedRepositoryIds.length > 0, {
|
|
3994
4234
|
message: "selected visibility needs at least one repository id",
|
|
3995
4235
|
path: ["selectedRepositoryIds"]
|
|
@@ -3997,7 +4237,7 @@ const githubActionsOrgDestinationSchema = z.object({
|
|
|
3997
4237
|
message: "repository ids only apply to selected visibility — remove them, or select it",
|
|
3998
4238
|
path: ["selectedRepositoryIds"]
|
|
3999
4239
|
});
|
|
4000
|
-
const githubActionsDestinationSchema = z.discriminatedUnion("kind", [
|
|
4240
|
+
const githubActionsDestinationSchema = z$1.discriminatedUnion("kind", [
|
|
4001
4241
|
githubActionsRepoDestinationSchema,
|
|
4002
4242
|
githubActionsEnvironmentDestinationSchema,
|
|
4003
4243
|
githubActionsOrgDestinationSchema
|
|
@@ -4036,14 +4276,14 @@ const GCP_REPLICATION_POLICIES = ["automatic", "user-managed"];
|
|
|
4036
4276
|
* Manager namespace is spelled `prod-storefront-DB_URL`, not
|
|
4037
4277
|
* `prod/storefront/DB_URL`.
|
|
4038
4278
|
*/
|
|
4039
|
-
const gcpSecretIdSchema = z.string().trim().min(1).max(255).regex(/^[A-Za-z0-9_-]+$/, "may contain letters, digits, hyphens, and underscores");
|
|
4279
|
+
const gcpSecretIdSchema = z$1.string().trim().min(1).max(255).regex(/^[A-Za-z0-9_-]+$/, "may contain letters, digits, hyphens, and underscores");
|
|
4040
4280
|
/**
|
|
4041
4281
|
* A GCP region for a user-managed replica (`us-east1`, `europe-west4`,
|
|
4042
4282
|
* `northamerica-northeast1`). Validated by shape rather than against a list,
|
|
4043
4283
|
* because Google adds regions faster than we ship — a name Secret Manager does
|
|
4044
4284
|
* not know is refused by Google with a clear message at creation.
|
|
4045
4285
|
*/
|
|
4046
|
-
const gcpLocationSchema = z.string().trim().regex(/^[a-z]+-[a-z]+\d+$/, "must be a GCP region ID, e.g. us-east1");
|
|
4286
|
+
const gcpLocationSchema = z$1.string().trim().regex(/^[a-z]+-[a-z]+\d+$/, "must be a GCP region ID, e.g. us-east1");
|
|
4047
4287
|
/**
|
|
4048
4288
|
* A Cloud KMS key, as its full resource name — the only form the API accepts:
|
|
4049
4289
|
* `projects/p/locations/l/keyRings/r/cryptoKeys/k`.
|
|
@@ -4053,7 +4293,7 @@ const gcpLocationSchema = z.string().trim().regex(/^[a-z]+-[a-z]+\d+$/, "must be
|
|
|
4053
4293
|
* rejected at creation: an automatic-replication secret needs a `global` key,
|
|
4054
4294
|
* and a user-managed replica needs one in its own region.
|
|
4055
4295
|
*/
|
|
4056
|
-
const gcpKmsKeyNameSchema = z.string().trim().max(1024).regex(/^projects\/[^/]+\/locations\/[^/]+\/keyRings\/[^/]+\/cryptoKeys\/[^/]+$/, "must be a full Cloud KMS key name (projects/…/locations/…/keyRings/…/cryptoKeys/…)");
|
|
4296
|
+
const gcpKmsKeyNameSchema = z$1.string().trim().max(1024).regex(/^projects\/[^/]+\/locations\/[^/]+\/keyRings\/[^/]+\/cryptoKeys\/[^/]+$/, "must be a full Cloud KMS key name (projects/…/locations/…/keyRings/…/cryptoKeys/…)");
|
|
4057
4297
|
/**
|
|
4058
4298
|
* Where inside a project's Secret Manager a binding writes.
|
|
4059
4299
|
*
|
|
@@ -4072,25 +4312,25 @@ const gcpKmsKeyNameSchema = z.string().trim().max(1024).regex(/^projects\/[^/]+\
|
|
|
4072
4312
|
* on, the version a push supersedes is destroyed as soon as the new one lands,
|
|
4073
4313
|
* so a secret keeps exactly one active version.
|
|
4074
4314
|
*/
|
|
4075
|
-
const gcpSecretManagerDestinationSchema = z.object({
|
|
4076
|
-
provider: z.literal("gcp-secret-manager"),
|
|
4077
|
-
layout: z.enum(GCP_SECRET_MANAGER_LAYOUTS).default("secret-per-name"),
|
|
4315
|
+
const gcpSecretManagerDestinationSchema = z$1.object({
|
|
4316
|
+
provider: z$1.literal("gcp-secret-manager"),
|
|
4317
|
+
layout: z$1.enum(GCP_SECRET_MANAGER_LAYOUTS).default("secret-per-name"),
|
|
4078
4318
|
/**
|
|
4079
4319
|
* `secret-per-name` only: prepended to every secret ID, e.g.
|
|
4080
4320
|
* `prod-storefront-`. Optional, but strongly advised in a project that holds
|
|
4081
4321
|
* anything else — without it a binding writes at the root of a namespace it
|
|
4082
4322
|
* does not own, and Secret Manager has no folders to hide behind.
|
|
4083
4323
|
*/
|
|
4084
|
-
idPrefix: z.string().trim().max(200).regex(/^[A-Za-z0-9_-]*$/, "may contain letters, digits, hyphens, and underscores").optional(),
|
|
4324
|
+
idPrefix: z$1.string().trim().max(200).regex(/^[A-Za-z0-9_-]*$/, "may contain letters, digits, hyphens, and underscores").optional(),
|
|
4085
4325
|
/** `json-bundle` only: the one secret that holds every value, e.g. `prod-storefront-env`. */
|
|
4086
4326
|
secretId: gcpSecretIdSchema.optional(),
|
|
4087
|
-
replication: z.enum(GCP_REPLICATION_POLICIES).default("automatic"),
|
|
4327
|
+
replication: z$1.enum(GCP_REPLICATION_POLICIES).default("automatic"),
|
|
4088
4328
|
/** `user-managed` only: the regions to replicate to. At least one. */
|
|
4089
|
-
locations: z.array(gcpLocationSchema).min(1).max(16).optional(),
|
|
4329
|
+
locations: z$1.array(gcpLocationSchema).min(1).max(16).optional(),
|
|
4090
4330
|
/** Customer-managed encryption key. Omitted means Google-managed keys. */
|
|
4091
4331
|
kmsKeyName: gcpKmsKeyNameSchema.optional(),
|
|
4092
4332
|
/** Destroy the version each push supersedes, keeping one active version. */
|
|
4093
|
-
pruneVersions: z.boolean().optional()
|
|
4333
|
+
pruneVersions: z$1.boolean().optional()
|
|
4094
4334
|
}).refine((d) => d.layout !== "json-bundle" || d.secretId !== void 0, {
|
|
4095
4335
|
message: "a json-bundle destination needs the ID of the secret to write",
|
|
4096
4336
|
path: ["secretId"]
|
|
@@ -4117,10 +4357,10 @@ const gcpSecretManagerDestinationSchema = z.object({
|
|
|
4117
4357
|
* the `id` from `GET /v2/deployments`, and the UUID in the deployment's
|
|
4118
4358
|
* dashboard URL.
|
|
4119
4359
|
*/
|
|
4120
|
-
const langgraphPlatformDestinationSchema = z.object({
|
|
4121
|
-
provider: z.literal("langgraph-platform"),
|
|
4360
|
+
const langgraphPlatformDestinationSchema = z$1.object({
|
|
4361
|
+
provider: z$1.literal("langgraph-platform"),
|
|
4122
4362
|
/** Deployment UUID, from the dashboard URL or `GET /v2/deployments`. */
|
|
4123
|
-
deploymentId: z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a LangGraph Platform deployment UUID")
|
|
4363
|
+
deploymentId: z$1.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a LangGraph Platform deployment UUID")
|
|
4124
4364
|
});
|
|
4125
4365
|
/**
|
|
4126
4366
|
* A Key Vault's name — the leftmost label of its DNS name, so the vault
|
|
@@ -4136,7 +4376,7 @@ const langgraphPlatformDestinationSchema = z.object({
|
|
|
4136
4376
|
* any host, and the whole address seekrit needs is this label plus the cloud
|
|
4137
4377
|
* already named on the connection.
|
|
4138
4378
|
*/
|
|
4139
|
-
const azureVaultNameSchema = z.string().trim().regex(/^[A-Za-z](?!.*--)[A-Za-z0-9-]{1,22}[A-Za-z0-9]$/, "must be a Key Vault name: 3–24 letters, digits, and single hyphens, starting with a letter");
|
|
4379
|
+
const azureVaultNameSchema = z$1.string().trim().regex(/^[A-Za-z](?!.*--)[A-Za-z0-9-]{1,22}[A-Za-z0-9]$/, "must be a Key Vault name: 3–24 letters, digits, and single hyphens, starting with a letter");
|
|
4140
4380
|
/**
|
|
4141
4381
|
* What to do with a name Key Vault cannot store.
|
|
4142
4382
|
*
|
|
@@ -4167,8 +4407,8 @@ const AZURE_KEY_VAULT_NAME_MODES = ["dash", "reject"];
|
|
|
4167
4407
|
* charges per *operation*, so fifty names cost the same stored fifty ways. One
|
|
4168
4408
|
* secret per name is simply correct here.
|
|
4169
4409
|
*/
|
|
4170
|
-
const azureKeyVaultDestinationSchema = z.object({
|
|
4171
|
-
provider: z.literal("azure-key-vault"),
|
|
4410
|
+
const azureKeyVaultDestinationSchema = z$1.object({
|
|
4411
|
+
provider: z$1.literal("azure-key-vault"),
|
|
4172
4412
|
/** Vault name, e.g. `acme-prod` for `acme-prod.vault.azure.net`. */
|
|
4173
4413
|
vault: azureVaultNameSchema,
|
|
4174
4414
|
/**
|
|
@@ -4177,9 +4417,9 @@ const azureKeyVaultDestinationSchema = z.object({
|
|
|
4177
4417
|
* accepts — so unlike Parameter Store's `path` this is a naming convention
|
|
4178
4418
|
* and nothing more. Worth setting in a vault that holds anything else.
|
|
4179
4419
|
*/
|
|
4180
|
-
prefix: z.string().trim().max(64).regex(/^[A-Za-z0-9-]*$/, "may contain letters, digits, and hyphens").optional(),
|
|
4420
|
+
prefix: z$1.string().trim().max(64).regex(/^[A-Za-z0-9-]*$/, "may contain letters, digits, and hyphens").optional(),
|
|
4181
4421
|
/** What to do with a name Key Vault cannot store — see {@link AZURE_KEY_VAULT_NAME_MODES}. */
|
|
4182
|
-
nameMode: z.enum(AZURE_KEY_VAULT_NAME_MODES).default("dash")
|
|
4422
|
+
nameMode: z$1.enum(AZURE_KEY_VAULT_NAME_MODES).default("dash")
|
|
4183
4423
|
});
|
|
4184
4424
|
/**
|
|
4185
4425
|
* The Space whose secrets a binding owns, addressed the way the Hub addresses
|
|
@@ -4198,7 +4438,7 @@ const azureKeyVaultDestinationSchema = z.object({
|
|
|
4198
4438
|
* leading character is held to alphanumeric, which is also what rejects the
|
|
4199
4439
|
* `.` and `..` that no repository may be called.
|
|
4200
4440
|
*/
|
|
4201
|
-
const huggingfaceRepoIdSchema = z.string().trim().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,95}\/[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/, "must be a Space ID as owner/name, e.g. acme/support-demo — not a URL");
|
|
4441
|
+
const huggingfaceRepoIdSchema = z$1.string().trim().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,95}\/[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/, "must be a Space ID as owner/name, e.g. acme/support-demo — not a URL");
|
|
4202
4442
|
/**
|
|
4203
4443
|
* One Hugging Face **Space**, whose secrets arrive in the app's container as
|
|
4204
4444
|
* environment variables.
|
|
@@ -4210,16 +4450,16 @@ const huggingfaceRepoIdSchema = z.string().trim().regex(/^[A-Za-z0-9][A-Za-z0-9.
|
|
|
4210
4450
|
* it, so this connector has no variables mode — a binding that wants one is
|
|
4211
4451
|
* asking for the wrong thing.
|
|
4212
4452
|
*/
|
|
4213
|
-
const huggingfaceSpacesDestinationSchema = z.object({
|
|
4214
|
-
provider: z.literal("huggingface-spaces"),
|
|
4453
|
+
const huggingfaceSpacesDestinationSchema = z$1.object({
|
|
4454
|
+
provider: z$1.literal("huggingface-spaces"),
|
|
4215
4455
|
/** Space ID as `owner/name`, e.g. `acme/support-demo`. */
|
|
4216
4456
|
repoId: huggingfaceRepoIdSchema
|
|
4217
4457
|
});
|
|
4218
4458
|
/** One existing OpenAI vault; every credential in this binding gets the same host boundary. */
|
|
4219
|
-
const openaiVaultDestinationSchema = z.object({
|
|
4220
|
-
provider: z.literal("openai-vault"),
|
|
4221
|
-
vaultId: z.string().trim().regex(/^vault_[A-Za-z0-9_-]+$/, "must be an OpenAI vault ID"),
|
|
4222
|
-
allowedHosts: z.array(z.string().trim().toLowerCase().max(253).regex(/^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/, "must be an exact hostname without scheme, port, path, or wildcard")).min(1).max(100).refine((hosts) => new Set(hosts).size === hosts.length, "hostnames must be unique")
|
|
4459
|
+
const openaiVaultDestinationSchema = z$1.object({
|
|
4460
|
+
provider: z$1.literal("openai-vault"),
|
|
4461
|
+
vaultId: z$1.string().trim().regex(/^vault_[A-Za-z0-9_-]+$/, "must be an OpenAI vault ID"),
|
|
4462
|
+
allowedHosts: z$1.array(z$1.string().trim().toLowerCase().max(253).regex(/^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/, "must be an exact hostname without scheme, port, path, or wildcard")).min(1).max(100).refine((hosts) => new Set(hosts).size === hosts.length, "hostnames must be unique")
|
|
4223
4463
|
});
|
|
4224
4464
|
/**
|
|
4225
4465
|
* A vault ID as the Claude Console and API show it: `vlt_` and an opaque
|
|
@@ -4227,7 +4467,7 @@ const openaiVaultDestinationSchema = z.object({
|
|
|
4227
4467
|
* vault's display name — is a 404 from inside an alarm, which says nothing
|
|
4228
4468
|
* about which half of the address was wrong.
|
|
4229
4469
|
*/
|
|
4230
|
-
const claudeVaultIdSchema = z.string().trim().regex(/^vlt_[A-Za-z0-9]{1,128}$/, "must be a vault ID, e.g. vlt_01ABC… — not its display name");
|
|
4470
|
+
const claudeVaultIdSchema = z$1.string().trim().regex(/^vlt_[A-Za-z0-9]{1,128}$/, "must be a vault ID, e.g. vlt_01ABC… — not its display name");
|
|
4231
4471
|
/** Ceiling on per-name host rules; a vault itself holds at most 20 credentials. */
|
|
4232
4472
|
const CLAUDE_VAULT_MAX_HOST_RULES = 100;
|
|
4233
4473
|
/** A hostname of two or more labels whose last label starts with a letter. */
|
|
@@ -4244,14 +4484,14 @@ const CLAUDE_VAULT_IPV4 = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2
|
|
|
4244
4484
|
* `stripe.com` **but not `stripe.com` itself**, so a service reached at its apex
|
|
4245
4485
|
* needs both entries. A bare TLD wildcard (`*.com`) is refused by shape.
|
|
4246
4486
|
*/
|
|
4247
|
-
const claudeVaultHostSchema = z.string().trim().toLowerCase().max(253).refine((host) => CLAUDE_VAULT_HOSTNAME.test(host) || CLAUDE_VAULT_IPV4.test(host), "must be a bare hostname (api.example.com), a *.example.com wildcard, or an IPv4 address — not a URL");
|
|
4487
|
+
const claudeVaultHostSchema = z$1.string().trim().toLowerCase().max(253).refine((host) => CLAUDE_VAULT_HOSTNAME.test(host) || CLAUDE_VAULT_IPV4.test(host), "must be a bare hostname (api.example.com), a *.example.com wildcard, or an IPv4 address — not a URL");
|
|
4248
4488
|
/**
|
|
4249
4489
|
* Where one secret's value may be substituted: a list of hosts, or the literal
|
|
4250
4490
|
* `"unrestricted"`, which lets it reach any host the agent's *environment*
|
|
4251
4491
|
* permits. The literal is spelled out rather than implied by an empty list, so
|
|
4252
4492
|
* the widest setting is never something a binding arrives at by omission.
|
|
4253
4493
|
*/
|
|
4254
|
-
const claudeVaultHostRuleSchema = z.union([z.literal("unrestricted"), z.array(claudeVaultHostSchema).min(1).max(16)]);
|
|
4494
|
+
const claudeVaultHostRuleSchema = z$1.union([z$1.literal("unrestricted"), z$1.array(claudeVaultHostSchema).min(1).max(16)]);
|
|
4255
4495
|
/**
|
|
4256
4496
|
* Which parts of an outbound request a secret's placeholder is swapped in. The
|
|
4257
4497
|
* API's own default when the field is omitted is both; seekrit's is `header`,
|
|
@@ -4284,15 +4524,15 @@ const CLAUDE_VAULT_INJECTION_LOCATIONS = [
|
|
|
4284
4524
|
* flow. Both levels apply beside the agent environment's own network policy,
|
|
4285
4525
|
* which must also let the host through; this shape cannot see that policy.
|
|
4286
4526
|
*/
|
|
4287
|
-
const claudeManagedAgentsDestinationSchema = z.object({
|
|
4288
|
-
provider: z.literal("claude-managed-agents"),
|
|
4527
|
+
const claudeManagedAgentsDestinationSchema = z$1.object({
|
|
4528
|
+
provider: z$1.literal("claude-managed-agents"),
|
|
4289
4529
|
/** The vault's ID, e.g. `vlt_01ABC…`. */
|
|
4290
4530
|
vaultId: claudeVaultIdSchema,
|
|
4291
4531
|
/** Hosts for every name without its own entry in `allowedHostsByName`. */
|
|
4292
4532
|
allowedHosts: claudeVaultHostRuleSchema.optional(),
|
|
4293
4533
|
/** Destination name → the hosts that one value may be substituted on. */
|
|
4294
|
-
allowedHostsByName: z.record(secretNameSchema, claudeVaultHostRuleSchema).optional(),
|
|
4295
|
-
injectionLocation: z.enum(CLAUDE_VAULT_INJECTION_LOCATIONS).default("header")
|
|
4534
|
+
allowedHostsByName: z$1.record(secretNameSchema, claudeVaultHostRuleSchema).optional(),
|
|
4535
|
+
injectionLocation: z$1.enum(CLAUDE_VAULT_INJECTION_LOCATIONS).default("header")
|
|
4296
4536
|
}).refine((d) => d.allowedHosts !== void 0 || Object.keys(d.allowedHostsByName ?? {}).length > 0, {
|
|
4297
4537
|
message: "name the hosts each secret may be used on — allowedHosts for all of them, allowedHostsByName per name, or \"unrestricted\"",
|
|
4298
4538
|
path: ["allowedHosts"]
|
|
@@ -4300,7 +4540,7 @@ const claudeManagedAgentsDestinationSchema = z.object({
|
|
|
4300
4540
|
message: `at most ${CLAUDE_VAULT_MAX_HOST_RULES} per-name host rules`,
|
|
4301
4541
|
path: ["allowedHostsByName"]
|
|
4302
4542
|
});
|
|
4303
|
-
const syncDestinationSchema = z.discriminatedUnion("provider", [
|
|
4543
|
+
const syncDestinationSchema = z$1.discriminatedUnion("provider", [
|
|
4304
4544
|
vercelDestinationSchema,
|
|
4305
4545
|
cloudflareWorkersDestinationSchema,
|
|
4306
4546
|
cloudflarePagesDestinationSchema,
|
|
@@ -4327,59 +4567,59 @@ const syncDestinationSchema = z.discriminatedUnion("provider", [
|
|
|
4327
4567
|
* How seekrit secret names become destination key names. Applied in order:
|
|
4328
4568
|
* explicit `rename` (wins outright), then `prefix`/`suffix`, then `case`.
|
|
4329
4569
|
*/
|
|
4330
|
-
const nameTransformSchema = z.object({
|
|
4331
|
-
prefix: z.string().max(64).regex(/^[A-Za-z0-9_]*$/, "must be alphanumeric or underscore").optional(),
|
|
4332
|
-
suffix: z.string().max(64).regex(/^[A-Za-z0-9_]*$/, "must be alphanumeric or underscore").optional(),
|
|
4333
|
-
case: z.enum([
|
|
4570
|
+
const nameTransformSchema = z$1.object({
|
|
4571
|
+
prefix: z$1.string().max(64).regex(/^[A-Za-z0-9_]*$/, "must be alphanumeric or underscore").optional(),
|
|
4572
|
+
suffix: z$1.string().max(64).regex(/^[A-Za-z0-9_]*$/, "must be alphanumeric or underscore").optional(),
|
|
4573
|
+
case: z$1.enum([
|
|
4334
4574
|
"preserve",
|
|
4335
4575
|
"upper",
|
|
4336
4576
|
"lower"
|
|
4337
4577
|
]).optional(),
|
|
4338
4578
|
/** Exact per-secret overrides, seekrit name → destination name. */
|
|
4339
|
-
rename: z.record(secretNameSchema, secretNameSchema).optional()
|
|
4579
|
+
rename: z$1.record(secretNameSchema, secretNameSchema).optional()
|
|
4340
4580
|
});
|
|
4341
|
-
z.object({
|
|
4581
|
+
z$1.object({
|
|
4342
4582
|
/**
|
|
4343
4583
|
* The id the client already fetched a public key for. Connections are a
|
|
4344
4584
|
* two-step dance — mint the key, wrap the credential to it, then create the
|
|
4345
4585
|
* row — so the id has to be chosen before the row exists. Omit it and the
|
|
4346
4586
|
* server generates one (only useful when there is no credential to wrap yet).
|
|
4347
4587
|
*/
|
|
4348
|
-
id: z.string().regex(/^syc_[A-Za-z0-9]{24}$/, "must be a connection id from the public-key call").optional(),
|
|
4349
|
-
name: z.string().trim().min(1).max(128),
|
|
4588
|
+
id: z$1.string().regex(/^syc_[A-Za-z0-9]{24}$/, "must be a connection id from the public-key call").optional(),
|
|
4589
|
+
name: z$1.string().trim().min(1).max(128),
|
|
4350
4590
|
config: syncConnectionConfigSchema,
|
|
4351
4591
|
/**
|
|
4352
4592
|
* The destination's API credential (a Vercel token), encrypted client-side to
|
|
4353
4593
|
* the connection's public key (a `wd1.` wrap). The control plane stores only
|
|
4354
4594
|
* this ciphertext; it is unwrapped transiently inside the sync engine DO.
|
|
4355
4595
|
*/
|
|
4356
|
-
wrappedCredential: z.string().min(1)
|
|
4596
|
+
wrappedCredential: z$1.string().min(1)
|
|
4357
4597
|
});
|
|
4358
|
-
z.object({ destination: syncDestinationSchema });
|
|
4359
|
-
const globListSchema = z.array(z.string().trim().min(1).max(256)).max(100);
|
|
4360
|
-
z.object({
|
|
4361
|
-
connectionId: z.string().min(1),
|
|
4362
|
-
environmentId: z.string().min(1),
|
|
4598
|
+
z$1.object({ destination: syncDestinationSchema });
|
|
4599
|
+
const globListSchema = z$1.array(z$1.string().trim().min(1).max(256)).max(100);
|
|
4600
|
+
z$1.object({
|
|
4601
|
+
connectionId: z$1.string().min(1),
|
|
4602
|
+
environmentId: z$1.string().min(1),
|
|
4363
4603
|
destination: syncDestinationSchema,
|
|
4364
4604
|
nameTransform: nameTransformSchema.optional(),
|
|
4365
4605
|
include: globListSchema.optional(),
|
|
4366
4606
|
exclude: globListSchema.optional(),
|
|
4367
|
-
onDelete: z.enum(["delete", "retain"]).default("delete"),
|
|
4368
|
-
mode: z.enum(["auto", "manual"]).default("auto"),
|
|
4369
|
-
wrappedDeks: z.array(z.object({
|
|
4370
|
-
environmentId: z.string().min(1),
|
|
4371
|
-
wrappedDek: z.string().min(1)
|
|
4607
|
+
onDelete: z$1.enum(["delete", "retain"]).default("delete"),
|
|
4608
|
+
mode: z$1.enum(["auto", "manual"]).default("auto"),
|
|
4609
|
+
wrappedDeks: z$1.array(z$1.object({
|
|
4610
|
+
environmentId: z$1.string().min(1),
|
|
4611
|
+
wrappedDek: z$1.string().min(1)
|
|
4372
4612
|
})).min(1),
|
|
4373
|
-
acknowledgedDecryption: z.literal(true)
|
|
4613
|
+
acknowledgedDecryption: z$1.literal(true)
|
|
4374
4614
|
});
|
|
4375
|
-
z.object({
|
|
4615
|
+
z$1.object({
|
|
4376
4616
|
destination: syncDestinationSchema.optional(),
|
|
4377
4617
|
nameTransform: nameTransformSchema.nullable().optional(),
|
|
4378
4618
|
include: globListSchema.nullable().optional(),
|
|
4379
4619
|
exclude: globListSchema.nullable().optional(),
|
|
4380
|
-
onDelete: z.enum(["delete", "retain"]).optional(),
|
|
4381
|
-
mode: z.enum(["auto", "manual"]).optional(),
|
|
4382
|
-
enabled: z.boolean().optional()
|
|
4620
|
+
onDelete: z$1.enum(["delete", "retain"]).optional(),
|
|
4621
|
+
mode: z$1.enum(["auto", "manual"]).optional(),
|
|
4622
|
+
enabled: z$1.boolean().optional()
|
|
4383
4623
|
});
|
|
4384
4624
|
// ../../packages/core/src/vault.ts
|
|
4385
4625
|
/** One encrypted credential within a connection. */
|
|
@@ -4411,73 +4651,73 @@ const VAULT_WEBHOOK_EVENTS = [
|
|
|
4411
4651
|
"connection.reauth_required",
|
|
4412
4652
|
"connection.revoked"
|
|
4413
4653
|
];
|
|
4414
|
-
const base64urlMember = z.string().regex(/^[A-Za-z0-9_-]{43}$/, "not a P-256 coordinate");
|
|
4415
|
-
const vaultPublicJwkSchema = z.object({
|
|
4416
|
-
kty: z.literal("EC"),
|
|
4417
|
-
crv: z.literal("P-256"),
|
|
4654
|
+
const base64urlMember = z$1.string().regex(/^[A-Za-z0-9_-]{43}$/, "not a P-256 coordinate");
|
|
4655
|
+
const vaultPublicJwkSchema = z$1.object({
|
|
4656
|
+
kty: z$1.literal("EC"),
|
|
4657
|
+
crv: z$1.literal("P-256"),
|
|
4418
4658
|
x: base64urlMember,
|
|
4419
4659
|
y: base64urlMember
|
|
4420
4660
|
});
|
|
4421
|
-
const isoInstant = z.iso.datetime({ offset: true });
|
|
4422
|
-
z.object({
|
|
4423
|
-
v: z.literal(1),
|
|
4424
|
-
accessToken: z.string().min(1).max(16384),
|
|
4425
|
-
tokenType: z.literal("Bearer"),
|
|
4661
|
+
const isoInstant = z$1.iso.datetime({ offset: true });
|
|
4662
|
+
z$1.object({
|
|
4663
|
+
v: z$1.literal(1),
|
|
4664
|
+
accessToken: z$1.string().min(1).max(16384),
|
|
4665
|
+
tokenType: z$1.literal("Bearer"),
|
|
4426
4666
|
accessTokenExpiresAt: isoInstant.optional(),
|
|
4427
|
-
refreshToken: z.string().min(1).max(16384).optional(),
|
|
4667
|
+
refreshToken: z$1.string().min(1).max(16384).optional(),
|
|
4428
4668
|
refreshTokenExpiresAt: isoInstant.optional(),
|
|
4429
|
-
scope: z.array(z.string().max(512)).max(128),
|
|
4669
|
+
scope: z$1.array(z$1.string().max(512)).max(128),
|
|
4430
4670
|
obtainedAt: isoInstant
|
|
4431
4671
|
});
|
|
4432
|
-
z.object({
|
|
4433
|
-
v: z.literal(1),
|
|
4434
|
-
key: z.string().min(1).max(8192)
|
|
4672
|
+
z$1.object({
|
|
4673
|
+
v: z$1.literal(1),
|
|
4674
|
+
key: z$1.string().min(1).max(8192)
|
|
4435
4675
|
});
|
|
4436
4676
|
/** RFC 6238 parameters, as an authenticator app would hold them. */
|
|
4437
|
-
const vaultTotpSchema = z.object({
|
|
4677
|
+
const vaultTotpSchema = z$1.object({
|
|
4438
4678
|
/** Base32 (RFC 4648), upper case, no padding, no separators. */
|
|
4439
|
-
secret: z.string().regex(/^[A-Z2-7]{16,128}$/, "TOTP secret must be base32"),
|
|
4440
|
-
digits: z.union([z.literal(6), z.literal(8)]),
|
|
4441
|
-
period: z.number().int().min(15).max(120),
|
|
4442
|
-
algorithm: z.enum([
|
|
4679
|
+
secret: z$1.string().regex(/^[A-Z2-7]{16,128}$/, "TOTP secret must be base32"),
|
|
4680
|
+
digits: z$1.union([z$1.literal(6), z$1.literal(8)]),
|
|
4681
|
+
period: z$1.number().int().min(15).max(120),
|
|
4682
|
+
algorithm: z$1.enum([
|
|
4443
4683
|
"SHA1",
|
|
4444
4684
|
"SHA256",
|
|
4445
4685
|
"SHA512"
|
|
4446
4686
|
])
|
|
4447
4687
|
});
|
|
4448
|
-
z.object({
|
|
4449
|
-
v: z.literal(1),
|
|
4450
|
-
username: z.string().min(1).max(512),
|
|
4451
|
-
password: z.string().min(1).max(4096),
|
|
4688
|
+
z$1.object({
|
|
4689
|
+
v: z$1.literal(1),
|
|
4690
|
+
username: z$1.string().min(1).max(512),
|
|
4691
|
+
password: z$1.string().min(1).max(4096),
|
|
4452
4692
|
totp: vaultTotpSchema.optional()
|
|
4453
4693
|
});
|
|
4454
|
-
const vaultSessionCookieSchema = z.object({
|
|
4455
|
-
name: z.string().min(1).max(4096),
|
|
4456
|
-
value: z.string().max(16384),
|
|
4694
|
+
const vaultSessionCookieSchema = z$1.object({
|
|
4695
|
+
name: z$1.string().min(1).max(4096),
|
|
4696
|
+
value: z$1.string().max(16384),
|
|
4457
4697
|
/** Kept exactly as captured — parent-domain cookies restore to their parent. */
|
|
4458
|
-
domain: z.string().min(1).max(253),
|
|
4459
|
-
path: z.string().min(1).max(4096),
|
|
4698
|
+
domain: z$1.string().min(1).max(253),
|
|
4699
|
+
path: z$1.string().min(1).max(4096),
|
|
4460
4700
|
/** Unix seconds; -1 ⇒ a session cookie. */
|
|
4461
|
-
expires: z.number(),
|
|
4462
|
-
httpOnly: z.boolean(),
|
|
4463
|
-
secure: z.boolean(),
|
|
4464
|
-
sameSite: z.enum([
|
|
4701
|
+
expires: z$1.number(),
|
|
4702
|
+
httpOnly: z$1.boolean(),
|
|
4703
|
+
secure: z$1.boolean(),
|
|
4704
|
+
sameSite: z$1.enum([
|
|
4465
4705
|
"Strict",
|
|
4466
4706
|
"Lax",
|
|
4467
4707
|
"None"
|
|
4468
4708
|
]).optional(),
|
|
4469
|
-
partitionKey: z.unknown().optional()
|
|
4709
|
+
partitionKey: z$1.unknown().optional()
|
|
4470
4710
|
});
|
|
4471
|
-
z.object({
|
|
4472
|
-
v: z.literal(1),
|
|
4473
|
-
siteId: z.string().min(1).max(64),
|
|
4711
|
+
z$1.object({
|
|
4712
|
+
v: z$1.literal(1),
|
|
4713
|
+
siteId: z$1.string().min(1).max(64),
|
|
4474
4714
|
capturedAt: isoInstant,
|
|
4475
|
-
userAgent: z.string().max(1024),
|
|
4476
|
-
cookies: z.array(vaultSessionCookieSchema).max(4096),
|
|
4715
|
+
userAgent: z$1.string().max(1024),
|
|
4716
|
+
cookies: z$1.array(vaultSessionCookieSchema).max(4096),
|
|
4477
4717
|
/** origin → key → value. */
|
|
4478
|
-
localStorage: z.record(z.string(), z.record(z.string(), z.string()))
|
|
4718
|
+
localStorage: z$1.record(z$1.string(), z$1.record(z$1.string(), z$1.string()))
|
|
4479
4719
|
});
|
|
4480
|
-
const vaultProviderIdSchema = z.string().trim().min(1).max(64).regex(/^[a-z0-9][a-z0-9._-]*$/, "provider ids are lowercase letters, digits, dots and dashes");
|
|
4720
|
+
const vaultProviderIdSchema = z$1.string().trim().min(1).max(64).regex(/^[a-z0-9][a-z0-9._-]*$/, "provider ids are lowercase letters, digits, dots and dashes");
|
|
4481
4721
|
/**
|
|
4482
4722
|
* What the dashboard, Connect and the SDK may know about a provider: its name,
|
|
4483
4723
|
* its method, and — for the consent screen — a plain sentence about what the
|
|
@@ -4485,90 +4725,90 @@ const vaultProviderIdSchema = z.string().trim().min(1).max(64).regex(/^[a-z0-9][
|
|
|
4485
4725
|
* those are compiled into the executor and the API must not be able to serve a
|
|
4486
4726
|
* different version of them.
|
|
4487
4727
|
*/
|
|
4488
|
-
const vaultCatalogProviderSchema = z.object({
|
|
4728
|
+
const vaultCatalogProviderSchema = z$1.object({
|
|
4489
4729
|
id: vaultProviderIdSchema,
|
|
4490
|
-
displayName: z.string().trim().min(1).max(80),
|
|
4491
|
-
method: z.enum(["oauth2", "api_key"]),
|
|
4730
|
+
displayName: z$1.string().trim().min(1).max(80),
|
|
4731
|
+
method: z$1.enum(["oauth2", "api_key"]),
|
|
4492
4732
|
/** The one sentence Connect shows: "read and send email as you". */
|
|
4493
|
-
consentSummary: z.string().trim().min(1).max(240),
|
|
4733
|
+
consentSummary: z$1.string().trim().min(1).max(240),
|
|
4494
4734
|
/** Hosts the executor will forward to, for display. */
|
|
4495
|
-
hosts: z.array(z.string().trim().min(1).max(253)).max(64).default([]),
|
|
4735
|
+
hosts: z$1.array(z$1.string().trim().min(1).max(253)).max(64).default([]),
|
|
4496
4736
|
/** Where the end user finds the key (api_key providers). */
|
|
4497
|
-
helpUrl: z.url().max(2048).optional(),
|
|
4737
|
+
helpUrl: z$1.url().max(2048).optional(),
|
|
4498
4738
|
/** Label for the key field (api_key providers), e.g. "API key". */
|
|
4499
|
-
keyLabel: z.string().trim().min(1).max(60).optional(),
|
|
4739
|
+
keyLabel: z$1.string().trim().min(1).max(60).optional(),
|
|
4500
4740
|
/** OAuth scopes the provider will be asked for (oauth2 providers), for display. */
|
|
4501
|
-
scopes: z.array(z.string().trim().min(1).max(512)).max(64).optional()
|
|
4741
|
+
scopes: z$1.array(z$1.string().trim().min(1).max(512)).max(64).optional()
|
|
4502
4742
|
});
|
|
4503
|
-
const vaultCatalogSiteSchema = z.object({
|
|
4743
|
+
const vaultCatalogSiteSchema = z$1.object({
|
|
4504
4744
|
id: vaultProviderIdSchema,
|
|
4505
|
-
displayName: z.string().trim().min(1).max(80),
|
|
4506
|
-
consentSummary: z.string().trim().min(1).max(240),
|
|
4745
|
+
displayName: z$1.string().trim().min(1).max(80),
|
|
4746
|
+
consentSummary: z$1.string().trim().min(1).max(240),
|
|
4507
4747
|
/** Origins the login form must live on (shown so the person can check). */
|
|
4508
|
-
loginOrigins: z.array(z.url().max(2048)).max(16).default([]),
|
|
4748
|
+
loginOrigins: z$1.array(z$1.url().max(2048)).max(16).default([]),
|
|
4509
4749
|
/** Where a restored session lands, for display. */
|
|
4510
|
-
startUrl: z.url().max(2048).optional()
|
|
4750
|
+
startUrl: z$1.url().max(2048).optional()
|
|
4511
4751
|
});
|
|
4512
|
-
const vaultCatalogSchema = z.object({
|
|
4513
|
-
v: z.literal(1),
|
|
4514
|
-
providers: z.array(vaultCatalogProviderSchema).max(200).default([]),
|
|
4515
|
-
sites: z.array(vaultCatalogSiteSchema).max(200).default([])
|
|
4752
|
+
const vaultCatalogSchema = z$1.object({
|
|
4753
|
+
v: z$1.literal(1),
|
|
4754
|
+
providers: z$1.array(vaultCatalogProviderSchema).max(200).default([]),
|
|
4755
|
+
sites: z$1.array(vaultCatalogSiteSchema).max(200).default([])
|
|
4516
4756
|
});
|
|
4517
|
-
const vaultDisplaySchema = z.object({
|
|
4757
|
+
const vaultDisplaySchema = z$1.object({
|
|
4518
4758
|
/** The platform's name as its users know it: "Instinct", not "instinct-prod". */
|
|
4519
|
-
name: z.string().trim().min(1).max(80),
|
|
4520
|
-
logoUrl: z.url().max(2048).startsWith("https://").optional(),
|
|
4759
|
+
name: z$1.string().trim().min(1).max(80),
|
|
4760
|
+
logoUrl: z$1.url().max(2048).startsWith("https://").optional(),
|
|
4521
4761
|
/** `#rrggbb`. */
|
|
4522
|
-
accent: z.string().regex(/^#[0-9a-fA-F]{6}$/, "accent must be a #rrggbb colour").optional(),
|
|
4762
|
+
accent: z$1.string().regex(/^#[0-9a-fA-F]{6}$/, "accent must be a #rrggbb colour").optional(),
|
|
4523
4763
|
/** Origins a `returnUrl` may point at. Exact-match, scheme + host (+ port). */
|
|
4524
|
-
returnOrigins: z.array(z.url().max(2048)).max(16).default([])
|
|
4764
|
+
returnOrigins: z$1.array(z$1.url().max(2048)).max(16).default([])
|
|
4525
4765
|
});
|
|
4526
|
-
z.object({
|
|
4766
|
+
z$1.object({
|
|
4527
4767
|
name: nameSchema,
|
|
4528
4768
|
display: vaultDisplaySchema.optional()
|
|
4529
4769
|
});
|
|
4530
|
-
z.object({
|
|
4770
|
+
z$1.object({
|
|
4531
4771
|
name: nameSchema.optional(),
|
|
4532
4772
|
display: vaultDisplaySchema.optional(),
|
|
4533
|
-
status: z.enum(["active", "disabled"]).optional()
|
|
4773
|
+
status: z$1.enum(["active", "disabled"]).optional()
|
|
4534
4774
|
});
|
|
4535
|
-
z.object({ name: nameSchema });
|
|
4775
|
+
z$1.object({ name: nameSchema });
|
|
4536
4776
|
/**
|
|
4537
4777
|
* The platform's opaque identifier for its end user. Stored as given and
|
|
4538
4778
|
* returned as given. It must not be an email address or anything else that
|
|
4539
4779
|
* identifies the person outside the platform: the person has no seekrit
|
|
4540
4780
|
* account, and this is the one field about them Vault holds in the clear.
|
|
4541
4781
|
*/
|
|
4542
|
-
const vaultUserRefSchema = z.string().trim().min(1).max(200).refine((v) => !/\s/.test(v), { message: "userRef must not contain whitespace" }).refine((v) => !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v), { message: "userRef must be an opaque identifier, not an email address" });
|
|
4543
|
-
z.object({
|
|
4782
|
+
const vaultUserRefSchema = z$1.string().trim().min(1).max(200).refine((v) => !/\s/.test(v), { message: "userRef must not contain whitespace" }).refine((v) => !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v), { message: "userRef must be an opaque identifier, not an email address" });
|
|
4783
|
+
z$1.object({
|
|
4544
4784
|
userRef: vaultUserRefSchema,
|
|
4545
4785
|
providerId: vaultProviderIdSchema,
|
|
4546
|
-
purpose: z.enum(["connect", "reconnect"]).default("connect"),
|
|
4786
|
+
purpose: z$1.enum(["connect", "reconnect"]).default("connect"),
|
|
4547
4787
|
/** Required for `reconnect`: the connection being repaired. */
|
|
4548
|
-
connectionId: z.string().trim().min(1).max(64).optional(),
|
|
4788
|
+
connectionId: z$1.string().trim().min(1).max(64).optional(),
|
|
4549
4789
|
/** Where the done screen may send the person. Must match a registered origin. */
|
|
4550
|
-
returnUrl: z.url().max(2048).optional(),
|
|
4551
|
-
expiresInSeconds: z.number().int().min(60).max(VAULT_CONNECT_SESSION_MAX_TTL_SECONDS).optional()
|
|
4790
|
+
returnUrl: z$1.url().max(2048).optional(),
|
|
4791
|
+
expiresInSeconds: z$1.number().int().min(60).max(VAULT_CONNECT_SESSION_MAX_TTL_SECONDS).optional()
|
|
4552
4792
|
});
|
|
4553
|
-
z.object({
|
|
4793
|
+
z$1.object({
|
|
4554
4794
|
userRef: vaultUserRefSchema,
|
|
4555
|
-
returnUrl: z.url().max(2048).optional(),
|
|
4556
|
-
expiresInSeconds: z.number().int().min(60).max(VAULT_CONNECT_SESSION_MAX_TTL_SECONDS).optional()
|
|
4795
|
+
returnUrl: z$1.url().max(2048).optional(),
|
|
4796
|
+
expiresInSeconds: z$1.number().int().min(60).max(VAULT_CONNECT_SESSION_MAX_TTL_SECONDS).optional()
|
|
4557
4797
|
});
|
|
4558
|
-
z.object({
|
|
4559
|
-
url: z.url().max(2048).refine((u) => u.startsWith("https://"), "webhook URLs must be https"),
|
|
4798
|
+
z$1.object({
|
|
4799
|
+
url: z$1.url().max(2048).refine((u) => u.startsWith("https://"), "webhook URLs must be https"),
|
|
4560
4800
|
/** Defaults to every event. */
|
|
4561
|
-
events: z.array(z.enum(VAULT_WEBHOOK_EVENTS)).min(1).max(VAULT_WEBHOOK_EVENTS.length).optional(),
|
|
4562
|
-
description: z.string().trim().max(200).optional()
|
|
4801
|
+
events: z$1.array(z$1.enum(VAULT_WEBHOOK_EVENTS)).min(1).max(VAULT_WEBHOOK_EVENTS.length).optional(),
|
|
4802
|
+
description: z$1.string().trim().max(200).optional()
|
|
4563
4803
|
});
|
|
4564
|
-
z.object({
|
|
4804
|
+
z$1.object({
|
|
4565
4805
|
userRef: vaultUserRefSchema.optional(),
|
|
4566
|
-
status: z.enum(VAULT_CONNECTION_STATUSES).optional(),
|
|
4567
|
-
cursor: z.string().max(200).optional(),
|
|
4568
|
-
limit: z.coerce.number().int().min(1).max(200).default(50)
|
|
4806
|
+
status: z$1.enum(VAULT_CONNECTION_STATUSES).optional(),
|
|
4807
|
+
cursor: z$1.string().max(200).optional(),
|
|
4808
|
+
limit: z$1.coerce.number().int().min(1).max(200).default(50)
|
|
4569
4809
|
});
|
|
4570
4810
|
/** An `https://host[:port]` origin with nothing after it; `http://localhost` in dev. */
|
|
4571
|
-
const vaultOriginSchema = z.string().trim().max(2048).refine((v) => {
|
|
4811
|
+
const vaultOriginSchema = z$1.string().trim().max(2048).refine((v) => {
|
|
4572
4812
|
let url;
|
|
4573
4813
|
try {
|
|
4574
4814
|
url = new URL(v);
|
|
@@ -4583,85 +4823,85 @@ const vaultOriginSchema = z.string().trim().max(2048).refine((v) => {
|
|
|
4583
4823
|
"[::1]"
|
|
4584
4824
|
].includes(url.hostname);
|
|
4585
4825
|
}, { message: "must be an https origin (scheme + host, nothing else)" });
|
|
4586
|
-
const vaultWrappingKeySchema = z.object({
|
|
4826
|
+
const vaultWrappingKeySchema = z$1.object({
|
|
4587
4827
|
jwk: vaultPublicJwkSchema,
|
|
4588
|
-
thumbprint: z.string().regex(/^[A-Za-z0-9_-]{43}$/)
|
|
4828
|
+
thumbprint: z$1.string().regex(/^[A-Za-z0-9_-]{43}$/)
|
|
4589
4829
|
});
|
|
4590
|
-
z.object({
|
|
4591
|
-
code: z.string().trim().min(16).max(200),
|
|
4830
|
+
z$1.object({
|
|
4831
|
+
code: z$1.string().trim().min(16).max(200),
|
|
4592
4832
|
origin: vaultOriginSchema,
|
|
4593
4833
|
wrappingKey: vaultWrappingKeySchema,
|
|
4594
4834
|
catalog: vaultCatalogSchema,
|
|
4595
|
-
sdkVersion: z.string().trim().min(1).max(40)
|
|
4835
|
+
sdkVersion: z$1.string().trim().min(1).max(40)
|
|
4596
4836
|
});
|
|
4597
|
-
z.object({
|
|
4837
|
+
z$1.object({
|
|
4598
4838
|
catalog: vaultCatalogSchema,
|
|
4599
|
-
sdkVersion: z.string().trim().min(1).max(40)
|
|
4839
|
+
sdkVersion: z$1.string().trim().min(1).max(40)
|
|
4600
4840
|
});
|
|
4601
|
-
z.object({ wrappingKey: vaultWrappingKeySchema });
|
|
4841
|
+
z$1.object({ wrappingKey: vaultWrappingKeySchema });
|
|
4602
4842
|
/** One encrypted slot, as produced by Connect or the executor. */
|
|
4603
|
-
const vaultEncryptedSlotSchema = z.object({
|
|
4604
|
-
slot: z.enum(VAULT_SLOTS),
|
|
4605
|
-
revision: z.number().int().min(1),
|
|
4606
|
-
ciphertext: z.string().startsWith("sc1.").max(VAULT_CIPHERTEXT_MAX_CHARS),
|
|
4607
|
-
wrappedDek: z.string().startsWith("wd1.").max(2048),
|
|
4608
|
-
keyThumbprint: z.string().regex(/^[A-Za-z0-9_-]{43}$/)
|
|
4843
|
+
const vaultEncryptedSlotSchema = z$1.object({
|
|
4844
|
+
slot: z$1.enum(VAULT_SLOTS),
|
|
4845
|
+
revision: z$1.number().int().min(1),
|
|
4846
|
+
ciphertext: z$1.string().startsWith("sc1.").max(VAULT_CIPHERTEXT_MAX_CHARS),
|
|
4847
|
+
wrappedDek: z$1.string().startsWith("wd1.").max(2048),
|
|
4848
|
+
keyThumbprint: z$1.string().regex(/^[A-Za-z0-9_-]{43}$/)
|
|
4609
4849
|
});
|
|
4610
|
-
z.object({
|
|
4611
|
-
slots: z.array(vaultEncryptedSlotSchema).min(1).max(2),
|
|
4850
|
+
z$1.object({
|
|
4851
|
+
slots: z$1.array(vaultEncryptedSlotSchema).min(1).max(2),
|
|
4612
4852
|
/** "ava@example.com" / "@ava" — shown to the platform and the org, never used for auth. */
|
|
4613
|
-
accountLabel: z.string().trim().min(1).max(200).optional()
|
|
4853
|
+
accountLabel: z$1.string().trim().min(1).max(200).optional()
|
|
4614
4854
|
});
|
|
4615
|
-
z.object({
|
|
4855
|
+
z$1.object({
|
|
4616
4856
|
/** The revision the writer read; the write lands as `expectedRevision + 1`. */
|
|
4617
|
-
expectedRevision: z.number().int().min(0),
|
|
4618
|
-
ciphertext: z.string().startsWith("sc1.").max(VAULT_CIPHERTEXT_MAX_CHARS),
|
|
4619
|
-
wrappedDek: z.string().startsWith("wd1.").max(2048),
|
|
4620
|
-
keyThumbprint: z.string().regex(/^[A-Za-z0-9_-]{43}$/)
|
|
4857
|
+
expectedRevision: z$1.number().int().min(0),
|
|
4858
|
+
ciphertext: z$1.string().startsWith("sc1.").max(VAULT_CIPHERTEXT_MAX_CHARS),
|
|
4859
|
+
wrappedDek: z$1.string().startsWith("wd1.").max(2048),
|
|
4860
|
+
keyThumbprint: z$1.string().regex(/^[A-Za-z0-9_-]{43}$/)
|
|
4621
4861
|
});
|
|
4622
|
-
z.discriminatedUnion("status", [z.object({
|
|
4623
|
-
status: z.literal("reauth_required"),
|
|
4624
|
-
reason: z.string().trim().min(1).max(120)
|
|
4625
|
-
}), z.object({ status: z.literal("active") })]);
|
|
4626
|
-
z.object({ claimToken: z.string().min(16).max(200) });
|
|
4627
|
-
z.object({ token: z.string().min(16).max(200) });
|
|
4628
|
-
z.object({ consentVersion: z.literal(1) });
|
|
4629
|
-
z.object({ connectionId: z.string().trim().min(1).max(64) });
|
|
4630
|
-
z.object({
|
|
4862
|
+
z$1.discriminatedUnion("status", [z$1.object({
|
|
4863
|
+
status: z$1.literal("reauth_required"),
|
|
4864
|
+
reason: z$1.string().trim().min(1).max(120)
|
|
4865
|
+
}), z$1.object({ status: z$1.literal("active") })]);
|
|
4866
|
+
z$1.object({ claimToken: z$1.string().min(16).max(200) });
|
|
4867
|
+
z$1.object({ token: z$1.string().min(16).max(200) });
|
|
4868
|
+
z$1.object({ consentVersion: z$1.literal(1) });
|
|
4869
|
+
z$1.object({ connectionId: z$1.string().trim().min(1).max(64) });
|
|
4870
|
+
z$1.object({
|
|
4631
4871
|
login: vaultEncryptedSlotSchema.refine((slot) => slot.slot === "login", "must be the login slot").optional(),
|
|
4632
|
-
accountLabel: z.string().trim().min(1).max(200).optional()
|
|
4872
|
+
accountLabel: z$1.string().trim().min(1).max(200).optional()
|
|
4633
4873
|
});
|
|
4634
|
-
z.object({
|
|
4874
|
+
z$1.object({
|
|
4635
4875
|
userRef: vaultUserRefSchema,
|
|
4636
|
-
connectionId: z.string().trim().min(1).max(64),
|
|
4637
|
-
request: z.object({
|
|
4638
|
-
method: z.string().trim().toUpperCase().regex(/^[A-Z]{3,10}$/),
|
|
4639
|
-
url: z.url().max(8192),
|
|
4640
|
-
headers: z.record(z.string().max(256), z.string().max(16384)).optional(),
|
|
4876
|
+
connectionId: z$1.string().trim().min(1).max(64),
|
|
4877
|
+
request: z$1.object({
|
|
4878
|
+
method: z$1.string().trim().toUpperCase().regex(/^[A-Z]{3,10}$/),
|
|
4879
|
+
url: z$1.url().max(8192),
|
|
4880
|
+
headers: z$1.record(z$1.string().max(256), z$1.string().max(16384)).optional(),
|
|
4641
4881
|
/** UTF-8 text. Binary bodies are out of scope for V1. */
|
|
4642
|
-
body: z.string().max(4194304).optional()
|
|
4882
|
+
body: z$1.string().max(4194304).optional()
|
|
4643
4883
|
})
|
|
4644
4884
|
});
|
|
4645
|
-
const connectionIdSchema = z.string().trim().min(1).max(64);
|
|
4646
|
-
const leaseIdSchema = z.string().trim().min(1).max(64);
|
|
4647
|
-
z.object({
|
|
4885
|
+
const connectionIdSchema = z$1.string().trim().min(1).max(64);
|
|
4886
|
+
const leaseIdSchema = z$1.string().trim().min(1).max(64);
|
|
4887
|
+
z$1.object({
|
|
4648
4888
|
userRef: vaultUserRefSchema,
|
|
4649
4889
|
connectionId: connectionIdSchema,
|
|
4650
4890
|
/** How long the agent may hold the browser. Clamped to the site's ceiling. */
|
|
4651
|
-
keepAliveMs: z.number().int().min(VAULT_LEASE_MIN_MS).max(VAULT_LEASE_MAX_MS).optional()
|
|
4891
|
+
keepAliveMs: z$1.number().int().min(VAULT_LEASE_MIN_MS).max(VAULT_LEASE_MAX_MS).optional()
|
|
4652
4892
|
});
|
|
4653
|
-
z.object({
|
|
4893
|
+
z$1.object({
|
|
4654
4894
|
userRef: vaultUserRefSchema,
|
|
4655
4895
|
leaseId: leaseIdSchema,
|
|
4656
|
-
field: z.enum(VAULT_FILL_FIELDS)
|
|
4896
|
+
field: z$1.enum(VAULT_FILL_FIELDS)
|
|
4657
4897
|
});
|
|
4658
|
-
z.object({
|
|
4898
|
+
z$1.object({
|
|
4659
4899
|
userRef: vaultUserRefSchema,
|
|
4660
4900
|
leaseId: leaseIdSchema,
|
|
4661
4901
|
/** Write the rotated session back (default). `false` drops the browser without saving. */
|
|
4662
|
-
save: z.boolean().default(true)
|
|
4902
|
+
save: z$1.boolean().default(true)
|
|
4663
4903
|
});
|
|
4664
|
-
z.object({
|
|
4904
|
+
z$1.object({
|
|
4665
4905
|
userRef: vaultUserRefSchema,
|
|
4666
4906
|
leaseId: leaseIdSchema
|
|
4667
4907
|
});
|
|
@@ -5045,6 +5285,19 @@ async function decryptDataKey(material, wrapped) {
|
|
|
5045
5285
|
throw new SeekritCryptoError("DECRYPT_FAILED", "data key unwrap failed: wrong key or tampered blob");
|
|
5046
5286
|
}
|
|
5047
5287
|
}
|
|
5288
|
+
// ../../packages/crypto/src/managed-credential.ts
|
|
5289
|
+
const generateCredentialRecipientKeyPair = generateKeyPair;
|
|
5290
|
+
/** Only called on the consumer; the API receives the wrapped bytes. */
|
|
5291
|
+
async function unwrapManagedCredential(wrapped, privateKeyJwk) {
|
|
5292
|
+
const bytes = await unwrapDek(wrapped, await importPrivateKey(privateKeyJwk));
|
|
5293
|
+
try {
|
|
5294
|
+
const result = JSON.parse(new TextDecoder().decode(bytes));
|
|
5295
|
+
if (!result || typeof result !== "object" || Array.isArray(result) || !Object.values(result).every((value) => typeof value === "string")) throw new Error();
|
|
5296
|
+
return result;
|
|
5297
|
+
} catch {
|
|
5298
|
+
throw new SeekritCryptoError("DECRYPT_FAILED", "invalid managed credential");
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5048
5301
|
// ../../packages/crypto/src/random.ts
|
|
5049
5302
|
const ALPHABETS = {
|
|
5050
5303
|
alphanumeric: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
@@ -5887,7 +6140,7 @@ async function createAgentTaskToken() {
|
|
|
5887
6140
|
};
|
|
5888
6141
|
}
|
|
5889
6142
|
// package.json
|
|
5890
|
-
var version = "2.
|
|
6143
|
+
var version = "2.7.0";
|
|
5891
6144
|
// ../../packages/api-client/src/index.ts
|
|
5892
6145
|
/**
|
|
5893
6146
|
* The proposer's note travels as a header, not a body field: an unprotected
|
|
@@ -7052,13 +7305,13 @@ const CLI_CLIENT = `cli/${version}`;
|
|
|
7052
7305
|
* bearer of nothing: a 401 where the honest answer is "you have no
|
|
7053
7306
|
* credentials", pointing at the API instead of at the missing variable.
|
|
7054
7307
|
*/
|
|
7055
|
-
function present(value) {
|
|
7308
|
+
function present$1(value) {
|
|
7056
7309
|
const trimmed = value?.trim();
|
|
7057
7310
|
return trimmed ? trimmed : void 0;
|
|
7058
7311
|
}
|
|
7059
7312
|
function tryBuildContext(dotenvVars = {}) {
|
|
7060
7313
|
const config = readGlobalConfig();
|
|
7061
|
-
const fromEnv = (key) => present(process.env[key]) ?? present(dotenvVars[key]);
|
|
7314
|
+
const fromEnv = (key) => present$1(process.env[key]) ?? present$1(dotenvVars[key]);
|
|
7062
7315
|
const apiUrl = fromEnv("SEEKRIT_API_URL") ?? config.apiUrl ?? "https://api.seekrit.dev";
|
|
7063
7316
|
const token = fromEnv("SEEKRIT_TOKEN") ?? config.token ?? config.sessionToken;
|
|
7064
7317
|
const devUser = fromEnv("SEEKRIT_DEV_USER") ?? config.devUser;
|
|
@@ -7103,7 +7356,7 @@ function describeError(err) {
|
|
|
7103
7356
|
if (!(err instanceof Error)) return String(err);
|
|
7104
7357
|
if (err.message !== "fetch failed") return err.message;
|
|
7105
7358
|
const config = readGlobalConfig();
|
|
7106
|
-
const apiUrl = present(process.env.SEEKRIT_API_URL) ?? config.apiUrl ?? "https://api.seekrit.dev";
|
|
7359
|
+
const apiUrl = present$1(process.env.SEEKRIT_API_URL) ?? config.apiUrl ?? "https://api.seekrit.dev";
|
|
7107
7360
|
const cause = err.cause;
|
|
7108
7361
|
const reason = cause instanceof Error ? cause.code ?? cause.message : void 0;
|
|
7109
7362
|
return `cannot reach the seekrit API at ${apiUrl}${reason ? ` (${reason})` : ""} — check SEEKRIT_API_URL, your network, and that the API is up`;
|
|
@@ -7658,7 +7911,7 @@ function defaultIsAlive(pid) {
|
|
|
7658
7911
|
}
|
|
7659
7912
|
}
|
|
7660
7913
|
/** Write a file so a reader sees the old bytes or the new ones, never half of each. */
|
|
7661
|
-
function writeAtomic(path, content) {
|
|
7914
|
+
function writeAtomic$1(path, content) {
|
|
7662
7915
|
mkdirSync(dirname(path), {
|
|
7663
7916
|
recursive: true,
|
|
7664
7917
|
mode: 448
|
|
@@ -7725,7 +7978,7 @@ var AgentLoginSession = class {
|
|
|
7725
7978
|
await this.recoverLeftover();
|
|
7726
7979
|
await this.acquire();
|
|
7727
7980
|
this.registerHolder();
|
|
7728
|
-
writeAtomic(this.o.path, this.synced);
|
|
7981
|
+
writeAtomic$1(this.o.path, this.synced);
|
|
7729
7982
|
this.placed = true;
|
|
7730
7983
|
this.lastRemotePoll = this.o.now();
|
|
7731
7984
|
this.timer = setInterval(() => void this.tick(), this.o.localPollMs);
|
|
@@ -7899,7 +8152,7 @@ var AgentLoginSession = class {
|
|
|
7899
8152
|
this.version = snapshot.version;
|
|
7900
8153
|
continue;
|
|
7901
8154
|
}
|
|
7902
|
-
writeAtomic(this.o.path, remote.content);
|
|
8155
|
+
writeAtomic$1(this.o.path, remote.content);
|
|
7903
8156
|
this.adopt(snapshot, remote);
|
|
7904
8157
|
this.o.log(`another sandbox refreshed the ${this.o.adapter.label} login at the same moment; switched to its copy. If ${this.o.adapter.label} reports a sign-in error, run \`seekrit agent-login ${this.o.adapter.id}\` again.`);
|
|
7905
8158
|
return;
|
|
@@ -7925,7 +8178,7 @@ var AgentLoginSession = class {
|
|
|
7925
8178
|
const local = readIfExists(this.o.path);
|
|
7926
8179
|
if (local !== null && local !== this.synced) return;
|
|
7927
8180
|
if (remote.content !== this.synced && !this.fresher(this.synced, remote.content)) {
|
|
7928
|
-
writeAtomic(this.o.path, remote.content);
|
|
8181
|
+
writeAtomic$1(this.o.path, remote.content);
|
|
7929
8182
|
this.o.log(`picked up a ${this.o.adapter.label} login another sandbox refreshed`);
|
|
7930
8183
|
}
|
|
7931
8184
|
if (remote.claim?.holder !== this.o.holder) this.claimUntil = null;
|
|
@@ -7976,7 +8229,7 @@ var AgentLoginSession = class {
|
|
|
7976
8229
|
secret: this.o.secretName,
|
|
7977
8230
|
holders
|
|
7978
8231
|
};
|
|
7979
|
-
writeAtomic(this.markerPath, `${JSON.stringify(marker, null, 2)}\n`);
|
|
8232
|
+
writeAtomic$1(this.markerPath, `${JSON.stringify(marker, null, 2)}\n`);
|
|
7980
8233
|
}
|
|
7981
8234
|
/** Several runs on one machine can share a home; the last one out cleans up. */
|
|
7982
8235
|
registerHolder() {
|
|
@@ -9560,7 +9813,7 @@ async function syncGrantsForGroupLink(ctx, orgId, envId, envSlug, groupId) {
|
|
|
9560
9813
|
}
|
|
9561
9814
|
// src/kms.ts
|
|
9562
9815
|
/** Collect a repeatable option into a list. */
|
|
9563
|
-
function collect$
|
|
9816
|
+
function collect$10(value, acc = []) {
|
|
9564
9817
|
acc.push(value);
|
|
9565
9818
|
return acc;
|
|
9566
9819
|
}
|
|
@@ -9628,7 +9881,7 @@ async function kmsRecoverMaterial(ctx, orgId, keyId, version) {
|
|
|
9628
9881
|
}
|
|
9629
9882
|
function registerKmsCommands(program) {
|
|
9630
9883
|
const kms = program.command("kms").description("managed keys for application-layer encryption & signing (client-side)");
|
|
9631
|
-
kms.command("create").description("create a managed key (material is generated locally and wrapped, never sent)").requiredOption("--name <name>", "org-unique key name").requiredOption("--purpose <purpose>", "encrypt | sign").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("--app <slug>", "scope the key to an application").option("--group <slug>", "scope the key to a group").option("--grant-user <email>", "also grant an org member (repeatable)", collect$
|
|
9884
|
+
kms.command("create").description("create a managed key (material is generated locally and wrapped, never sent)").requiredOption("--name <name>", "org-unique key name").requiredOption("--purpose <purpose>", "encrypt | sign").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("--app <slug>", "scope the key to an application").option("--group <slug>", "scope the key to a group").option("--grant-user <email>", "also grant an org member (repeatable)", collect$10, []).option("--grant-token <tokenId>", "also grant a service token (repeatable)", collect$10, []).action(async (options) => {
|
|
9632
9885
|
if (options.purpose !== "encrypt" && options.purpose !== "sign") fail("--purpose must be encrypt or sign");
|
|
9633
9886
|
if (options.app && options.group) fail("pass at most one of --app or --group");
|
|
9634
9887
|
const ctx = buildContext();
|
|
@@ -9849,7 +10102,7 @@ function registerKmsCommands(program) {
|
|
|
9849
10102
|
}
|
|
9850
10103
|
// src/recovery.ts
|
|
9851
10104
|
/** Collect a repeatable option into a list. */
|
|
9852
|
-
function collect$
|
|
10105
|
+
function collect$9(value, acc = []) {
|
|
9853
10106
|
acc.push(value);
|
|
9854
10107
|
return acc;
|
|
9855
10108
|
}
|
|
@@ -9946,7 +10199,7 @@ function registerRecoveryCommands(program) {
|
|
|
9946
10199
|
for (const cst of status.custodians) console.log(` - ${cst.label ?? cst.principalId} (${cst.principalType}, share #${cst.shareIndex})`);
|
|
9947
10200
|
if (status.coverage.unprotectedEnvIds.length > 0) console.log(`${status.coverage.unprotectedEnvIds.length} environment(s) not yet protected — run \`seekrit recovery sync\``);
|
|
9948
10201
|
});
|
|
9949
|
-
recovery.command("setup").description("enable recovery: split a fresh recovery key across custodians").requiredOption("--threshold <M>", "custodians required to recover (M in M-of-N)").option("--custodian <email|skt_id>", "a recovery custodian (repeatable)", collect$
|
|
10202
|
+
recovery.command("setup").description("enable recovery: split a fresh recovery key across custodians").requiredOption("--threshold <M>", "custodians required to recover (M in M-of-N)").option("--custodian <email|skt_id>", "a recovery custodian (repeatable)", collect$9, []).option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").action(async (options) => {
|
|
9950
10203
|
const ctx = buildContext();
|
|
9951
10204
|
const org = await resolveOrg(ctx, options.org);
|
|
9952
10205
|
const config = await buildRecoveryConfig(ctx, org.id, options.threshold, options.custodian);
|
|
@@ -9964,7 +10217,7 @@ function registerRecoveryCommands(program) {
|
|
|
9964
10217
|
const { wrapped, skipped } = await syncRecoveryGrants(ctx, (await resolveOrg(ctx, options.org)).id);
|
|
9965
10218
|
console.error(`recovery-protected ${wrapped} environment(s); skipped ${skipped} you cannot decrypt`);
|
|
9966
10219
|
});
|
|
9967
|
-
recovery.command("rotate").description("rotate the recovery key (new keypair, custodians, and env re-wraps)").requiredOption("--threshold <M>", "custodians required to recover (M in M-of-N)").option("--custodian <email|skt_id>", "a recovery custodian (repeatable)", collect$
|
|
10220
|
+
recovery.command("rotate").description("rotate the recovery key (new keypair, custodians, and env re-wraps)").requiredOption("--threshold <M>", "custodians required to recover (M in M-of-N)").option("--custodian <email|skt_id>", "a recovery custodian (repeatable)", collect$9, []).option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").action(async (options) => {
|
|
9968
10221
|
const ctx = buildContext();
|
|
9969
10222
|
const org = await resolveOrg(ctx, options.org);
|
|
9970
10223
|
const config = await buildRecoveryConfig(ctx, org.id, options.threshold, options.custodian);
|
|
@@ -11103,7 +11356,7 @@ function formatSecrets(values, format) {
|
|
|
11103
11356
|
* `buildContext()` in any of them silently breaks the promise the feature makes.
|
|
11104
11357
|
*/
|
|
11105
11358
|
/** Collect a repeatable option into a list. */
|
|
11106
|
-
function collect$
|
|
11359
|
+
function collect$8(value, acc = []) {
|
|
11107
11360
|
acc.push(value);
|
|
11108
11361
|
return acc;
|
|
11109
11362
|
}
|
|
@@ -11380,7 +11633,7 @@ function registerArchiveCommands(program) {
|
|
|
11380
11633
|
} catch {}
|
|
11381
11634
|
fail("none of the recovery shares in this archive unwrap with that key");
|
|
11382
11635
|
});
|
|
11383
|
-
archive.command("decrypt <file>").description("decrypt an archive's secrets with your own key (offline)").option("-o, --out <dir>", "write one file per environment into this directory").option("--stdout", "print plaintext to stdout instead of writing files").option("--env <label>", "only this environment (app/env, group@env, slug, or id)").option("--format <format>", "dotenv | json | shell", "dotenv").option("--token <skt_…>", "decrypt as a service token instead of a passphrase").option("--key-file <path>", "decrypt with a private key JWK file").option("--share <file>", "custodian share for an offline quorum (repeatable)", collect$
|
|
11636
|
+
archive.command("decrypt <file>").description("decrypt an archive's secrets with your own key (offline)").option("-o, --out <dir>", "write one file per environment into this directory").option("--stdout", "print plaintext to stdout instead of writing files").option("--env <label>", "only this environment (app/env, group@env, slug, or id)").option("--format <format>", "dotenv | json | shell", "dotenv").option("--token <skt_…>", "decrypt as a service token instead of a passphrase").option("--key-file <path>", "decrypt with a private key JWK file").option("--share <file>", "custodian share for an offline quorum (repeatable)", collect$8).option("-y, --yes", "skip the confirmation when printing plaintext to stdout").action(async (file, options) => {
|
|
11384
11637
|
if (!options.out && !options.stdout) fail("choose a destination: --out <dir> to write files, or --stdout to print plaintext");
|
|
11385
11638
|
if (![
|
|
11386
11639
|
"dotenv",
|
|
@@ -12612,6 +12865,81 @@ function registerDoctorCommand(program) {
|
|
|
12612
12865
|
if (checks.some((c) => c.status === "fail")) process.exitCode = 1;
|
|
12613
12866
|
});
|
|
12614
12867
|
}
|
|
12868
|
+
// src/dynamic.ts
|
|
12869
|
+
function dynamicTtl(value) {
|
|
12870
|
+
const match = /^(\d+)([smhd]?)$/.exec(value);
|
|
12871
|
+
if (!match) throw new Error("invalid TTL (use 15m, 1h, or 1d)");
|
|
12872
|
+
const seconds = Number(match[1]) * ({
|
|
12873
|
+
s: 1,
|
|
12874
|
+
m: 60,
|
|
12875
|
+
h: 3600,
|
|
12876
|
+
d: 86400
|
|
12877
|
+
}[match[2] || "s"] ?? 1);
|
|
12878
|
+
if (!Number.isSafeInteger(seconds) || seconds < 60 || seconds > 604800) throw new Error("TTL must be 1 minute–7 days");
|
|
12879
|
+
return seconds;
|
|
12880
|
+
}
|
|
12881
|
+
function registerDynamicCommands(program) {
|
|
12882
|
+
const dynamic = program.command("dynamic").description("manage provider-issued temporary credentials");
|
|
12883
|
+
const targets = dynamic.command("target").description("manage dynamic credential targets");
|
|
12884
|
+
targets.command("add").requiredOption("--name <name>", "target name").requiredOption("--config <file>", "non-secret provider configuration JSON").option("--org <slug>", "organization").description("register a target; read its admin credential from stdin and encrypt it locally").action(async (options) => {
|
|
12885
|
+
const config = managedTargetConfigSchema.parse(JSON.parse(readFileSync(options.config, "utf8")));
|
|
12886
|
+
if (process.stdin.isTTY) fail("pipe the admin credential on stdin");
|
|
12887
|
+
const secret = (await readStdin()).trim();
|
|
12888
|
+
if (!secret) fail("admin credential is empty");
|
|
12889
|
+
const ctx = buildContext();
|
|
12890
|
+
const org = await resolveOrg(ctx, options.org);
|
|
12891
|
+
const { publicKeyJwk } = await ctx.client.getLeaseBrokerKey(org.id);
|
|
12892
|
+
const wrappedAdminSecret = await wrapDek(new TextEncoder().encode(secret), publicKeyJwk);
|
|
12893
|
+
const { target } = await ctx.client.registerLeaseTarget(org.id, {
|
|
12894
|
+
name: options.name,
|
|
12895
|
+
config,
|
|
12896
|
+
wrappedAdminSecret
|
|
12897
|
+
});
|
|
12898
|
+
console.log(JSON.stringify(target));
|
|
12899
|
+
});
|
|
12900
|
+
targets.command("list").option("--org <slug>", "organization").action(async (options) => {
|
|
12901
|
+
const ctx = buildContext();
|
|
12902
|
+
const org = await resolveOrg(ctx, options.org);
|
|
12903
|
+
const { targets } = await ctx.client.listLeaseTargets(org.id);
|
|
12904
|
+
console.log(JSON.stringify(targets.filter((target) => isManagedTarget(target))));
|
|
12905
|
+
});
|
|
12906
|
+
dynamic.command("lease <target>").option("--ttl <duration>", "credential lifetime", "1h").option("--org <slug>", "organization").description("mint and decrypt a credential locally; outputs JSON once").action(async (reference, options) => {
|
|
12907
|
+
const ttlSeconds = dynamicTtl(options.ttl);
|
|
12908
|
+
const ctx = buildContext();
|
|
12909
|
+
const org = await resolveOrg(ctx, options.org);
|
|
12910
|
+
const { targets } = await ctx.client.listLeaseTargets(org.id);
|
|
12911
|
+
const matches = targets.filter((target) => target.id === reference || target.name === reference);
|
|
12912
|
+
if (matches.length !== 1) fail("target must identify exactly one registered target");
|
|
12913
|
+
const target = matches[0];
|
|
12914
|
+
if (!target) fail("target not found");
|
|
12915
|
+
const config = managedTargetConfigSchema.parse(target.config);
|
|
12916
|
+
const recipient = await generateCredentialRecipientKeyPair();
|
|
12917
|
+
const result = await ctx.client.mintLease(org.id, {
|
|
12918
|
+
provider: config.provider,
|
|
12919
|
+
targetId: target.id,
|
|
12920
|
+
recipientPublicKey: recipient.publicKeyJwk,
|
|
12921
|
+
ttlSeconds
|
|
12922
|
+
});
|
|
12923
|
+
try {
|
|
12924
|
+
const credential = await unwrapManagedCredential(result.credential.wrappedCredential, recipient.privateKeyJwk);
|
|
12925
|
+
console.log(JSON.stringify({
|
|
12926
|
+
leaseId: result.lease.id,
|
|
12927
|
+
expiresAt: result.credential.expiresAt,
|
|
12928
|
+
credential
|
|
12929
|
+
}));
|
|
12930
|
+
} catch (error) {
|
|
12931
|
+
await ctx.client.revokeLease(org.id, result.lease.id);
|
|
12932
|
+
throw error;
|
|
12933
|
+
}
|
|
12934
|
+
});
|
|
12935
|
+
dynamic.command("revoke <leaseId>").option("--org <slug>", "organization").option("-y, --yes", "skip the confirmation prompt").description("revoke a temporary credential now").action(async (leaseId, options) => {
|
|
12936
|
+
await confirmDestructive(options.yes, `Revoke temporary credential ${leaseId}?`);
|
|
12937
|
+
const ctx = buildContext();
|
|
12938
|
+
const org = await resolveOrg(ctx, options.org);
|
|
12939
|
+
await ctx.client.revokeLease(org.id, leaseId);
|
|
12940
|
+
console.log(JSON.stringify({ revoked: leaseId }));
|
|
12941
|
+
});
|
|
12942
|
+
}
|
|
12615
12943
|
// src/gcp.ts
|
|
12616
12944
|
/**
|
|
12617
12945
|
* `seekrit gcp` — temporary GCP credentials via IAM Credentials
|
|
@@ -13123,134 +13451,772 @@ function registerHoneyTokenCommands(program) {
|
|
|
13123
13451
|
console.error(`${honeyTokenId} deleted`);
|
|
13124
13452
|
});
|
|
13125
13453
|
}
|
|
13126
|
-
// src/
|
|
13127
|
-
/**
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
const
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
const clientSecret = fromEnv("SEEKRIT_CLIENT_SECRET") ?? config.clientSecret;
|
|
13137
|
-
if (!clientId || !clientSecret) return null;
|
|
13138
|
-
return {
|
|
13139
|
-
clientId,
|
|
13140
|
-
clientSecret
|
|
13454
|
+
// src/run-command.ts
|
|
13455
|
+
/** Keep signal handlers installed from provisioning through asynchronous cleanup. */
|
|
13456
|
+
async function runCommand(cmd, args, prepare, cleanup) {
|
|
13457
|
+
let child;
|
|
13458
|
+
let forwarded;
|
|
13459
|
+
let result = { code: 1 };
|
|
13460
|
+
const stragglers = /* @__PURE__ */ new Set();
|
|
13461
|
+
const collect = () => {
|
|
13462
|
+
if (process.platform === "win32" || !child?.pid) return;
|
|
13463
|
+
for (const pid of teardownTargets(child.pid)) stragglers.add(pid);
|
|
13141
13464
|
};
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
|
|
13147
|
-
|
|
13148
|
-
|
|
13149
|
-
const fromEnv = (key) => process.env[key] ?? dotenvVars[key];
|
|
13150
|
-
if (fromEnv("SEEKRIT_TOKEN") || fromEnv("SEEKRIT_DEV_USER")) return true;
|
|
13151
|
-
const config = readGlobalConfig();
|
|
13152
|
-
return Boolean(config.token || config.sessionToken || config.devUser);
|
|
13153
|
-
}
|
|
13154
|
-
/** Mint a fresh org-scoped admin token using M2M credentials (keyless). */
|
|
13155
|
-
async function mintAdminToken(apiUrl, creds) {
|
|
13156
|
-
const client = new SeekritClient({
|
|
13157
|
-
baseUrl: apiUrl,
|
|
13158
|
-
auth: {
|
|
13159
|
-
type: "m2m",
|
|
13160
|
-
clientId: creds.clientId,
|
|
13161
|
-
clientSecret: creds.clientSecret
|
|
13162
|
-
},
|
|
13163
|
-
client: `cli/${version}`
|
|
13164
|
-
});
|
|
13165
|
-
const { orgs } = await client.listOrgs();
|
|
13166
|
-
const org = orgs[0];
|
|
13167
|
-
if (!org) throw new Error("machine client has no organization — sign up first (POST /signup)");
|
|
13168
|
-
const created = await createServiceToken();
|
|
13169
|
-
await client.createToken(org.id, {
|
|
13170
|
-
name: "agent-admin (auto)",
|
|
13171
|
-
tokenId: created.tokenId,
|
|
13172
|
-
tokenHash: created.tokenHash,
|
|
13173
|
-
publicKeyJwk: created.publicKeyJwk,
|
|
13174
|
-
role: "admin",
|
|
13175
|
-
environmentId: null
|
|
13176
|
-
});
|
|
13177
|
-
return created.token;
|
|
13178
|
-
}
|
|
13179
|
-
/**
|
|
13180
|
-
* If M2M credentials are configured (and no explicit token/dev-user is), ensure
|
|
13181
|
-
* an admin token exists for them — minting and caching one on first use — and
|
|
13182
|
-
* expose it as `SEEKRIT_TOKEN` so the normal credential resolution
|
|
13183
|
-
* ({@link tryBuildContext}) picks it up. A no-op (returns null) when there is
|
|
13184
|
-
* nothing to bootstrap. Throws only when credentials ARE present but minting
|
|
13185
|
-
* fails, so the reason surfaces instead of a later opaque "no credentials".
|
|
13186
|
-
*/
|
|
13187
|
-
async function ensureM2mAdminToken(dotenvVars = {}) {
|
|
13188
|
-
if (hasExplicitCredential(dotenvVars)) return null;
|
|
13189
|
-
const creds = readM2mCreds(dotenvVars);
|
|
13190
|
-
if (!creds) return null;
|
|
13191
|
-
const cached = readCachedM2mToken(creds.clientId);
|
|
13192
|
-
if (cached) {
|
|
13193
|
-
let usable = false;
|
|
13194
|
-
try {
|
|
13195
|
-
serviceTokenAuth(cached);
|
|
13196
|
-
usable = true;
|
|
13465
|
+
const forward = (signal) => {
|
|
13466
|
+
forwarded ??= signal;
|
|
13467
|
+
if (!child || child.exitCode !== null || child.signalCode !== null) return;
|
|
13468
|
+
collect();
|
|
13469
|
+
child.kill(signal);
|
|
13470
|
+
for (const pid of stragglers) try {
|
|
13471
|
+
process.kill(pid, signal);
|
|
13197
13472
|
} catch {}
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
13473
|
+
};
|
|
13474
|
+
const signals = [
|
|
13475
|
+
"SIGINT",
|
|
13476
|
+
"SIGTERM",
|
|
13477
|
+
"SIGHUP",
|
|
13478
|
+
"SIGQUIT"
|
|
13479
|
+
];
|
|
13480
|
+
for (const signal of signals) process.on(signal, forward);
|
|
13481
|
+
try {
|
|
13482
|
+
const env = await prepare(() => forwarded !== void 0);
|
|
13483
|
+
if (!forwarded) {
|
|
13484
|
+
const running = spawn(cmd, args, {
|
|
13485
|
+
stdio: "inherit",
|
|
13486
|
+
env
|
|
13487
|
+
});
|
|
13488
|
+
child = running;
|
|
13489
|
+
result = await new Promise((resolve, reject) => {
|
|
13490
|
+
running.once("exit", (code, signal) => resolve({
|
|
13491
|
+
code: code ?? 1,
|
|
13492
|
+
signal: signal ?? void 0
|
|
13493
|
+
}));
|
|
13494
|
+
running.once("error", (err) => reject(/* @__PURE__ */ new Error(`failed to start ${cmd}: ${err.message}`)));
|
|
13495
|
+
});
|
|
13201
13496
|
}
|
|
13202
|
-
}
|
|
13203
|
-
const token = await mintAdminToken(process.env.SEEKRIT_API_URL ?? dotenvVars.SEEKRIT_API_URL ?? readGlobalConfig().apiUrl ?? "https://api.seekrit.dev", creds);
|
|
13204
|
-
cacheM2mToken(creds.clientId, token);
|
|
13205
|
-
process.env.SEEKRIT_TOKEN = token;
|
|
13206
|
-
return token;
|
|
13207
|
-
}
|
|
13208
|
-
/** Lowercase, hyphenated, at most 40 characters — what `slugSchema` accepts. */
|
|
13209
|
-
function slugify(name) {
|
|
13210
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/g, "") || "workspace";
|
|
13211
|
-
}
|
|
13212
|
-
/** `acme-storefront` → `Acme Storefront`: a slug-ish name as a human reads it. */
|
|
13213
|
-
function titleCase(name) {
|
|
13214
|
-
return name.split(/[^A-Za-z0-9]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
13215
|
-
}
|
|
13216
|
-
/**
|
|
13217
|
-
* The project's name, from whichever manifest the directory carries — the
|
|
13218
|
-
* package name without its npm scope, the Python or Rust package name, the last
|
|
13219
|
-
* segment of a Go module path — falling back to the directory's own name. The
|
|
13220
|
-
* manifests are read leniently (a regex, not a parser) because a malformed one
|
|
13221
|
-
* should cost the default, not the command.
|
|
13222
|
-
*/
|
|
13223
|
-
function detectProjectName(dir = process.cwd()) {
|
|
13224
|
-
const read = (file) => {
|
|
13225
|
-
const path = join(dir, file);
|
|
13226
|
-
if (!existsSync(path)) return null;
|
|
13497
|
+
} finally {
|
|
13227
13498
|
try {
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13499
|
+
if (forwarded) {
|
|
13500
|
+
collect();
|
|
13501
|
+
await reapStragglers(stragglers, forwarded);
|
|
13502
|
+
}
|
|
13503
|
+
} finally {
|
|
13504
|
+
try {
|
|
13505
|
+
if (!await cleanup() && result.code === 0) result.code = 1;
|
|
13506
|
+
} finally {
|
|
13507
|
+
for (const signal of signals) process.off(signal, forward);
|
|
13508
|
+
}
|
|
13231
13509
|
}
|
|
13232
|
-
}
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
const name = JSON.parse(pkg).name;
|
|
13236
|
-
if (typeof name === "string" && name.trim()) return name.replace(/^@[^/]+\//, "");
|
|
13237
|
-
} catch {}
|
|
13238
|
-
const pyName = read("pyproject.toml")?.match(/^\[project\][^[]*?^name\s*=\s*"([^"]+)"/ms)?.[1];
|
|
13239
|
-
if (pyName) return pyName;
|
|
13240
|
-
const cargoName = read("Cargo.toml")?.match(/^\[package\][^[]*?^name\s*=\s*"([^"]+)"/ms)?.[1];
|
|
13241
|
-
if (cargoName) return cargoName;
|
|
13242
|
-
const goModule = read("go.mod")?.match(/^module\s+(\S+)/m)?.[1];
|
|
13243
|
-
if (goModule) return goModule.split("/").pop() ?? goModule;
|
|
13244
|
-
return basename(dir);
|
|
13510
|
+
}
|
|
13511
|
+
if (!child && forwarded) result.signal = forwarded;
|
|
13512
|
+
return result;
|
|
13245
13513
|
}
|
|
13246
13514
|
/**
|
|
13247
|
-
*
|
|
13248
|
-
*
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13515
|
+
* Every live process `seekrit run` is responsible for tearing down, from a
|
|
13516
|
+
* single `ps(1)` snapshot: each descendant of `root`, plus — when we lead our
|
|
13517
|
+
* own process group — every other member of that group.
|
|
13518
|
+
*
|
|
13519
|
+
* Two mechanisms, because each covers the other's blind spot:
|
|
13520
|
+
*
|
|
13521
|
+
* - The parent-chain walk reaches a process that left our group via
|
|
13522
|
+
* setsid(2)/setpgid(2), which no group-wide kill and no tty-generated signal
|
|
13523
|
+
* can touch. But it loses anything whose chain up to `root` has already
|
|
13524
|
+
* broken — teardown kills the middle layers first, and an orphan reparents to
|
|
13525
|
+
* init, out of reach of any walk down from `root`.
|
|
13526
|
+
* - Group membership survives exactly that: a pgid outlives the parent that
|
|
13527
|
+
* passed it down. But it misses the ones that left the group.
|
|
13528
|
+
*
|
|
13529
|
+
* The group half applies only when our pid *is* our pgid, which is the shell-job
|
|
13530
|
+
* case: the shell put this command in a new group, so every other member of it
|
|
13531
|
+
* descends from us. Nested inside a shell script or a CI runner we are not the
|
|
13532
|
+
* leader and the group holds processes we did not start, so there we use the
|
|
13533
|
+
* walk alone.
|
|
13534
|
+
*
|
|
13535
|
+
* Zombies are skipped: they are already dead and waiting to be reaped, so
|
|
13536
|
+
* counting them would stall teardown for the full grace period and then report
|
|
13537
|
+
* a kill that did nothing. Returns nothing if `ps` is unavailable; callers fall
|
|
13538
|
+
* back to signalling the immediate child.
|
|
13539
|
+
*/
|
|
13540
|
+
function teardownTargets(root) {
|
|
13541
|
+
const ps = spawnSync("ps", [
|
|
13542
|
+
"-A",
|
|
13543
|
+
"-o",
|
|
13544
|
+
"pid=,ppid=,pgid=,stat="
|
|
13545
|
+
], { encoding: "utf8" });
|
|
13546
|
+
if (ps.status !== 0 || typeof ps.stdout !== "string") return [];
|
|
13547
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
13548
|
+
const groupOf = /* @__PURE__ */ new Map();
|
|
13549
|
+
const isId = (value) => value !== void 0 && Number.isInteger(value);
|
|
13550
|
+
for (const line of ps.stdout.split("\n")) {
|
|
13551
|
+
const [pid, ppid, pgid, stat] = line.trim().split(/\s+/, 4);
|
|
13552
|
+
const [id, parent, group] = [
|
|
13553
|
+
pid,
|
|
13554
|
+
ppid,
|
|
13555
|
+
pgid
|
|
13556
|
+
].map(Number);
|
|
13557
|
+
if (!isId(id) || !isId(parent) || !isId(group)) continue;
|
|
13558
|
+
if (stat?.startsWith("Z")) continue;
|
|
13559
|
+
const siblings = childrenOf.get(parent);
|
|
13560
|
+
if (siblings) siblings.push(id);
|
|
13561
|
+
else childrenOf.set(parent, [id]);
|
|
13562
|
+
groupOf.set(id, group);
|
|
13563
|
+
}
|
|
13564
|
+
const found = /* @__PURE__ */ new Set();
|
|
13565
|
+
const queue = [root];
|
|
13566
|
+
const seen = new Set(queue);
|
|
13567
|
+
for (let pid = queue.shift(); pid !== void 0; pid = queue.shift()) for (const kid of childrenOf.get(pid) ?? []) {
|
|
13568
|
+
if (seen.has(kid)) continue;
|
|
13569
|
+
seen.add(kid);
|
|
13570
|
+
found.add(kid);
|
|
13571
|
+
queue.push(kid);
|
|
13572
|
+
}
|
|
13573
|
+
if (groupOf.get(process.pid) === process.pid) {
|
|
13574
|
+
for (const [pid, group] of groupOf) if (group === process.pid) found.add(pid);
|
|
13575
|
+
}
|
|
13576
|
+
found.delete(process.pid);
|
|
13577
|
+
found.delete(root);
|
|
13578
|
+
return [...found];
|
|
13579
|
+
}
|
|
13580
|
+
/** Whether `pid` still exists — signal 0 tests reachability, delivers nothing. */
|
|
13581
|
+
function isAlive(pid) {
|
|
13582
|
+
try {
|
|
13583
|
+
process.kill(pid, 0);
|
|
13584
|
+
return true;
|
|
13585
|
+
} catch {
|
|
13586
|
+
return false;
|
|
13587
|
+
}
|
|
13588
|
+
}
|
|
13589
|
+
/** How long a straggler gets to finish shutting down before SIGKILL. */
|
|
13590
|
+
const STRAGGLER_GRACE_MS = 2e3;
|
|
13591
|
+
/**
|
|
13592
|
+
* Once the command has exited, make sure nothing it started outlives us.
|
|
13593
|
+
*
|
|
13594
|
+
* Anything still alive here has ignored the signal we forwarded *and* lost the
|
|
13595
|
+
* parent that launched it, and we are the last process that knows its pid:
|
|
13596
|
+
* the tty won't hang it up (it is no longer in the foreground group, and the
|
|
13597
|
+
* shell has moved on), so it survives until the machine reboots. That is how
|
|
13598
|
+
* `tsx` and the server under it stacked up for days.
|
|
13599
|
+
*
|
|
13600
|
+
* So escalate rather than trust. SIGTERM first — it costs no extra time and is
|
|
13601
|
+
* a rung the SIGINT-only handlers common in dev servers don't catch — then
|
|
13602
|
+
* SIGKILL whatever is left after the grace period. Nothing is killed unless it
|
|
13603
|
+
* was asked to stop first and declined, and this runs only when we forwarded a
|
|
13604
|
+
* termination signal: a command that exits on its own having deliberately left a
|
|
13605
|
+
* daemon behind keeps working.
|
|
13606
|
+
*/
|
|
13607
|
+
async function reapStragglers(pids, signal) {
|
|
13608
|
+
let live = [...pids].filter(isAlive);
|
|
13609
|
+
if (live.length === 0) return;
|
|
13610
|
+
for (const pid of live) try {
|
|
13611
|
+
process.kill(pid, "SIGTERM");
|
|
13612
|
+
} catch {}
|
|
13613
|
+
const deadline = Date.now() + STRAGGLER_GRACE_MS;
|
|
13614
|
+
while (live.length > 0 && Date.now() < deadline) {
|
|
13615
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
13616
|
+
live = live.filter(isAlive);
|
|
13617
|
+
}
|
|
13618
|
+
if (live.length === 0) return;
|
|
13619
|
+
console.error(`seekrit: force-killed ${live.length} leftover process(es) that ignored ${signal} and SIGTERM (${live.join(", ")})`);
|
|
13620
|
+
for (const pid of live) try {
|
|
13621
|
+
process.kill(pid, "SIGKILL");
|
|
13622
|
+
} catch {}
|
|
13623
|
+
}
|
|
13624
|
+
// src/identity.ts
|
|
13625
|
+
/**
|
|
13626
|
+
* `seekrit identity` — keyless access to AWS, Google Cloud, OpenAI and Anthropic
|
|
13627
|
+
* from a seekrit workload identity, the way a hosting platform's OIDC
|
|
13628
|
+
* integration works.
|
|
13629
|
+
*
|
|
13630
|
+
* The workload authenticates to seekrit (an enrolled P-256 key, or a platform
|
|
13631
|
+
* JWT it already has), asks for a short-lived identity token addressed to each
|
|
13632
|
+
* provider, and writes it to the token file that provider's own SDK already
|
|
13633
|
+
* knows how to read. The provider verifies the token against the org's issuer
|
|
13634
|
+
* (`https://oidc.seekrit.dev/<orgId>`) and its own trust policy, and mints its own short-lived
|
|
13635
|
+
* credential. No provider key is stored anywhere — not here, not in seekrit.
|
|
13636
|
+
*
|
|
13637
|
+
* Identity, not secrets: nothing on this path decrypts anything, and the API
|
|
13638
|
+
* signs these tokens with its own issuer key, so the zero-knowledge invariant is
|
|
13639
|
+
* untouched.
|
|
13640
|
+
*/
|
|
13641
|
+
const DEFAULT_ISSUER = "https://api.seekrit.dev";
|
|
13642
|
+
const CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
13643
|
+
const TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange";
|
|
13644
|
+
const JWT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:jwt";
|
|
13645
|
+
function present(value) {
|
|
13646
|
+
return value?.trim() ? value.trim() : void 0;
|
|
13647
|
+
}
|
|
13648
|
+
function httpsOrigin(value, what) {
|
|
13649
|
+
let url;
|
|
13650
|
+
try {
|
|
13651
|
+
url = new URL(value);
|
|
13652
|
+
} catch {
|
|
13653
|
+
throw new Error(`${what} must be an HTTPS origin, got "${value}"`);
|
|
13654
|
+
}
|
|
13655
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error(`${what} must be an HTTPS origin, got "${value}"`);
|
|
13656
|
+
return url.origin;
|
|
13657
|
+
}
|
|
13658
|
+
/**
|
|
13659
|
+
* Where the workload's credential comes from: the file the dashboard downloads
|
|
13660
|
+
* at enrollment (`SEEKRIT_WORKLOAD_FILE`), or — for a platform-federated
|
|
13661
|
+
* workload — its id plus the platform token's path.
|
|
13662
|
+
*/
|
|
13663
|
+
function loadWorkloadCredentials(opts, env = process.env) {
|
|
13664
|
+
const file = present(opts.workloadFile) ?? present(env.SEEKRIT_WORKLOAD_FILE);
|
|
13665
|
+
let fromFile = {};
|
|
13666
|
+
if (file) {
|
|
13667
|
+
let parsed;
|
|
13668
|
+
try {
|
|
13669
|
+
parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
13670
|
+
} catch (err) {
|
|
13671
|
+
throw new Error(`cannot read workload credentials from ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
13672
|
+
}
|
|
13673
|
+
if (!parsed || typeof parsed !== "object") throw new Error(`${file} is not a workload credential file`);
|
|
13674
|
+
fromFile = parsed;
|
|
13675
|
+
}
|
|
13676
|
+
const workloadId = present(opts.workloadId) ?? present(fromFile.workloadId) ?? present(env.SEEKRIT_WORKLOAD_ID);
|
|
13677
|
+
if (!workloadId) throw new Error("no workload identity: set SEEKRIT_WORKLOAD_FILE (or --workload-file) to the credentials downloaded at enrollment, or --workload-id with --platform-token-file");
|
|
13678
|
+
if (!/^wli_[A-Za-z0-9_-]{1,128}$/.test(workloadId)) throw new Error(`"${workloadId}" is not a workload id (expected wli_…)`);
|
|
13679
|
+
const issuer = httpsOrigin(present(opts.issuer) ?? present(fromFile.issuer) ?? present(env.SEEKRIT_ISSUER) ?? DEFAULT_ISSUER, "the seekrit issuer");
|
|
13680
|
+
const platformTokenFile = present(opts.platformTokenFile) ?? present(fromFile.platformTokenFile) ?? present(env.SEEKRIT_PLATFORM_TOKEN_FILE);
|
|
13681
|
+
const signingKey = fromFile.signingKey;
|
|
13682
|
+
if (!!signingKey === !!platformTokenFile) throw new Error(signingKey ? "the workload file holds an enrollment key; do not also pass a platform token" : "this workload needs an enrollment key (in its credential file) or --platform-token-file");
|
|
13683
|
+
if (signingKey && (signingKey.kty !== "EC" || signingKey.crv !== "P-256" || !signingKey.d || !signingKey.x)) throw new Error("the workload file's signingKey is not a private P-256 key");
|
|
13684
|
+
return {
|
|
13685
|
+
workloadId,
|
|
13686
|
+
issuer,
|
|
13687
|
+
signingKey,
|
|
13688
|
+
platformTokenFile
|
|
13689
|
+
};
|
|
13690
|
+
}
|
|
13691
|
+
function base64url(bytes) {
|
|
13692
|
+
return Buffer.from(bytes).toString("base64url");
|
|
13693
|
+
}
|
|
13694
|
+
/** A one-use, 60-second ES256 client assertion, the shape `/oauth/token` verifies. */
|
|
13695
|
+
async function signClientAssertion(creds, key, nowSeconds) {
|
|
13696
|
+
const unsigned = `${base64url(new TextEncoder().encode(JSON.stringify({
|
|
13697
|
+
alg: "ES256",
|
|
13698
|
+
typ: "JWT"
|
|
13699
|
+
})))}.${base64url(new TextEncoder().encode(JSON.stringify({
|
|
13700
|
+
iss: creds.workloadId,
|
|
13701
|
+
sub: creds.workloadId,
|
|
13702
|
+
aud: `${creds.issuer}/oauth/token`,
|
|
13703
|
+
iat: nowSeconds,
|
|
13704
|
+
exp: nowSeconds + 60,
|
|
13705
|
+
jti: crypto.randomUUID()
|
|
13706
|
+
})))}`;
|
|
13707
|
+
const signature = await crypto.subtle.sign({
|
|
13708
|
+
name: "ECDSA",
|
|
13709
|
+
hash: "SHA-256"
|
|
13710
|
+
}, key, new TextEncoder().encode(unsigned));
|
|
13711
|
+
return `${unsigned}.${base64url(new Uint8Array(signature))}`;
|
|
13712
|
+
}
|
|
13713
|
+
async function errorText(response) {
|
|
13714
|
+
try {
|
|
13715
|
+
const body = await response.json();
|
|
13716
|
+
if (body.error_description) return body.error_description;
|
|
13717
|
+
if (typeof body.error === "object" && body.error?.message) return body.error.message;
|
|
13718
|
+
if (typeof body.error === "string") return body.error;
|
|
13719
|
+
} catch {}
|
|
13720
|
+
return `HTTP ${response.status}`;
|
|
13721
|
+
}
|
|
13722
|
+
/** Session exchange + identity-token requests against one seekrit deployment. */
|
|
13723
|
+
var WorkloadClient = class {
|
|
13724
|
+
creds;
|
|
13725
|
+
fetcher;
|
|
13726
|
+
now;
|
|
13727
|
+
session;
|
|
13728
|
+
key;
|
|
13729
|
+
constructor(creds, fetcher = (input, init) => fetch(input, init), now = Date.now) {
|
|
13730
|
+
this.creds = creds;
|
|
13731
|
+
this.fetcher = fetcher;
|
|
13732
|
+
this.now = now;
|
|
13733
|
+
}
|
|
13734
|
+
signingKey() {
|
|
13735
|
+
this.key ??= crypto.subtle.importKey("jwk", this.creds.signingKey, {
|
|
13736
|
+
name: "ECDSA",
|
|
13737
|
+
namedCurve: "P-256"
|
|
13738
|
+
}, false, ["sign"]);
|
|
13739
|
+
return this.key;
|
|
13740
|
+
}
|
|
13741
|
+
async exchange() {
|
|
13742
|
+
const form = new URLSearchParams({
|
|
13743
|
+
client_id: this.creds.workloadId,
|
|
13744
|
+
scope: "connect"
|
|
13745
|
+
});
|
|
13746
|
+
if (this.creds.platformTokenFile) {
|
|
13747
|
+
const platformToken = readFileSync(this.creds.platformTokenFile, "utf8").trim();
|
|
13748
|
+
form.set("grant_type", TOKEN_EXCHANGE_GRANT);
|
|
13749
|
+
form.set("subject_token_type", JWT_TOKEN_TYPE);
|
|
13750
|
+
form.set("subject_token", platformToken);
|
|
13751
|
+
} else {
|
|
13752
|
+
form.set("grant_type", "client_credentials");
|
|
13753
|
+
form.set("client_assertion_type", CLIENT_ASSERTION_TYPE);
|
|
13754
|
+
form.set("client_assertion", await signClientAssertion(this.creds, await this.signingKey(), Math.floor(this.now() / 1e3)));
|
|
13755
|
+
}
|
|
13756
|
+
const response = await this.fetcher(`${this.creds.issuer}/oauth/token`, {
|
|
13757
|
+
method: "POST",
|
|
13758
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
13759
|
+
body: form
|
|
13760
|
+
});
|
|
13761
|
+
if (!response.ok) throw new Error(`workload sign-in failed: ${await errorText(response)}`);
|
|
13762
|
+
const body = await response.json();
|
|
13763
|
+
this.session = {
|
|
13764
|
+
token: body.access_token,
|
|
13765
|
+
expiresAt: this.now() + body.expires_in * 1e3
|
|
13766
|
+
};
|
|
13767
|
+
return body.access_token;
|
|
13768
|
+
}
|
|
13769
|
+
async accessToken() {
|
|
13770
|
+
if (this.session && this.session.expiresAt - 3e4 > this.now()) return this.session.token;
|
|
13771
|
+
return this.exchange();
|
|
13772
|
+
}
|
|
13773
|
+
/** An identity token for one enrolled audience. */
|
|
13774
|
+
async identityToken(audience) {
|
|
13775
|
+
for (let attempt = 0;; attempt++) {
|
|
13776
|
+
const response = await this.fetcher(`${this.creds.issuer}/v1/connect/identity-token`, {
|
|
13777
|
+
method: "POST",
|
|
13778
|
+
headers: {
|
|
13779
|
+
authorization: `Bearer ${await this.accessToken()}`,
|
|
13780
|
+
"content-type": "application/json"
|
|
13781
|
+
},
|
|
13782
|
+
body: JSON.stringify({ audience })
|
|
13783
|
+
});
|
|
13784
|
+
if (response.status === 401 && attempt === 0) {
|
|
13785
|
+
this.session = void 0;
|
|
13786
|
+
continue;
|
|
13787
|
+
}
|
|
13788
|
+
if (!response.ok) throw new Error(`identity token for "${audience}" refused: ${await errorText(response)}`);
|
|
13789
|
+
return await response.json();
|
|
13790
|
+
}
|
|
13791
|
+
}
|
|
13792
|
+
};
|
|
13793
|
+
/** The audience each provider's own docs use; any enrolled audience can override it. */
|
|
13794
|
+
const DEFAULT_AUDIENCES = {
|
|
13795
|
+
aws: "sts.amazonaws.com",
|
|
13796
|
+
openai: "https://api.openai.com/v1",
|
|
13797
|
+
anthropic: "https://api.anthropic.com"
|
|
13798
|
+
};
|
|
13799
|
+
/**
|
|
13800
|
+
* Credentials that outrank web identity in each provider's default chain. An
|
|
13801
|
+
* explicit `--aws`/`--anthropic`/… means "use the federated identity", so a
|
|
13802
|
+
* long-lived key left in the shell must not quietly win — the same rule as
|
|
13803
|
+
* `seekrit run --lease`.
|
|
13804
|
+
*/
|
|
13805
|
+
const SHADOWING = {
|
|
13806
|
+
aws: [
|
|
13807
|
+
"AWS_ACCESS_KEY_ID",
|
|
13808
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
13809
|
+
"AWS_SESSION_TOKEN",
|
|
13810
|
+
"AWS_PROFILE"
|
|
13811
|
+
],
|
|
13812
|
+
gcp: [],
|
|
13813
|
+
openai: ["OPENAI_API_KEY"],
|
|
13814
|
+
anthropic: [
|
|
13815
|
+
"ANTHROPIC_API_KEY",
|
|
13816
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
13817
|
+
"ANTHROPIC_PROFILE",
|
|
13818
|
+
"ANTHROPIC_IDENTITY_TOKEN"
|
|
13819
|
+
]
|
|
13820
|
+
};
|
|
13821
|
+
/** `projects/N/locations/global/workloadIdentityPools/P/providers/R`, from any of its spellings. */
|
|
13822
|
+
function normalizeGcpProvider(input) {
|
|
13823
|
+
const resource = input.trim().replace(/^https:\/\/iam\.googleapis\.com\//, "").replace(/^\/\/iam\.googleapis\.com\//, "");
|
|
13824
|
+
if (!/^projects\/\d+\/locations\/global\/workloadIdentityPools\/[a-z0-9-]{4,32}\/providers\/[a-z0-9-]{4,32}$/.test(resource)) throw new Error(`--gcp must name a workload identity pool provider by project number: projects/<number>/locations/global/workloadIdentityPools/<pool>/providers/<provider> (got "${input}")`);
|
|
13825
|
+
return resource;
|
|
13826
|
+
}
|
|
13827
|
+
function parseAudienceOverrides(values = []) {
|
|
13828
|
+
const out = {};
|
|
13829
|
+
for (const value of values) {
|
|
13830
|
+
const at = value.indexOf("=");
|
|
13831
|
+
const id = value.slice(0, at);
|
|
13832
|
+
const audience = value.slice(at + 1).trim();
|
|
13833
|
+
if (at < 1 || ![
|
|
13834
|
+
"aws",
|
|
13835
|
+
"gcp",
|
|
13836
|
+
"openai",
|
|
13837
|
+
"anthropic"
|
|
13838
|
+
].includes(id) || !audience) throw new Error(`--audience takes provider=audience, e.g. anthropic=api.anthropic.com`);
|
|
13839
|
+
if (out[id]) throw new Error(`--audience given twice for ${id}`);
|
|
13840
|
+
out[id] = audience;
|
|
13841
|
+
}
|
|
13842
|
+
return out;
|
|
13843
|
+
}
|
|
13844
|
+
/**
|
|
13845
|
+
* What to write and export for each requested provider. Pure: paths are inside
|
|
13846
|
+
* `dir`, which the caller creates (mode 0700) and removes.
|
|
13847
|
+
*/
|
|
13848
|
+
function planProviders(opts, dir, workloadId, env = process.env) {
|
|
13849
|
+
const audiences = parseAudienceOverrides(opts.audience);
|
|
13850
|
+
const plans = [];
|
|
13851
|
+
if (opts.aws !== void 0) {
|
|
13852
|
+
if (!/^arn:aws[a-z-]*:iam::\d{12}:role\/[\w+=,.@/-]+$/.test(opts.aws.trim())) throw new Error(`--aws must be an IAM role ARN, got "${opts.aws}"`);
|
|
13853
|
+
const tokenFile = join(dir, "aws-token");
|
|
13854
|
+
plans.push({
|
|
13855
|
+
id: "aws",
|
|
13856
|
+
audience: audiences.aws ?? DEFAULT_AUDIENCES.aws,
|
|
13857
|
+
tokenFile,
|
|
13858
|
+
env: {
|
|
13859
|
+
AWS_ROLE_ARN: opts.aws.trim(),
|
|
13860
|
+
AWS_WEB_IDENTITY_TOKEN_FILE: tokenFile,
|
|
13861
|
+
AWS_ROLE_SESSION_NAME: present(env.AWS_ROLE_SESSION_NAME) ?? `seekrit-${workloadId}`.slice(0, 64)
|
|
13862
|
+
},
|
|
13863
|
+
unset: SHADOWING.aws,
|
|
13864
|
+
files: []
|
|
13865
|
+
});
|
|
13866
|
+
}
|
|
13867
|
+
if (opts.gcp !== void 0) {
|
|
13868
|
+
const resource = normalizeGcpProvider(opts.gcp);
|
|
13869
|
+
const tokenFile = join(dir, "gcp-token");
|
|
13870
|
+
const configFile = join(dir, "gcp-credentials.json");
|
|
13871
|
+
const sa = present(opts.gcpServiceAccount);
|
|
13872
|
+
if (sa && !/^[a-z0-9-]+@[a-z0-9.-]+\.gserviceaccount\.com$/.test(sa)) throw new Error(`--gcp-service-account must be a service account email, got "${sa}"`);
|
|
13873
|
+
const config = {
|
|
13874
|
+
type: "external_account",
|
|
13875
|
+
audience: `//iam.googleapis.com/${resource}`,
|
|
13876
|
+
subject_token_type: JWT_TOKEN_TYPE,
|
|
13877
|
+
token_url: "https://sts.googleapis.com/v1/token",
|
|
13878
|
+
credential_source: {
|
|
13879
|
+
file: tokenFile,
|
|
13880
|
+
format: { type: "text" }
|
|
13881
|
+
},
|
|
13882
|
+
...sa ? { service_account_impersonation_url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${sa}:generateAccessToken` } : {}
|
|
13883
|
+
};
|
|
13884
|
+
plans.push({
|
|
13885
|
+
id: "gcp",
|
|
13886
|
+
audience: audiences.gcp ?? `https://iam.googleapis.com/${resource}`,
|
|
13887
|
+
tokenFile,
|
|
13888
|
+
env: {
|
|
13889
|
+
GOOGLE_APPLICATION_CREDENTIALS: configFile,
|
|
13890
|
+
CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE: configFile
|
|
13891
|
+
},
|
|
13892
|
+
unset: SHADOWING.gcp,
|
|
13893
|
+
files: [{
|
|
13894
|
+
path: configFile,
|
|
13895
|
+
content: `${JSON.stringify(config, null, 2)}\n`
|
|
13896
|
+
}]
|
|
13897
|
+
});
|
|
13898
|
+
} else if (opts.gcpServiceAccount) throw new Error("--gcp-service-account needs --gcp");
|
|
13899
|
+
if (opts.openai) {
|
|
13900
|
+
const tokenFile = join(dir, "openai-token");
|
|
13901
|
+
plans.push({
|
|
13902
|
+
id: "openai",
|
|
13903
|
+
audience: audiences.openai ?? DEFAULT_AUDIENCES.openai,
|
|
13904
|
+
tokenFile,
|
|
13905
|
+
env: { OPENAI_IDENTITY_TOKEN_FILE: tokenFile },
|
|
13906
|
+
unset: SHADOWING.openai,
|
|
13907
|
+
files: []
|
|
13908
|
+
});
|
|
13909
|
+
}
|
|
13910
|
+
if (opts.anthropic) {
|
|
13911
|
+
const tokenFile = join(dir, "anthropic-token");
|
|
13912
|
+
plans.push({
|
|
13913
|
+
id: "anthropic",
|
|
13914
|
+
audience: audiences.anthropic ?? DEFAULT_AUDIENCES.anthropic,
|
|
13915
|
+
tokenFile,
|
|
13916
|
+
env: { ANTHROPIC_IDENTITY_TOKEN_FILE: tokenFile },
|
|
13917
|
+
unset: SHADOWING.anthropic,
|
|
13918
|
+
files: []
|
|
13919
|
+
});
|
|
13920
|
+
}
|
|
13921
|
+
for (const id of Object.keys(audiences)) if (!plans.some((p) => p.id === id)) throw new Error(`--audience ${id}=… without --${id}`);
|
|
13922
|
+
if (plans.length === 0) throw new Error("name at least one provider: --aws <role-arn>, --gcp <provider>, --openai, --anthropic");
|
|
13923
|
+
return plans;
|
|
13924
|
+
}
|
|
13925
|
+
/**
|
|
13926
|
+
* The child's environment: shadowing credentials removed, provider variables
|
|
13927
|
+
* set. Returns the names it removed (never values) so the caller can say so.
|
|
13928
|
+
*/
|
|
13929
|
+
function childEnvironment(base, plans) {
|
|
13930
|
+
const env = { ...base };
|
|
13931
|
+
const removed = [];
|
|
13932
|
+
for (const plan of plans) for (const name of plan.unset) if (env[name] !== void 0) {
|
|
13933
|
+
delete env[name];
|
|
13934
|
+
removed.push(name);
|
|
13935
|
+
}
|
|
13936
|
+
for (const plan of plans) Object.assign(env, plan.env);
|
|
13937
|
+
return {
|
|
13938
|
+
env,
|
|
13939
|
+
removed
|
|
13940
|
+
};
|
|
13941
|
+
}
|
|
13942
|
+
/** Variables a provider SDK needs from the operator before it will federate at all. */
|
|
13943
|
+
function missingProviderSettings(plans, env) {
|
|
13944
|
+
const missing = [];
|
|
13945
|
+
if (plans.some((p) => p.id === "anthropic")) {
|
|
13946
|
+
for (const name of ["ANTHROPIC_FEDERATION_RULE_ID", "ANTHROPIC_ORGANIZATION_ID"]) if (!present(env[name])) missing.push(name);
|
|
13947
|
+
}
|
|
13948
|
+
return missing;
|
|
13949
|
+
}
|
|
13950
|
+
/** Write-then-rename, so a reader never sees half a token. */
|
|
13951
|
+
function writeAtomic(path, content) {
|
|
13952
|
+
const tmp = `${path}.${crypto.randomUUID()}.tmp`;
|
|
13953
|
+
writeFileSync(tmp, content, { mode: 384 });
|
|
13954
|
+
renameSync(tmp, path);
|
|
13955
|
+
}
|
|
13956
|
+
/**
|
|
13957
|
+
* Keeps each provider's token file current. Refreshes at half the token's
|
|
13958
|
+
* lifetime, so a provider SDK rereading the file always finds a token with
|
|
13959
|
+
* plenty of life left — and, for Anthropic, which accepts each token's `jti`
|
|
13960
|
+
* only once, a fresh one by the time it next exchanges.
|
|
13961
|
+
*/
|
|
13962
|
+
var TokenFileRefresher = class {
|
|
13963
|
+
client;
|
|
13964
|
+
plans;
|
|
13965
|
+
timers = /* @__PURE__ */ new Map();
|
|
13966
|
+
expiries = /* @__PURE__ */ new Map();
|
|
13967
|
+
stopped = false;
|
|
13968
|
+
log;
|
|
13969
|
+
minIntervalMs;
|
|
13970
|
+
setTimer;
|
|
13971
|
+
clearTimer;
|
|
13972
|
+
now;
|
|
13973
|
+
constructor(client, plans, opts = {}) {
|
|
13974
|
+
this.client = client;
|
|
13975
|
+
this.plans = plans;
|
|
13976
|
+
this.log = opts.log ?? ((m) => console.error(m));
|
|
13977
|
+
this.minIntervalMs = opts.minIntervalMs ?? 3e4;
|
|
13978
|
+
this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
13979
|
+
this.clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
|
|
13980
|
+
this.now = opts.now ?? Date.now;
|
|
13981
|
+
}
|
|
13982
|
+
/** Fetch every token once. Throws if any provider's first token cannot be had. */
|
|
13983
|
+
async start() {
|
|
13984
|
+
for (const plan of this.plans) for (const file of plan.files) writeAtomic(file.path, file.content);
|
|
13985
|
+
await Promise.all(this.plans.map((plan) => this.refresh(plan, true)));
|
|
13986
|
+
}
|
|
13987
|
+
async refresh(plan, first = false) {
|
|
13988
|
+
if (this.stopped) return;
|
|
13989
|
+
try {
|
|
13990
|
+
const issued = await this.client.identityToken(plan.audience);
|
|
13991
|
+
if (this.stopped) return;
|
|
13992
|
+
writeAtomic(plan.tokenFile, issued.token);
|
|
13993
|
+
this.expiries.set(plan.id, this.now() + issued.expiresIn * 1e3);
|
|
13994
|
+
this.schedule(plan, Math.max(this.minIntervalMs, issued.expiresIn * 1e3 / 2));
|
|
13995
|
+
} catch (err) {
|
|
13996
|
+
if (first) throw err;
|
|
13997
|
+
const left = (this.expiries.get(plan.id) ?? 0) - this.now();
|
|
13998
|
+
this.log(`seekrit: could not refresh the ${plan.id} identity token (${err instanceof Error ? err.message : String(err)}); ${left > 0 ? `the current one is valid for ${Math.round(left / 1e3)}s more, retrying` : "the current one has expired, retrying"}`);
|
|
13999
|
+
this.schedule(plan, Math.min(this.minIntervalMs, Math.max(1e3, left / 4)));
|
|
14000
|
+
}
|
|
14001
|
+
}
|
|
14002
|
+
schedule(plan, ms) {
|
|
14003
|
+
if (this.stopped) return;
|
|
14004
|
+
const timer = this.setTimer(() => void this.refresh(plan), ms);
|
|
14005
|
+
timer.unref?.();
|
|
14006
|
+
this.timers.set(plan.id, timer);
|
|
14007
|
+
}
|
|
14008
|
+
stop() {
|
|
14009
|
+
this.stopped = true;
|
|
14010
|
+
for (const timer of this.timers.values()) this.clearTimer(timer);
|
|
14011
|
+
this.timers.clear();
|
|
14012
|
+
}
|
|
14013
|
+
};
|
|
14014
|
+
/**
|
|
14015
|
+
* Run `cmd` with provider token files kept fresh for as long as it lives. The
|
|
14016
|
+
* token directory is private (0700, files 0600) and removed afterwards.
|
|
14017
|
+
*/
|
|
14018
|
+
async function runWithIdentity(cmd, args, client, opts) {
|
|
14019
|
+
const log = opts.log ?? ((m) => console.error(m));
|
|
14020
|
+
const dir = mkdtempSync(join(tmpdir(), "seekrit-identity-"));
|
|
14021
|
+
let refresher;
|
|
14022
|
+
try {
|
|
14023
|
+
const plans = planProviders(opts, dir, opts.workloadId);
|
|
14024
|
+
for (const name of missingProviderSettings(plans, process.env)) log(`seekrit: ${name} is not set; the provider SDK will not use the federated token without it`);
|
|
14025
|
+
return await runCommand(cmd, args, async () => {
|
|
14026
|
+
refresher = new TokenFileRefresher(client, plans, { log });
|
|
14027
|
+
await refresher.start();
|
|
14028
|
+
const { env, removed } = childEnvironment(process.env, plans);
|
|
14029
|
+
if (removed.length > 0) log(`seekrit: unset ${removed.join(", ")} for the command so the federated identity is used`);
|
|
14030
|
+
return env;
|
|
14031
|
+
}, async () => {
|
|
14032
|
+
refresher?.stop();
|
|
14033
|
+
rmSync(dir, {
|
|
14034
|
+
recursive: true,
|
|
14035
|
+
force: true
|
|
14036
|
+
});
|
|
14037
|
+
return true;
|
|
14038
|
+
});
|
|
14039
|
+
} catch (err) {
|
|
14040
|
+
rmSync(dir, {
|
|
14041
|
+
recursive: true,
|
|
14042
|
+
force: true
|
|
14043
|
+
});
|
|
14044
|
+
throw err;
|
|
14045
|
+
}
|
|
14046
|
+
}
|
|
14047
|
+
function collect$7(value, previous = []) {
|
|
14048
|
+
return [...previous, value];
|
|
14049
|
+
}
|
|
14050
|
+
function clientFrom(options) {
|
|
14051
|
+
try {
|
|
14052
|
+
const creds = loadWorkloadCredentials(options);
|
|
14053
|
+
return {
|
|
14054
|
+
client: new WorkloadClient(creds),
|
|
14055
|
+
workloadId: creds.workloadId
|
|
14056
|
+
};
|
|
14057
|
+
} catch (err) {
|
|
14058
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
14059
|
+
}
|
|
14060
|
+
}
|
|
14061
|
+
function credentialFlags(command) {
|
|
14062
|
+
return command.option("--workload-file <path>", "credentials downloaded at enrollment (default: $SEEKRIT_WORKLOAD_FILE)").option("--workload-id <id>", "workload id, for a platform-federated workload").option("--platform-token-file <path>", "platform JWT to exchange, reread on every sign-in (default: $SEEKRIT_PLATFORM_TOKEN_FILE)").option("--issuer <origin>", `seekrit API origin (default: the credential file's, or ${DEFAULT_ISSUER})`);
|
|
14063
|
+
}
|
|
14064
|
+
function registerIdentityCommands(program) {
|
|
14065
|
+
const identity = program.command("identity").description("keyless AWS, Google Cloud, OpenAI and Anthropic access from a seekrit workload identity");
|
|
14066
|
+
credentialFlags(identity.command("token").description("print a signed identity token for one enrolled audience").requiredOption("--audience <aud>", "an audience enrolled on the workload").option("--json", "print {token, expiresIn, issuer, subject} instead of the bare token")).action(async (options) => {
|
|
14067
|
+
const { client } = clientFrom(options);
|
|
14068
|
+
try {
|
|
14069
|
+
const issued = await client.identityToken(options.audience);
|
|
14070
|
+
console.log(options.json ? JSON.stringify(issued, null, 2) : issued.token);
|
|
14071
|
+
} catch (err) {
|
|
14072
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
14073
|
+
}
|
|
14074
|
+
});
|
|
14075
|
+
credentialFlags(identity.command("run").description("run a command with provider identity-token files kept fresh and the provider SDK variables set").passThroughOptions().option("--aws <role-arn>", "assume this IAM role via web identity").option("--gcp <provider>", "workload identity pool provider: projects/<number>/locations/global/workloadIdentityPools/<pool>/providers/<id>").option("--gcp-service-account <email>", "impersonate this service account after federating").option("--openai", "write OPENAI_IDENTITY_TOKEN_FILE").option("--anthropic", "write ANTHROPIC_IDENTITY_TOKEN_FILE").option("--audience <provider=aud>", "override a provider's default audience (repeatable)", collect$7).argument("<command...>", "command to run (prefix with -- to pass flags)")).action(async (commandParts, options) => {
|
|
14076
|
+
const [cmd, ...args] = commandParts;
|
|
14077
|
+
if (!cmd) fail("no command given");
|
|
14078
|
+
const { client, workloadId } = clientFrom(options);
|
|
14079
|
+
let result;
|
|
14080
|
+
try {
|
|
14081
|
+
result = await runWithIdentity(cmd, args, client, {
|
|
14082
|
+
...options,
|
|
14083
|
+
workloadId
|
|
14084
|
+
});
|
|
14085
|
+
} catch (err) {
|
|
14086
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
14087
|
+
}
|
|
14088
|
+
if (result.signal) process.kill(process.pid, result.signal);
|
|
14089
|
+
else process.exit(result.code);
|
|
14090
|
+
});
|
|
14091
|
+
}
|
|
14092
|
+
// src/m2m.ts
|
|
14093
|
+
/**
|
|
14094
|
+
* Resolve M2M client credentials from (in order) the process environment, a
|
|
14095
|
+
* `.env` overlay (for `seekrit run`), then saved config. Both halves must come
|
|
14096
|
+
* through for the credential to be usable.
|
|
14097
|
+
*/
|
|
14098
|
+
function readM2mCreds(dotenvVars = {}) {
|
|
14099
|
+
const config = readGlobalConfig();
|
|
14100
|
+
const fromEnv = (key) => process.env[key] ?? dotenvVars[key];
|
|
14101
|
+
const clientId = fromEnv("SEEKRIT_CLIENT_ID") ?? config.clientId;
|
|
14102
|
+
const clientSecret = fromEnv("SEEKRIT_CLIENT_SECRET") ?? config.clientSecret;
|
|
14103
|
+
if (!clientId || !clientSecret) return null;
|
|
14104
|
+
return {
|
|
14105
|
+
clientId,
|
|
14106
|
+
clientSecret
|
|
14107
|
+
};
|
|
14108
|
+
}
|
|
14109
|
+
/**
|
|
14110
|
+
* True when a service/session/dev credential is already configured explicitly.
|
|
14111
|
+
* A browser-authorized session counts: a human who ran `seekrit login` must not
|
|
14112
|
+
* be silently swapped onto a machine identity.
|
|
14113
|
+
*/
|
|
14114
|
+
function hasExplicitCredential(dotenvVars) {
|
|
14115
|
+
const fromEnv = (key) => process.env[key] ?? dotenvVars[key];
|
|
14116
|
+
if (fromEnv("SEEKRIT_TOKEN") || fromEnv("SEEKRIT_DEV_USER")) return true;
|
|
14117
|
+
const config = readGlobalConfig();
|
|
14118
|
+
return Boolean(config.token || config.sessionToken || config.devUser);
|
|
14119
|
+
}
|
|
14120
|
+
/** Mint a fresh org-scoped admin token using M2M credentials (keyless). */
|
|
14121
|
+
async function mintAdminToken(apiUrl, creds) {
|
|
14122
|
+
const client = new SeekritClient({
|
|
14123
|
+
baseUrl: apiUrl,
|
|
14124
|
+
auth: {
|
|
14125
|
+
type: "m2m",
|
|
14126
|
+
clientId: creds.clientId,
|
|
14127
|
+
clientSecret: creds.clientSecret
|
|
14128
|
+
},
|
|
14129
|
+
client: `cli/${version}`
|
|
14130
|
+
});
|
|
14131
|
+
const { orgs } = await client.listOrgs();
|
|
14132
|
+
const org = orgs[0];
|
|
14133
|
+
if (!org) throw new Error("machine client has no organization — sign up first (POST /signup)");
|
|
14134
|
+
const created = await createServiceToken();
|
|
14135
|
+
await client.createToken(org.id, {
|
|
14136
|
+
name: "agent-admin (auto)",
|
|
14137
|
+
tokenId: created.tokenId,
|
|
14138
|
+
tokenHash: created.tokenHash,
|
|
14139
|
+
publicKeyJwk: created.publicKeyJwk,
|
|
14140
|
+
role: "admin",
|
|
14141
|
+
environmentId: null
|
|
14142
|
+
});
|
|
14143
|
+
return created.token;
|
|
14144
|
+
}
|
|
14145
|
+
/**
|
|
14146
|
+
* If M2M credentials are configured (and no explicit token/dev-user is), ensure
|
|
14147
|
+
* an admin token exists for them — minting and caching one on first use — and
|
|
14148
|
+
* expose it as `SEEKRIT_TOKEN` so the normal credential resolution
|
|
14149
|
+
* ({@link tryBuildContext}) picks it up. A no-op (returns null) when there is
|
|
14150
|
+
* nothing to bootstrap. Throws only when credentials ARE present but minting
|
|
14151
|
+
* fails, so the reason surfaces instead of a later opaque "no credentials".
|
|
14152
|
+
*/
|
|
14153
|
+
async function ensureM2mAdminToken(dotenvVars = {}) {
|
|
14154
|
+
if (hasExplicitCredential(dotenvVars)) return null;
|
|
14155
|
+
const creds = readM2mCreds(dotenvVars);
|
|
14156
|
+
if (!creds) return null;
|
|
14157
|
+
const cached = readCachedM2mToken(creds.clientId);
|
|
14158
|
+
if (cached) {
|
|
14159
|
+
let usable = false;
|
|
14160
|
+
try {
|
|
14161
|
+
serviceTokenAuth(cached);
|
|
14162
|
+
usable = true;
|
|
14163
|
+
} catch {}
|
|
14164
|
+
if (usable) {
|
|
14165
|
+
process.env.SEEKRIT_TOKEN = cached;
|
|
14166
|
+
return cached;
|
|
14167
|
+
}
|
|
14168
|
+
}
|
|
14169
|
+
const token = await mintAdminToken(process.env.SEEKRIT_API_URL ?? dotenvVars.SEEKRIT_API_URL ?? readGlobalConfig().apiUrl ?? "https://api.seekrit.dev", creds);
|
|
14170
|
+
cacheM2mToken(creds.clientId, token);
|
|
14171
|
+
process.env.SEEKRIT_TOKEN = token;
|
|
14172
|
+
return token;
|
|
14173
|
+
}
|
|
14174
|
+
/** Lowercase, hyphenated, at most 40 characters — what `slugSchema` accepts. */
|
|
14175
|
+
function slugify(name) {
|
|
14176
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/g, "") || "workspace";
|
|
14177
|
+
}
|
|
14178
|
+
/** `acme-storefront` → `Acme Storefront`: a slug-ish name as a human reads it. */
|
|
14179
|
+
function titleCase(name) {
|
|
14180
|
+
return name.split(/[^A-Za-z0-9]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
14181
|
+
}
|
|
14182
|
+
/**
|
|
14183
|
+
* The project's name, from whichever manifest the directory carries — the
|
|
14184
|
+
* package name without its npm scope, the Python or Rust package name, the last
|
|
14185
|
+
* segment of a Go module path — falling back to the directory's own name. The
|
|
14186
|
+
* manifests are read leniently (a regex, not a parser) because a malformed one
|
|
14187
|
+
* should cost the default, not the command.
|
|
14188
|
+
*/
|
|
14189
|
+
function detectProjectName(dir = process.cwd()) {
|
|
14190
|
+
const read = (file) => {
|
|
14191
|
+
const path = join(dir, file);
|
|
14192
|
+
if (!existsSync(path)) return null;
|
|
14193
|
+
try {
|
|
14194
|
+
return readFileSync(path, "utf8");
|
|
14195
|
+
} catch {
|
|
14196
|
+
return null;
|
|
14197
|
+
}
|
|
14198
|
+
};
|
|
14199
|
+
const pkg = read("package.json");
|
|
14200
|
+
if (pkg) try {
|
|
14201
|
+
const name = JSON.parse(pkg).name;
|
|
14202
|
+
if (typeof name === "string" && name.trim()) return name.replace(/^@[^/]+\//, "");
|
|
14203
|
+
} catch {}
|
|
14204
|
+
const pyName = read("pyproject.toml")?.match(/^\[project\][^[]*?^name\s*=\s*"([^"]+)"/ms)?.[1];
|
|
14205
|
+
if (pyName) return pyName;
|
|
14206
|
+
const cargoName = read("Cargo.toml")?.match(/^\[package\][^[]*?^name\s*=\s*"([^"]+)"/ms)?.[1];
|
|
14207
|
+
if (cargoName) return cargoName;
|
|
14208
|
+
const goModule = read("go.mod")?.match(/^module\s+(\S+)/m)?.[1];
|
|
14209
|
+
if (goModule) return goModule.split("/").pop() ?? goModule;
|
|
14210
|
+
return basename(dir);
|
|
14211
|
+
}
|
|
14212
|
+
/**
|
|
14213
|
+
* What `init` would sign up as, from the flags and the directory. Pure, so the
|
|
14214
|
+
* naming rules are testable without a terminal or an API.
|
|
14215
|
+
*/
|
|
14216
|
+
function planWorkspace(options, project) {
|
|
14217
|
+
const orgName = options.orgName?.trim() || titleCase(project);
|
|
14218
|
+
const orgSlug = options.org ?? slugify(options.orgName?.trim() || project);
|
|
14219
|
+
if (!slugSchema.safeParse(orgSlug).success) fail(`"${orgSlug}" is not a valid org slug (lowercase letters, numbers, and hyphens) — pass --org <slug>`);
|
|
13254
14220
|
return {
|
|
13255
14221
|
orgName,
|
|
13256
14222
|
orgSlug
|
|
@@ -16171,176 +17137,6 @@ function registerRotationCommands(program) {
|
|
|
16171
17137
|
console.error(`disabled rotation of ${r.secretName}${rotatorRevoked ? " — rotator key access revoked for this environment" : ""}`);
|
|
16172
17138
|
});
|
|
16173
17139
|
}
|
|
16174
|
-
// src/run-command.ts
|
|
16175
|
-
/** Keep signal handlers installed from provisioning through asynchronous cleanup. */
|
|
16176
|
-
async function runCommand(cmd, args, prepare, cleanup) {
|
|
16177
|
-
let child;
|
|
16178
|
-
let forwarded;
|
|
16179
|
-
let result = { code: 1 };
|
|
16180
|
-
const stragglers = /* @__PURE__ */ new Set();
|
|
16181
|
-
const collect = () => {
|
|
16182
|
-
if (process.platform === "win32" || !child?.pid) return;
|
|
16183
|
-
for (const pid of teardownTargets(child.pid)) stragglers.add(pid);
|
|
16184
|
-
};
|
|
16185
|
-
const forward = (signal) => {
|
|
16186
|
-
forwarded ??= signal;
|
|
16187
|
-
if (!child || child.exitCode !== null || child.signalCode !== null) return;
|
|
16188
|
-
collect();
|
|
16189
|
-
child.kill(signal);
|
|
16190
|
-
for (const pid of stragglers) try {
|
|
16191
|
-
process.kill(pid, signal);
|
|
16192
|
-
} catch {}
|
|
16193
|
-
};
|
|
16194
|
-
const signals = [
|
|
16195
|
-
"SIGINT",
|
|
16196
|
-
"SIGTERM",
|
|
16197
|
-
"SIGHUP",
|
|
16198
|
-
"SIGQUIT"
|
|
16199
|
-
];
|
|
16200
|
-
for (const signal of signals) process.on(signal, forward);
|
|
16201
|
-
try {
|
|
16202
|
-
const env = await prepare(() => forwarded !== void 0);
|
|
16203
|
-
if (!forwarded) {
|
|
16204
|
-
const running = spawn(cmd, args, {
|
|
16205
|
-
stdio: "inherit",
|
|
16206
|
-
env
|
|
16207
|
-
});
|
|
16208
|
-
child = running;
|
|
16209
|
-
result = await new Promise((resolve, reject) => {
|
|
16210
|
-
running.once("exit", (code, signal) => resolve({
|
|
16211
|
-
code: code ?? 1,
|
|
16212
|
-
signal: signal ?? void 0
|
|
16213
|
-
}));
|
|
16214
|
-
running.once("error", (err) => reject(/* @__PURE__ */ new Error(`failed to start ${cmd}: ${err.message}`)));
|
|
16215
|
-
});
|
|
16216
|
-
}
|
|
16217
|
-
} finally {
|
|
16218
|
-
try {
|
|
16219
|
-
if (forwarded) {
|
|
16220
|
-
collect();
|
|
16221
|
-
await reapStragglers(stragglers, forwarded);
|
|
16222
|
-
}
|
|
16223
|
-
} finally {
|
|
16224
|
-
try {
|
|
16225
|
-
if (!await cleanup() && result.code === 0) result.code = 1;
|
|
16226
|
-
} finally {
|
|
16227
|
-
for (const signal of signals) process.off(signal, forward);
|
|
16228
|
-
}
|
|
16229
|
-
}
|
|
16230
|
-
}
|
|
16231
|
-
if (!child && forwarded) result.signal = forwarded;
|
|
16232
|
-
return result;
|
|
16233
|
-
}
|
|
16234
|
-
/**
|
|
16235
|
-
* Every live process `seekrit run` is responsible for tearing down, from a
|
|
16236
|
-
* single `ps(1)` snapshot: each descendant of `root`, plus — when we lead our
|
|
16237
|
-
* own process group — every other member of that group.
|
|
16238
|
-
*
|
|
16239
|
-
* Two mechanisms, because each covers the other's blind spot:
|
|
16240
|
-
*
|
|
16241
|
-
* - The parent-chain walk reaches a process that left our group via
|
|
16242
|
-
* setsid(2)/setpgid(2), which no group-wide kill and no tty-generated signal
|
|
16243
|
-
* can touch. But it loses anything whose chain up to `root` has already
|
|
16244
|
-
* broken — teardown kills the middle layers first, and an orphan reparents to
|
|
16245
|
-
* init, out of reach of any walk down from `root`.
|
|
16246
|
-
* - Group membership survives exactly that: a pgid outlives the parent that
|
|
16247
|
-
* passed it down. But it misses the ones that left the group.
|
|
16248
|
-
*
|
|
16249
|
-
* The group half applies only when our pid *is* our pgid, which is the shell-job
|
|
16250
|
-
* case: the shell put this command in a new group, so every other member of it
|
|
16251
|
-
* descends from us. Nested inside a shell script or a CI runner we are not the
|
|
16252
|
-
* leader and the group holds processes we did not start, so there we use the
|
|
16253
|
-
* walk alone.
|
|
16254
|
-
*
|
|
16255
|
-
* Zombies are skipped: they are already dead and waiting to be reaped, so
|
|
16256
|
-
* counting them would stall teardown for the full grace period and then report
|
|
16257
|
-
* a kill that did nothing. Returns nothing if `ps` is unavailable; callers fall
|
|
16258
|
-
* back to signalling the immediate child.
|
|
16259
|
-
*/
|
|
16260
|
-
function teardownTargets(root) {
|
|
16261
|
-
const ps = spawnSync("ps", [
|
|
16262
|
-
"-A",
|
|
16263
|
-
"-o",
|
|
16264
|
-
"pid=,ppid=,pgid=,stat="
|
|
16265
|
-
], { encoding: "utf8" });
|
|
16266
|
-
if (ps.status !== 0 || typeof ps.stdout !== "string") return [];
|
|
16267
|
-
const childrenOf = /* @__PURE__ */ new Map();
|
|
16268
|
-
const groupOf = /* @__PURE__ */ new Map();
|
|
16269
|
-
const isId = (value) => value !== void 0 && Number.isInteger(value);
|
|
16270
|
-
for (const line of ps.stdout.split("\n")) {
|
|
16271
|
-
const [pid, ppid, pgid, stat] = line.trim().split(/\s+/, 4);
|
|
16272
|
-
const [id, parent, group] = [
|
|
16273
|
-
pid,
|
|
16274
|
-
ppid,
|
|
16275
|
-
pgid
|
|
16276
|
-
].map(Number);
|
|
16277
|
-
if (!isId(id) || !isId(parent) || !isId(group)) continue;
|
|
16278
|
-
if (stat?.startsWith("Z")) continue;
|
|
16279
|
-
const siblings = childrenOf.get(parent);
|
|
16280
|
-
if (siblings) siblings.push(id);
|
|
16281
|
-
else childrenOf.set(parent, [id]);
|
|
16282
|
-
groupOf.set(id, group);
|
|
16283
|
-
}
|
|
16284
|
-
const found = /* @__PURE__ */ new Set();
|
|
16285
|
-
const queue = [root];
|
|
16286
|
-
const seen = new Set(queue);
|
|
16287
|
-
for (let pid = queue.shift(); pid !== void 0; pid = queue.shift()) for (const kid of childrenOf.get(pid) ?? []) {
|
|
16288
|
-
if (seen.has(kid)) continue;
|
|
16289
|
-
seen.add(kid);
|
|
16290
|
-
found.add(kid);
|
|
16291
|
-
queue.push(kid);
|
|
16292
|
-
}
|
|
16293
|
-
if (groupOf.get(process.pid) === process.pid) {
|
|
16294
|
-
for (const [pid, group] of groupOf) if (group === process.pid) found.add(pid);
|
|
16295
|
-
}
|
|
16296
|
-
found.delete(process.pid);
|
|
16297
|
-
found.delete(root);
|
|
16298
|
-
return [...found];
|
|
16299
|
-
}
|
|
16300
|
-
/** Whether `pid` still exists — signal 0 tests reachability, delivers nothing. */
|
|
16301
|
-
function isAlive(pid) {
|
|
16302
|
-
try {
|
|
16303
|
-
process.kill(pid, 0);
|
|
16304
|
-
return true;
|
|
16305
|
-
} catch {
|
|
16306
|
-
return false;
|
|
16307
|
-
}
|
|
16308
|
-
}
|
|
16309
|
-
/** How long a straggler gets to finish shutting down before SIGKILL. */
|
|
16310
|
-
const STRAGGLER_GRACE_MS = 2e3;
|
|
16311
|
-
/**
|
|
16312
|
-
* Once the command has exited, make sure nothing it started outlives us.
|
|
16313
|
-
*
|
|
16314
|
-
* Anything still alive here has ignored the signal we forwarded *and* lost the
|
|
16315
|
-
* parent that launched it, and we are the last process that knows its pid:
|
|
16316
|
-
* the tty won't hang it up (it is no longer in the foreground group, and the
|
|
16317
|
-
* shell has moved on), so it survives until the machine reboots. That is how
|
|
16318
|
-
* `tsx` and the server under it stacked up for days.
|
|
16319
|
-
*
|
|
16320
|
-
* So escalate rather than trust. SIGTERM first — it costs no extra time and is
|
|
16321
|
-
* a rung the SIGINT-only handlers common in dev servers don't catch — then
|
|
16322
|
-
* SIGKILL whatever is left after the grace period. Nothing is killed unless it
|
|
16323
|
-
* was asked to stop first and declined, and this runs only when we forwarded a
|
|
16324
|
-
* termination signal: a command that exits on its own having deliberately left a
|
|
16325
|
-
* daemon behind keeps working.
|
|
16326
|
-
*/
|
|
16327
|
-
async function reapStragglers(pids, signal) {
|
|
16328
|
-
let live = [...pids].filter(isAlive);
|
|
16329
|
-
if (live.length === 0) return;
|
|
16330
|
-
for (const pid of live) try {
|
|
16331
|
-
process.kill(pid, "SIGTERM");
|
|
16332
|
-
} catch {}
|
|
16333
|
-
const deadline = Date.now() + STRAGGLER_GRACE_MS;
|
|
16334
|
-
while (live.length > 0 && Date.now() < deadline) {
|
|
16335
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
16336
|
-
live = live.filter(isAlive);
|
|
16337
|
-
}
|
|
16338
|
-
if (live.length === 0) return;
|
|
16339
|
-
console.error(`seekrit: force-killed ${live.length} leftover process(es) that ignored ${signal} and SIGTERM (${live.join(", ")})`);
|
|
16340
|
-
for (const pid of live) try {
|
|
16341
|
-
process.kill(pid, "SIGKILL");
|
|
16342
|
-
} catch {}
|
|
16343
|
-
}
|
|
16344
17140
|
// src/run-leases.ts
|
|
16345
17141
|
/** Validate all flags before contacting the API or creating any database roles. */
|
|
16346
17142
|
function parseRunLeaseOptions(bindings = [], ttl) {
|
|
@@ -18000,6 +18796,8 @@ registerHermesCommands(program);
|
|
|
18000
18796
|
registerSshCommands(program);
|
|
18001
18797
|
registerAwsCommands(program);
|
|
18002
18798
|
registerGcpCommands(program);
|
|
18799
|
+
registerDynamicCommands(program);
|
|
18800
|
+
registerIdentityCommands(program);
|
|
18003
18801
|
registerMongoCommands(program);
|
|
18004
18802
|
registerKmsCommands(program);
|
|
18005
18803
|
registerRotationCommands(program);
|