@seekrit/cli 2.7.0 → 2.8.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.
Files changed (2) hide show
  1. package/dist/index.js +1060 -34
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -169,7 +169,7 @@ async function policySignerThumbprint(jwk) {
169
169
  x: jwk.x,
170
170
  y: jwk.y
171
171
  });
172
- const digest = await crypto.subtle.digest("SHA-256", utf8(canonical));
172
+ const digest = await crypto.subtle.digest("SHA-256", utf8$1(canonical));
173
173
  return base64url$1(new Uint8Array(digest));
174
174
  }
175
175
  /**
@@ -184,7 +184,7 @@ async function policySignerThumbprint(jwk) {
184
184
  */
185
185
  async function signAgentPolicy(privateKey, signerJwk, draft) {
186
186
  const kid = await policySignerThumbprint(signerJwk);
187
- const bytes = utf8(canonicalizeAgentPolicy({
187
+ const bytes = utf8$1(canonicalizeAgentPolicy({
188
188
  ...draft,
189
189
  signer: {
190
190
  kid,
@@ -380,7 +380,7 @@ function normalizeRule(rule) {
380
380
  label: rule.label?.trim() || void 0
381
381
  };
382
382
  }
383
- function utf8(text) {
383
+ function utf8$1(text) {
384
384
  return new TextEncoder().encode(text);
385
385
  }
386
386
  function utf8Decode$1(bytes) {
@@ -1370,6 +1370,16 @@ z$1.object({
1370
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
1372
  z$1.object({ audience: audienceSchema }).strict();
1373
+ // ../../packages/core/src/customer-provisioner.ts
1374
+ /** A credential shown to the administrator for the provisioner deployment.
1375
+ * Routing metadata is bundled with its signing key; upstream secrets stay local. */
1376
+ function createProvisionerConnection(input) {
1377
+ const body = JSON.stringify({
1378
+ version: 1,
1379
+ ...input
1380
+ });
1381
+ return `sp1.${btoa(String.fromCharCode(...new TextEncoder().encode(body))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`;
1382
+ }
1373
1383
  // ../../packages/core/src/dotenv.ts
1374
1384
  /**
1375
1385
  * `.env` parsing and serialization — the canonical definition of what a `.env`
@@ -1752,10 +1762,6 @@ function prettyJsonOrRaw(value) {
1752
1762
  const inspection = inspectJsonValue(value);
1753
1763
  return inspection.kind === "json" ? inspection.pretty : value;
1754
1764
  }
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
1765
  // ../../packages/core/src/managed-providers/common.ts
1760
1766
  const managedEndpointSchema = z$1.string().url().max(2048).refine((value) => {
1761
1767
  try {
@@ -1768,6 +1774,10 @@ const managedEndpointSchema = z$1.string().url().max(2048).refine((value) => {
1768
1774
  }, "use a public HTTPS endpoint without embedded credentials, query, or fragment");
1769
1775
  const managedTtlSchema = z$1.number().int().min(60).max(604800).optional();
1770
1776
  const nameSchema$1 = z$1.string().min(1).max(128).regex(/^[a-zA-Z_][a-zA-Z0-9_-]*$/);
1777
+ // ../../packages/core/src/managed-providers/aws-cache.ts
1778
+ const cacheRegionSchema = z$1.string().regex(/^[a-z]{2}(?:-[a-z]+)+-\d$/);
1779
+ const cacheGroupSchema = z$1.string().min(1).max(128).regex(/^[a-zA-Z][a-zA-Z0-9-]*$/);
1780
+ 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");
1771
1781
  // ../../packages/core/src/managed-providers/memorydb.ts
1772
1782
  const memorydbTargetConfigSchema = z$1.object({
1773
1783
  provider: z$1.literal("memorydb"),
@@ -1956,7 +1966,7 @@ const tailscaleTargetConfigSchema = z$1.object({
1956
1966
  message: "federation requires HTTPS issuer, audience, and subject"
1957
1967
  });
1958
1968
  });
1959
- const managedTargetConfigSchema = z$1.discriminatedUnion("provider", [
1969
+ const hostedManagedTargetConfigSchema = z$1.discriminatedUnion("provider", [
1960
1970
  clickhouseTargetConfigSchema,
1961
1971
  rabbitmqTargetConfigSchema,
1962
1972
  milvusTargetConfigSchema,
@@ -1969,12 +1979,230 @@ const managedTargetConfigSchema = z$1.discriminatedUnion("provider", [
1969
1979
  tailscaleTargetConfigSchema,
1970
1980
  elasticsearchTargetConfigSchema
1971
1981
  ]);
1982
+ function hasLocalServiceEndpoint(provider) {
1983
+ return [
1984
+ "milvus",
1985
+ "rabbitmq",
1986
+ "clickhouse",
1987
+ "elasticsearch"
1988
+ ].includes(provider);
1989
+ }
1990
+ /** Reuse the provider's existing permission validation without accepting a
1991
+ * remotely supplied destination for a locally held administrator credential. */
1992
+ function customerSettingsAsHosted(provider, settings) {
1993
+ const endpoint = typeof settings.endpoint === "string" ? settings.endpoint : void 0;
1994
+ if (hasLocalServiceEndpoint(provider) && endpoint !== void 0) {
1995
+ const url = new URL(z$1.string().parse(endpoint));
1996
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error("Use an HTTPS service endpoint without credentials.");
1997
+ }
1998
+ const normalized = hostedManagedTargetConfigSchema.parse({
1999
+ ...settings,
2000
+ provider,
2001
+ executor: "in_do",
2002
+ ...hasLocalServiceEndpoint(provider) ? { endpoint: "https://customer-service.example.com" } : {}
2003
+ });
2004
+ return endpoint === void 0 ? normalized : {
2005
+ ...normalized,
2006
+ endpoint
2007
+ };
2008
+ }
2009
+ /** Permissions remain configured in Seekrit; destination and admin secret stay local. */
2010
+ const remoteManagedTargetConfigSchema = z$1.object({
2011
+ provider: z$1.enum(MANAGED_LEASE_KINDS),
2012
+ executor: z$1.literal("remote"),
2013
+ provisionerId: z$1.string().regex(/^prv_[A-Za-z0-9_-]{32}$/).optional(),
2014
+ transport: z$1.enum(["push", "pull"]).default("push"),
2015
+ provisionerUrl: managedEndpointSchema.optional(),
2016
+ settings: z$1.record(z$1.string(), z$1.unknown()),
2017
+ maxTtlSeconds: managedTtlSchema
2018
+ }).strict().superRefine((config, ctx) => {
2019
+ if (!config.provisionerId && config.transport === "push" && !config.provisionerUrl) ctx.addIssue({
2020
+ code: "custom",
2021
+ path: ["provisionerUrl"],
2022
+ message: "HTTPS mode requires a provisioner URL"
2023
+ });
2024
+ if (config.transport === "pull" && config.provisionerUrl) ctx.addIssue({
2025
+ code: "custom",
2026
+ path: ["provisionerUrl"],
2027
+ message: "outbound mode does not use a provisioner URL"
2028
+ });
2029
+ if ([
2030
+ "provider",
2031
+ "executor",
2032
+ "maxTtlSeconds"
2033
+ ].some((key) => key in config.settings)) {
2034
+ ctx.addIssue({
2035
+ code: "custom",
2036
+ path: ["settings"],
2037
+ message: "settings contain a reserved field"
2038
+ });
2039
+ return;
2040
+ }
2041
+ try {
2042
+ if (!config.provisionerId && config.settings.endpoint !== void 0) throw new Error("Register a provisioner to configure its endpoint in Seekrit.");
2043
+ if (config.provisionerId && hasLocalServiceEndpoint(config.provider) && !config.settings.endpoint) throw new Error("Configure the service endpoint in Seekrit.");
2044
+ customerSettingsAsHosted(config.provider, config.settings);
2045
+ } catch (error) {
2046
+ if (error instanceof z$1.ZodError) for (const issue of error.issues) ctx.addIssue({
2047
+ code: "custom",
2048
+ path: ["settings", ...issue.path],
2049
+ message: issue.message
2050
+ });
2051
+ else ctx.addIssue({
2052
+ code: "custom",
2053
+ path: ["settings"],
2054
+ message: "invalid service settings"
2055
+ });
2056
+ }
2057
+ }).transform((config) => {
2058
+ const normalized = customerSettingsAsHosted(config.provider, config.settings);
2059
+ return {
2060
+ ...config,
2061
+ settings: Object.fromEntries(Object.entries(normalized).filter(([key]) => ![
2062
+ "provider",
2063
+ "executor",
2064
+ "maxTtlSeconds"
2065
+ ].includes(key) && (key !== "endpoint" || config.settings.endpoint !== void 0)))
2066
+ };
2067
+ });
2068
+ const managedTargetConfigSchema = z$1.union([hostedManagedTargetConfigSchema, remoteManagedTargetConfigSchema]);
1972
2069
  function isManagedLeaseKind(value) {
1973
2070
  return MANAGED_LEASE_KINDS.includes(value);
1974
2071
  }
1975
2072
  function isManagedTarget(value) {
1976
2073
  return isManagedLeaseKind(value.provider);
1977
2074
  }
2075
+ const id = z$1.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/);
2076
+ const timestamp = z$1.number().int().nonnegative().safe();
2077
+ const provisionerPrincipalSchema = z$1.object({
2078
+ type: z$1.enum(["user", "service_token"]),
2079
+ id,
2080
+ publicKeyJwk: z$1.string().min(1).max(1024)
2081
+ }).strict();
2082
+ const provisionerBootstrapSchema = z$1.object({
2083
+ version: z$1.literal(1),
2084
+ orgId: id,
2085
+ provisionerId: id,
2086
+ authority: provisionerPrincipalSchema,
2087
+ createdAt: timestamp
2088
+ }).strict();
2089
+ const provisionerGrantSchema = z$1.object({
2090
+ version: z$1.literal(1),
2091
+ id,
2092
+ orgId: id,
2093
+ provisionerId: id,
2094
+ principal: provisionerPrincipalSchema,
2095
+ targets: z$1.array(id.or(z$1.literal("*"))).min(1).max(100),
2096
+ manage: z$1.boolean(),
2097
+ issuedAt: timestamp,
2098
+ expiresAt: timestamp
2099
+ }).strict();
2100
+ const provisionerChainSchema = z$1.object({
2101
+ bootstrap: z$1.string().max(4096).startsWith("pb1."),
2102
+ grants: z$1.array(z$1.string().max(4096).startsWith("pg1.")).max(3)
2103
+ }).strict();
2104
+ z$1.object({
2105
+ version: z$1.literal(1),
2106
+ orgId: id,
2107
+ provisionerId: id,
2108
+ targetId: id,
2109
+ provider: id,
2110
+ leaseId: id,
2111
+ instanceId: id,
2112
+ nonce: id,
2113
+ targetDigest: z$1.string().regex(/^[A-Za-z0-9_-]{43}$/),
2114
+ action: z$1.unknown(),
2115
+ recipientPublicKey: z$1.string().max(1024).optional(),
2116
+ issuedAt: timestamp,
2117
+ expiresAt: timestamp
2118
+ }).strict();
2119
+ const provisionerAuthorizationSchema = provisionerChainSchema.extend({ proof: z$1.string().max(16384).startsWith("pr1.") });
2120
+ const utf8 = new TextEncoder();
2121
+ function b64(bytes) {
2122
+ return btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
2123
+ }
2124
+ function unb64(value) {
2125
+ if (!/^[A-Za-z0-9_-]+$/.test(value)) throw new Error("Invalid authorization encoding");
2126
+ return Uint8Array.from(atob(value.replaceAll("-", "+").replaceAll("_", "/")), (c) => c.charCodeAt(0));
2127
+ }
2128
+ function readProvisionerEnvelope(envelope, prefix) {
2129
+ const parts = envelope.split(".");
2130
+ if (envelope.length > 16384 || parts.length !== 3 || parts[0] !== prefix) throw new Error("Invalid customer authorization envelope");
2131
+ return JSON.parse(new TextDecoder("utf-8", {
2132
+ fatal: true,
2133
+ ignoreBOM: false
2134
+ }).decode(unb64(parts[1] ?? "")));
2135
+ }
2136
+ function provisionerPublicKey(value) {
2137
+ const jwk = JSON.parse(value);
2138
+ if (jwk.kty !== "EC" || jwk.crv !== "P-256" || !jwk.x || !jwk.y || jwk.d) throw new Error("Customer authorization requires a public P-256 key");
2139
+ return {
2140
+ kty: "EC",
2141
+ crv: "P-256",
2142
+ x: jwk.x,
2143
+ y: jwk.y
2144
+ };
2145
+ }
2146
+ async function signProvisionerEnvelope(prefix, key, payload) {
2147
+ const encoded = b64(utf8.encode(JSON.stringify(payload)));
2148
+ const signature = await crypto.subtle.sign({
2149
+ name: "ECDSA",
2150
+ hash: "SHA-256"
2151
+ }, key, utf8.encode(`${prefix}\n${encoded}`));
2152
+ return `${prefix}.${encoded}.${b64(new Uint8Array(signature))}`;
2153
+ }
2154
+ async function verifyProvisionerEnvelope(envelope, prefix, publicKeyJwk) {
2155
+ const payload = readProvisionerEnvelope(envelope, prefix);
2156
+ const [, encoded, signature] = envelope.split(".");
2157
+ const key = await crypto.subtle.importKey("jwk", provisionerPublicKey(publicKeyJwk), {
2158
+ name: "ECDSA",
2159
+ namedCurve: "P-256"
2160
+ }, false, ["verify"]);
2161
+ if (!await crypto.subtle.verify({
2162
+ name: "ECDSA",
2163
+ hash: "SHA-256"
2164
+ }, key, unb64(signature ?? ""), utf8.encode(`${prefix}\n${encoded}`))) throw new Error("Invalid customer authorization signature");
2165
+ return payload;
2166
+ }
2167
+ /** The API compares bootstrap to its registration; the Rust service additionally
2168
+ * verifies its MAC with a root-derived key unavailable to the API. */
2169
+ async function verifyProvisionerChain(chain, orgId, provisionerId, now = Date.now()) {
2170
+ provisionerChainSchema.parse(chain);
2171
+ const root = provisionerBootstrapSchema.parse(readProvisionerEnvelope(chain.bootstrap, "pb1"));
2172
+ if (root.orgId !== orgId || root.provisionerId !== provisionerId || root.authority.type !== "user") throw new Error("Customer authorization belongs to another provisioner");
2173
+ provisionerPublicKey(root.authority.publicKeyJwk);
2174
+ let access = {
2175
+ principal: root.authority,
2176
+ targets: ["*"],
2177
+ manage: true,
2178
+ expiresAt: Number.MAX_SAFE_INTEGER,
2179
+ chain
2180
+ };
2181
+ for (const envelope of chain.grants) {
2182
+ if (!access.manage) throw new Error("This permission cannot delegate access");
2183
+ const grant = provisionerGrantSchema.parse(await verifyProvisionerEnvelope(envelope, "pg1", access.principal.publicKeyJwk));
2184
+ if (grant.orgId !== orgId || grant.provisionerId !== provisionerId || grant.issuedAt > now + 3e5 || grant.expiresAt <= now || grant.expiresAt <= grant.issuedAt || grant.expiresAt - grant.issuedAt > 7776e6 || grant.expiresAt > access.expiresAt || !grant.targets.every((t) => access.targets.includes("*") || access.targets.includes(t)) || grant.manage && grant.principal.type !== "user") throw new Error("Expired or invalid customer access grant");
2185
+ provisionerPublicKey(grant.principal.publicKeyJwk);
2186
+ access = {
2187
+ principal: grant.principal,
2188
+ targets: grant.targets,
2189
+ manage: grant.manage,
2190
+ expiresAt: grant.expiresAt,
2191
+ chain
2192
+ };
2193
+ }
2194
+ return access;
2195
+ }
2196
+ /** Targets carry their templates inside encrypted configuration. */
2197
+ function provisionerTransportAction(action) {
2198
+ if (action.kind === "sql") return {
2199
+ kind: "sql",
2200
+ statements: [],
2201
+ parameters: action.parameters
2202
+ };
2203
+ if (action.kind === "ssh-sign") return action;
2204
+ return { kind: "noop" };
2205
+ }
1978
2206
  z$1.enum([
1979
2207
  "postgres",
1980
2208
  "mysql",
@@ -2052,7 +2280,12 @@ const awsRegionSchema$1 = z$1.string().regex(/^[a-z]{2}(?:-[a-z]+)+-\d$/, "must
2052
2280
  * documented safe set and bound the length.
2053
2281
  */
2054
2282
  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_+=,.@-]");
2283
+ /**
2284
+ * An STS session name stamped onto the assumed-role credential (and surfaced in
2285
+ * CloudTrail). We derive it from the lease id, so this is a sanity gate matching
2286
+ * the AWS-documented pattern.
2287
+ */
2288
+ const awsRoleSessionNameSchema = z$1.string().regex(/^[\w+=,.@-]{2,64}$/, "must be 2–64 chars of [A-Za-z0-9_+=,.@-]");
2056
2289
  /**
2057
2290
  * A GCP service-account email the broker is allowed to impersonate (or that
2058
2291
  * appears in a delegation chain). Structurally validated and bounded: it is
@@ -2083,7 +2316,14 @@ const p256PublicKeyJwkSchema = z$1.string().max(2048).refine((s) => {
2083
2316
  return false;
2084
2317
  }
2085
2318
  }, "must be a JWK-serialized P-256 public key");
2086
- z$1.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "must be 1–64 chars of [A-Za-z0-9_-]");
2319
+ /**
2320
+ * A MongoDB user name. Derived from the lease id (so it's server-chosen, not
2321
+ * user input), but validated as a strict token anyway: MongoDB user documents
2322
+ * are structured BSON — there is no string interpolation to inject into — yet a
2323
+ * bounded `[A-Za-z0-9_.-]` name keeps the created principal readable in
2324
+ * `db.getUsers()` and free of the `.`/`$` characters Mongo reserves.
2325
+ */
2326
+ const mongoUserNameSchema = z$1.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "must be 1–64 chars of [A-Za-z0-9_-]");
2087
2327
  /**
2088
2328
  * A MongoDB database name — the db a preset role is granted on, or the
2089
2329
  * authentication database a leased user is created in. MongoDB forbids
@@ -2124,6 +2364,11 @@ const mongoAccessLevelSchema = z$1.enum([
2124
2364
  "readwrite",
2125
2365
  "custom"
2126
2366
  ]);
2367
+ /** The built-in MongoDB role each preset grants on the target database. */
2368
+ const MONGO_PRESET_ROLES = {
2369
+ readonly: "read",
2370
+ readwrite: "readWrite"
2371
+ };
2127
2372
  const connectionSchema = z$1.object({
2128
2373
  host: z$1.string().min(1),
2129
2374
  port: z$1.number().int().min(1).max(65535),
@@ -2134,6 +2379,8 @@ const statementSchema$1 = z$1.string().min(1).max(4e3);
2134
2379
  /** A bare SQL identifier (schema name) — no quotes/whitespace/semicolons. */
2135
2380
  const identifierSchema = z$1.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,62}$/, "must be an identifier");
2136
2381
  const postgresTargetConfigSchema = z$1.object({
2382
+ tls: z$1.boolean().optional(),
2383
+ provisionerId: z$1.string().regex(/^prv_[A-Za-z0-9_-]{32}$/).optional(),
2137
2384
  provider: z$1.literal("postgres"),
2138
2385
  executor: executorModeSchema,
2139
2386
  accessLevel: postgresAccessLevelSchema.optional(),
@@ -2146,6 +2393,8 @@ const postgresTargetConfigSchema = z$1.object({
2146
2393
  /** A MySQL account host part (`'name'@'<host>'`) — no quotes/whitespace. */
2147
2394
  const mysqlHostSchema$1 = z$1.string().regex(/^[A-Za-z0-9_.%:-]{1,255}$/, "must be a host pattern");
2148
2395
  const mysqlTargetConfigSchema = z$1.object({
2396
+ tls: z$1.boolean().optional(),
2397
+ provisionerId: z$1.string().regex(/^prv_[A-Za-z0-9_-]{32}$/).optional(),
2149
2398
  provider: z$1.literal("mysql"),
2150
2399
  executor: executorModeSchema,
2151
2400
  accessLevel: mysqlAccessLevelSchema.optional(),
@@ -2162,6 +2411,7 @@ const redisConnectionSchema = z$1.object({
2162
2411
  db: z$1.number().int().min(0).max(15).optional()
2163
2412
  });
2164
2413
  const redisTargetConfigSchema = z$1.object({
2414
+ provisionerId: z$1.string().regex(/^prv_[A-Za-z0-9_-]{32}$/).optional(),
2165
2415
  provider: z$1.literal("redis"),
2166
2416
  executor: executorModeSchema,
2167
2417
  accessLevel: redisAccessLevelSchema.optional(),
@@ -2171,8 +2421,9 @@ const redisTargetConfigSchema = z$1.object({
2171
2421
  revokeStatements: z$1.array(statementSchema$1).max(16).optional()
2172
2422
  });
2173
2423
  const sshTargetConfigSchema = z$1.object({
2424
+ provisionerId: z$1.string().regex(/^prv_[A-Za-z0-9_-]{32}$/).optional(),
2174
2425
  provider: z$1.literal("ssh"),
2175
- executor: z$1.literal("in_do"),
2426
+ executor: executorModeSchema,
2176
2427
  caPublicKey: sshPublicKeySchema,
2177
2428
  allowedPrincipals: z$1.array(sshPrincipalSchema).max(64).optional(),
2178
2429
  extensions: z$1.array(sshExtensionSchema).max(16).optional(),
@@ -2184,8 +2435,9 @@ const sshTargetConfigSchema = z$1.object({
2184
2435
  });
2185
2436
  const AWS_MAX_TTL_SECONDS = 43200;
2186
2437
  const awsTargetConfigSchema = z$1.object({
2438
+ provisionerId: z$1.string().regex(/^prv_[A-Za-z0-9_-]{32}$/).optional(),
2187
2439
  provider: z$1.literal("aws"),
2188
- executor: z$1.literal("in_do"),
2440
+ executor: executorModeSchema,
2189
2441
  roleArn: awsRoleArnSchema,
2190
2442
  region: awsRegionSchema$1,
2191
2443
  externalId: awsExternalIdSchema.optional(),
@@ -2194,16 +2446,18 @@ const awsTargetConfigSchema = z$1.object({
2194
2446
  });
2195
2447
  const GCP_MAX_TTL_SECONDS = 43200;
2196
2448
  const gcpTargetConfigSchema = z$1.object({
2449
+ provisionerId: z$1.string().regex(/^prv_[A-Za-z0-9_-]{32}$/).optional(),
2197
2450
  provider: z$1.literal("gcp"),
2198
- executor: z$1.literal("in_do"),
2451
+ executor: executorModeSchema,
2199
2452
  serviceAccount: gcpServiceAccountEmailSchema,
2200
2453
  scopes: z$1.array(gcpOauthScopeSchema).min(1).max(32).optional(),
2201
2454
  delegates: z$1.array(gcpServiceAccountEmailSchema).max(8).optional(),
2202
2455
  maxTtlSeconds: z$1.number().int().min(60).max(GCP_MAX_TTL_SECONDS).optional()
2203
2456
  });
2204
2457
  const mongoTargetConfigSchema = z$1.object({
2458
+ provisionerId: z$1.string().regex(/^prv_[A-Za-z0-9_-]{32}$/).optional(),
2205
2459
  provider: z$1.literal("mongodb"),
2206
- executor: z$1.literal("in_do"),
2460
+ executor: executorModeSchema,
2207
2461
  accessLevel: mongoAccessLevelSchema.optional(),
2208
2462
  connection: connectionSchema,
2209
2463
  authSource: mongoDatabaseNameSchema.optional(),
@@ -2211,7 +2465,7 @@ const mongoTargetConfigSchema = z$1.object({
2211
2465
  tls: z$1.boolean().optional(),
2212
2466
  maxTtlSeconds: z$1.number().int().min(60).max(604800).optional()
2213
2467
  });
2214
- const leaseTargetConfigSchema = z$1.discriminatedUnion("provider", [
2468
+ const leaseTargetConfigSchema = z$1.union([
2215
2469
  postgresTargetConfigSchema,
2216
2470
  mysqlTargetConfigSchema,
2217
2471
  redisTargetConfigSchema,
@@ -2219,9 +2473,26 @@ const leaseTargetConfigSchema = z$1.discriminatedUnion("provider", [
2219
2473
  awsTargetConfigSchema,
2220
2474
  gcpTargetConfigSchema,
2221
2475
  mongoTargetConfigSchema,
2222
- ...managedTargetConfigSchema.options
2223
- ]);
2476
+ managedTargetConfigSchema
2477
+ ]).superRefine((config, ctx) => {
2478
+ if (config.executor === "remote" && [
2479
+ "ssh",
2480
+ "aws",
2481
+ "gcp",
2482
+ "mongodb"
2483
+ ].includes(config.provider) && !("provisionerId" in config && config.provisionerId)) ctx.addIssue({
2484
+ code: "custom",
2485
+ path: ["provisionerId"],
2486
+ message: "Select a registered customer provisioner."
2487
+ });
2488
+ if ("provisionerId" in config && config.provisionerId && config.executor !== "remote") ctx.addIssue({
2489
+ code: "custom",
2490
+ path: ["executor"],
2491
+ message: "A provisioner target must run in your network."
2492
+ });
2493
+ });
2224
2494
  z$1.object({
2495
+ id: z$1.string().regex(/^lt_[A-Za-z0-9_-]{24}$/).optional(),
2225
2496
  name: z$1.string().trim().min(1).max(128),
2226
2497
  config: leaseTargetConfigSchema,
2227
2498
  /**
@@ -2230,7 +2501,7 @@ z$1.object({
2230
2501
  * control plane stores only this ciphertext — it never sees the plaintext.
2231
2502
  */
2232
2503
  wrappedAdminSecret: z$1.string().min(1)
2233
- });
2504
+ }).refine((v) => !("provisionerId" in v.config && v.config.provisionerId) || v.wrappedAdminSecret.startsWith("wd1.") && v.wrappedAdminSecret.length <= 49152, "Encrypted provisioner configuration must be a wd1 blob no larger than 48 KiB");
2234
2505
  /** Requested lease lifetime, shared by all providers. */
2235
2506
  const ttlSecondsSchema = z$1.number().int().min(60).max(604800);
2236
2507
  /**
@@ -2241,6 +2512,7 @@ const ttlSecondsSchema = z$1.number().int().min(60).max(604800);
2241
2512
  const mintPostgresLeaseSchema = z$1.object({
2242
2513
  provider: z$1.literal("postgres"),
2243
2514
  targetId: z$1.string().min(1),
2515
+ authorization: provisionerAuthorizationSchema.optional(),
2244
2516
  roleName: postgresRoleNameSchema,
2245
2517
  verifier: scramVerifierSchema,
2246
2518
  ttlSeconds: ttlSecondsSchema
@@ -2253,6 +2525,7 @@ const mintPostgresLeaseSchema = z$1.object({
2253
2525
  const mintMysqlLeaseSchema = z$1.object({
2254
2526
  provider: z$1.literal("mysql"),
2255
2527
  targetId: z$1.string().min(1),
2528
+ authorization: provisionerAuthorizationSchema.optional(),
2256
2529
  roleName: mysqlUserNameSchema,
2257
2530
  verifier: mysqlNativeVerifierSchema,
2258
2531
  ttlSeconds: ttlSecondsSchema
@@ -2265,6 +2538,7 @@ const mintMysqlLeaseSchema = z$1.object({
2265
2538
  const mintRedisLeaseSchema = z$1.object({
2266
2539
  provider: z$1.literal("redis"),
2267
2540
  targetId: z$1.string().min(1),
2541
+ authorization: provisionerAuthorizationSchema.optional(),
2268
2542
  roleName: redisUserNameSchema,
2269
2543
  verifier: redisSha256VerifierSchema,
2270
2544
  ttlSeconds: ttlSecondsSchema
@@ -2277,6 +2551,7 @@ const mintRedisLeaseSchema = z$1.object({
2277
2551
  const mintSshLeaseSchema = z$1.object({
2278
2552
  provider: z$1.literal("ssh"),
2279
2553
  targetId: z$1.string().min(1),
2554
+ authorization: provisionerAuthorizationSchema.optional(),
2280
2555
  publicKey: sshPublicKeySchema,
2281
2556
  principals: z$1.array(sshPrincipalSchema).min(1).max(32),
2282
2557
  ttlSeconds: ttlSecondsSchema
@@ -2293,6 +2568,7 @@ const mintSshLeaseSchema = z$1.object({
2293
2568
  const mintAwsLeaseSchema = z$1.object({
2294
2569
  provider: z$1.literal("aws"),
2295
2570
  targetId: z$1.string().min(1),
2571
+ authorization: provisionerAuthorizationSchema.optional(),
2296
2572
  recipientPublicKey: p256PublicKeyJwkSchema,
2297
2573
  ttlSeconds: z$1.number().int().min(900).max(AWS_MAX_TTL_SECONDS)
2298
2574
  });
@@ -2309,6 +2585,7 @@ const mintAwsLeaseSchema = z$1.object({
2309
2585
  const mintGcpLeaseSchema = z$1.object({
2310
2586
  provider: z$1.literal("gcp"),
2311
2587
  targetId: z$1.string().min(1),
2588
+ authorization: provisionerAuthorizationSchema.optional(),
2312
2589
  recipientPublicKey: p256PublicKeyJwkSchema,
2313
2590
  ttlSeconds: z$1.number().int().min(60).max(GCP_MAX_TTL_SECONDS)
2314
2591
  });
@@ -2322,12 +2599,14 @@ const mintGcpLeaseSchema = z$1.object({
2322
2599
  const mintMongoLeaseSchema = z$1.object({
2323
2600
  provider: z$1.literal("mongodb"),
2324
2601
  targetId: z$1.string().min(1),
2602
+ authorization: provisionerAuthorizationSchema.optional(),
2325
2603
  recipientPublicKey: p256PublicKeyJwkSchema,
2326
2604
  ttlSeconds: ttlSecondsSchema
2327
2605
  });
2328
2606
  const mintManagedLeaseSchema = z$1.object({
2329
2607
  provider: z$1.enum(MANAGED_LEASE_KINDS),
2330
2608
  targetId: z$1.string().min(1),
2609
+ authorization: provisionerAuthorizationSchema.optional(),
2331
2610
  recipientPublicKey: p256PublicKeyJwkSchema.refine((value) => {
2332
2611
  try {
2333
2612
  return !("d" in JSON.parse(value));
@@ -2347,6 +2626,57 @@ z$1.discriminatedUnion("provider", [
2347
2626
  mintMongoLeaseSchema,
2348
2627
  mintManagedLeaseSchema
2349
2628
  ]);
2629
+ // ../../packages/core/src/providers/aws.ts
2630
+ /**
2631
+ * Tier-2 AWS provider. Unlike the verifier-injection providers, the target
2632
+ * (AWS/STS) mints the credential itself, so `render` cannot produce a
2633
+ * zero-knowledge verifier — instead it produces an {@link AwsStsSpec} the broker
2634
+ * executor runs: assume `roleArn` via STS, then wrap the returned credential to
2635
+ * the consumer's ephemeral public key before returning it. `render` stays pure
2636
+ * and secret-free (it never touches the admin credential or the network); the
2637
+ * consumer's private key never leaves their machine, so the plaintext
2638
+ * credential is only ever decryptable there.
2639
+ *
2640
+ * Revocation is a no-op: STS session credentials can't be revoked individually,
2641
+ * so v1 relies on the short `DurationSeconds` STS itself enforces — exactly like
2642
+ * the SSH provider's short-TTL certificates.
2643
+ */
2644
+ /** Clamp a requested TTL into STS's `DurationSeconds` window and the target's cap. */
2645
+ function clampDuration(requested, maxTtlSeconds) {
2646
+ return Math.max(900, Math.min(requested, Math.min(maxTtlSeconds ?? 43200, AWS_MAX_TTL_SECONDS)));
2647
+ }
2648
+ const awsProvider = {
2649
+ kind: "aws",
2650
+ zkTier: 2,
2651
+ render(config, req, ctx) {
2652
+ if (req.provider !== "aws") throw new Error("aws provider received a non-aws request");
2653
+ const cfg = config;
2654
+ awsRoleArnSchema.parse(cfg.roleArn);
2655
+ p256PublicKeyJwkSchema.parse(req.recipientPublicKey);
2656
+ const roleSessionName = awsRoleSessionNameSchema.parse(ctx.leaseId);
2657
+ const durationSeconds = clampDuration(req.ttlSeconds, cfg.maxTtlSeconds);
2658
+ return {
2659
+ resourceRef: roleSessionName,
2660
+ expiresAt: new Date(ctx.nowMs + durationSeconds * 1e3).toISOString(),
2661
+ provision: {
2662
+ kind: "aws-sts",
2663
+ spec: {
2664
+ roleArn: cfg.roleArn,
2665
+ roleSessionName,
2666
+ durationSeconds,
2667
+ region: cfg.region,
2668
+ externalId: cfg.externalId,
2669
+ policy: cfg.sessionPolicy,
2670
+ recipientPublicKey: req.recipientPublicKey
2671
+ }
2672
+ },
2673
+ revoke: { kind: "noop" }
2674
+ };
2675
+ },
2676
+ renderRevoke() {
2677
+ return { kind: "noop" };
2678
+ }
2679
+ };
2350
2680
  /**
2351
2681
  * The one-time IAM setup an admin performs so seekrit can assume the target
2352
2682
  * role. Analogue of `sshHostSetupInstructions` / `postgresGroupBootstrapSql` —
@@ -2373,6 +2703,59 @@ function awsTrustPolicyInstructions(config) {
2373
2703
  `# on ${config.roleArn}. seekrit only ever calls AssumeRole on this role.`
2374
2704
  ].join("\n");
2375
2705
  }
2706
+ // ../../packages/core/src/providers/gcp.ts
2707
+ /**
2708
+ * Tier-2 GCP provider. Like {@link awsProvider}, the target (GCP's IAM
2709
+ * Credentials API) mints the credential itself, so `render` cannot produce a
2710
+ * zero-knowledge verifier — instead it produces a {@link GcpImpersonateSpec} the
2711
+ * broker executor runs: impersonate `serviceAccount` via `generateAccessToken`,
2712
+ * then wrap the returned OAuth token to the consumer's ephemeral public key
2713
+ * before returning it. `render` stays pure and secret-free (it never touches the
2714
+ * admin key or the network); the consumer's private key never leaves their
2715
+ * machine, so the plaintext token is only ever decryptable there.
2716
+ *
2717
+ * Revocation is a no-op: short-lived access tokens can't be revoked
2718
+ * individually, so v1 relies on the short `lifetime` GCP itself enforces —
2719
+ * exactly like the AWS and SSH providers.
2720
+ */
2721
+ /** The default scope when a target sets none — full API access, IAM-scoped. */
2722
+ const GCP_DEFAULT_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
2723
+ /** Clamp a requested TTL into GCP's `lifetime` window and the target's cap. */
2724
+ function clampLifetime(requested, maxTtlSeconds) {
2725
+ return Math.max(60, Math.min(requested, Math.min(maxTtlSeconds ?? 43200, GCP_MAX_TTL_SECONDS)));
2726
+ }
2727
+ const gcpProvider = {
2728
+ kind: "gcp",
2729
+ zkTier: 2,
2730
+ render(config, req, ctx) {
2731
+ if (req.provider !== "gcp") throw new Error("gcp provider received a non-gcp request");
2732
+ const cfg = config;
2733
+ gcpServiceAccountEmailSchema.parse(cfg.serviceAccount);
2734
+ p256PublicKeyJwkSchema.parse(req.recipientPublicKey);
2735
+ const scopes = cfg.scopes?.length ? cfg.scopes : [GCP_DEFAULT_SCOPE];
2736
+ for (const scope of scopes) gcpOauthScopeSchema.parse(scope);
2737
+ const lifetimeSeconds = clampLifetime(req.ttlSeconds, cfg.maxTtlSeconds);
2738
+ const expiresAt = new Date(ctx.nowMs + lifetimeSeconds * 1e3).toISOString();
2739
+ return {
2740
+ resourceRef: cfg.serviceAccount,
2741
+ expiresAt,
2742
+ provision: {
2743
+ kind: "gcp-iam",
2744
+ spec: {
2745
+ serviceAccount: cfg.serviceAccount,
2746
+ scopes,
2747
+ lifetimeSeconds,
2748
+ delegates: cfg.delegates,
2749
+ recipientPublicKey: req.recipientPublicKey
2750
+ }
2751
+ },
2752
+ revoke: { kind: "noop" }
2753
+ };
2754
+ },
2755
+ renderRevoke() {
2756
+ return { kind: "noop" };
2757
+ }
2758
+ };
2376
2759
  /**
2377
2760
  * The one-time IAM setup an admin performs so seekrit can impersonate the target
2378
2761
  * service account. Analogue of `awsTrustPolicyInstructions` — printed for the
@@ -2394,6 +2777,145 @@ function gcpSetupInstructions(config) {
2394
2777
  "gcloud services enable iamcredentials.googleapis.com"
2395
2778
  ].join("\n");
2396
2779
  }
2780
+ // ../../packages/core/src/providers/managed.ts
2781
+ function managedLeaseProvider(kind) {
2782
+ return {
2783
+ kind,
2784
+ zkTier: 2,
2785
+ render(target, request, context) {
2786
+ const config = managedTargetConfigSchema.parse(target);
2787
+ if (config.provider !== kind || request.provider !== kind || !("recipientPublicKey" in request)) throw new Error("managed credential provider mismatch");
2788
+ p256PublicKeyJwkSchema.parse(request.recipientPublicKey);
2789
+ const name = `sk_${context.leaseId}`;
2790
+ if (!/^sk_[a-zA-Z0-9_-]{1,64}$/.test(name)) throw new Error("invalid lease id");
2791
+ const ttl = Math.min(request.ttlSeconds, config.maxTtlSeconds ?? 604800);
2792
+ const expiresAt = new Date(context.nowMs + ttl * 1e3).toISOString();
2793
+ const spec = {
2794
+ config,
2795
+ name,
2796
+ expiresAt,
2797
+ recipientPublicKey: request.recipientPublicKey
2798
+ };
2799
+ return {
2800
+ resourceRef: name,
2801
+ expiresAt,
2802
+ provision: {
2803
+ kind: "managed-credential",
2804
+ spec
2805
+ },
2806
+ revoke: {
2807
+ kind: "managed-credential",
2808
+ spec: {
2809
+ ...spec,
2810
+ recipientPublicKey: ""
2811
+ }
2812
+ }
2813
+ };
2814
+ },
2815
+ renderRevoke(target, resourceRef) {
2816
+ const config = managedTargetConfigSchema.parse(target);
2817
+ if (config.provider !== kind || !/^sk_[a-zA-Z0-9_-]{1,64}$/.test(resourceRef)) throw new Error("invalid managed credential revoke");
2818
+ return {
2819
+ kind: "managed-credential",
2820
+ spec: {
2821
+ config,
2822
+ name: resourceRef,
2823
+ recipientPublicKey: "",
2824
+ expiresAt: ""
2825
+ }
2826
+ };
2827
+ }
2828
+ };
2829
+ }
2830
+ // ../../packages/core/src/providers/mongodb.ts
2831
+ /**
2832
+ * Tier-2 MongoDB provider. Unlike the verifier-injection providers, MongoDB's
2833
+ * `createUser` hashes the password *server-side* (there is no supported path to
2834
+ * inject a precomputed SCRAM credential), so `render` cannot produce a
2835
+ * zero-knowledge verifier. Instead it produces a {@link MongoUserSpec} the
2836
+ * broker executor runs: generate a password, `createUser` with the preset
2837
+ * roles, then wrap the credential to the consumer's ephemeral public key before
2838
+ * returning it. `render` stays pure and secret-free — it never generates the
2839
+ * password, touches the admin credential, or hits the network; the consumer's
2840
+ * private key never leaves their machine, so the plaintext credential is only
2841
+ * ever decryptable there.
2842
+ *
2843
+ * Revocation is real (unlike AWS/SSH): the broker's alarm-driven `dropUser` ends
2844
+ * the lease. Access levels map to MongoDB's built-in roles — `read` /
2845
+ * `readWrite` on the target database — or an explicit `roles` array for custom.
2846
+ */
2847
+ /** Clamp a requested TTL to the target's optional cap. */
2848
+ function clampTtl(requested, maxTtlSeconds) {
2849
+ return maxTtlSeconds ? Math.min(requested, maxTtlSeconds) : requested;
2850
+ }
2851
+ /** The role grants a target's access level maps to. Defaults to read-only. */
2852
+ function rolesFor(config) {
2853
+ const db = config.connection.database;
2854
+ if (config.accessLevel === "custom") {
2855
+ if (!config.roles || config.roles.length === 0) throw new Error("mongodb custom target requires a non-empty roles array");
2856
+ return config.roles;
2857
+ }
2858
+ const preset = config.accessLevel === "readwrite" ? "readwrite" : "readonly";
2859
+ return [{
2860
+ role: MONGO_PRESET_ROLES[preset],
2861
+ db
2862
+ }];
2863
+ }
2864
+ const mongoProvider = {
2865
+ kind: "mongodb",
2866
+ zkTier: 2,
2867
+ render(config, req, ctx) {
2868
+ if (config.provider !== "mongodb") throw new Error("mongoProvider given a non-mongodb config");
2869
+ if (req.provider !== "mongodb") throw new Error("mongo provider received a non-mongodb request");
2870
+ p256PublicKeyJwkSchema.parse(req.recipientPublicKey);
2871
+ const username = mongoUserNameSchema.parse(ctx.leaseId);
2872
+ const authSource = mongoDatabaseNameSchema.parse(config.authSource ?? "admin");
2873
+ const database = mongoDatabaseNameSchema.parse(config.connection.database);
2874
+ const roles = rolesFor(config);
2875
+ const ttlSeconds = clampTtl(req.ttlSeconds, config.maxTtlSeconds);
2876
+ const expiresAt = new Date(ctx.nowMs + ttlSeconds * 1e3).toISOString();
2877
+ return {
2878
+ resourceRef: username,
2879
+ expiresAt,
2880
+ provision: {
2881
+ kind: "mongo-user",
2882
+ spec: {
2883
+ username,
2884
+ authSource,
2885
+ database,
2886
+ roles,
2887
+ recipientPublicKey: req.recipientPublicKey,
2888
+ expiresAt
2889
+ }
2890
+ },
2891
+ revoke: {
2892
+ kind: "mongo-user",
2893
+ spec: {
2894
+ username,
2895
+ authSource,
2896
+ database: "",
2897
+ roles: [],
2898
+ recipientPublicKey: "",
2899
+ expiresAt: ""
2900
+ }
2901
+ }
2902
+ };
2903
+ },
2904
+ renderRevoke(config, resourceRef) {
2905
+ if (config.provider !== "mongodb") throw new Error("mongoProvider given a non-mongodb config");
2906
+ return {
2907
+ kind: "mongo-user",
2908
+ spec: {
2909
+ username: mongoUserNameSchema.parse(resourceRef),
2910
+ authSource: mongoDatabaseNameSchema.parse(config.authSource ?? "admin"),
2911
+ database: "",
2912
+ roles: [],
2913
+ recipientPublicKey: "",
2914
+ expiresAt: ""
2915
+ }
2916
+ };
2917
+ }
2918
+ };
2397
2919
  /**
2398
2920
  * The one-time setup an admin performs so seekrit can provision users: create a
2399
2921
  * dedicated provisioning user with `userAdmin` on the target database (or
@@ -2418,6 +2940,187 @@ function mongoAdminSetupInstructions(config) {
2418
2940
  `// --uri "mongodb://seekrit_provisioner:<pwd>@${config.connection.host}:${config.connection.port}/?authSource=${authSource}"`
2419
2941
  ].join("\n");
2420
2942
  }
2943
+ // ../../packages/core/src/providers/mysql.ts
2944
+ /**
2945
+ * Tier-1 MySQL/MariaDB provider. The consumer generated the password and its
2946
+ * `mysql_native_password` verifier locally (see @seekrit/crypto
2947
+ * `mysqlNativePasswordVerifier`); we only ever see the verifier, which
2948
+ * `CREATE USER … IDENTIFIED WITH mysql_native_password AS '<hash>'` stores
2949
+ * verbatim and which cannot be used to authenticate (login is a
2950
+ * challenge-response needing a `SHA1(password)` preimage). So the handed-out
2951
+ * credential is zero-knowledge end to end — the provider merely renders SQL.
2952
+ *
2953
+ * Unlike Postgres there is no account-level `VALID UNTIL`: the broker's
2954
+ * alarm-driven `DROP USER` is the sole expiry mechanism. Preset access levels
2955
+ * apply inline `GRANT`s (MySQL roles need activation, an awkward fit for a
2956
+ * plain connection URL), so there is no shared group role / setup SQL.
2957
+ */
2958
+ const DEFAULT_HOST = "%";
2959
+ /** Mirror of the registration-time host schema (render must not trust inputs). */
2960
+ const HOST_RE = /^[A-Za-z0-9_.%:-]{1,255}$/;
2961
+ /** Escape a backtick-quoted identifier by doubling backticks. */
2962
+ function ident(name) {
2963
+ return name.replace(/`/g, "``");
2964
+ }
2965
+ function render$2(template, vars) {
2966
+ return template.replace(/\{\{(name|verifier)\}\}/g, (_, key) => key === "name" ? vars.name : vars.verifier);
2967
+ }
2968
+ /**
2969
+ * Concrete create/revoke templates for a target. Presets synthesize a
2970
+ * `CREATE USER` plus an inline `GRANT` scoped to the target database; custom
2971
+ * (or a legacy target with no access level) uses the admin's statements,
2972
+ * falling back to a bare CREATE USER / DROP USER. Host and database come from
2973
+ * the (trusted, registration-validated) config and are baked in directly;
2974
+ * only `{{name}}`/`{{verifier}}` remain as per-lease placeholders.
2975
+ */
2976
+ function statementsFor$2(config) {
2977
+ const account = `'{{name}}'@'${config.userHost && HOST_RE.test(config.userHost) ? config.userHost : DEFAULT_HOST}'`;
2978
+ const create = `CREATE USER ${account} IDENTIFIED WITH mysql_native_password AS '{{verifier}}';`;
2979
+ const revoke = `DROP USER IF EXISTS ${account};`;
2980
+ if (config.accessLevel === "readonly" || config.accessLevel === "readwrite") {
2981
+ const db = ident(config.connection.database);
2982
+ return {
2983
+ create: [create, `GRANT ${config.accessLevel === "readonly" ? "SELECT" : "SELECT, INSERT, UPDATE, DELETE"} ON \`${db}\`.* TO ${account};`],
2984
+ revoke: [revoke]
2985
+ };
2986
+ }
2987
+ return {
2988
+ create: config.createStatements ?? [create],
2989
+ revoke: config.revokeStatements ?? [revoke]
2990
+ };
2991
+ }
2992
+ const mysqlProvider = {
2993
+ kind: "mysql",
2994
+ zkTier: 1,
2995
+ render(config, req, ctx) {
2996
+ if (config.provider !== "mysql") throw new Error("mysqlProvider given a non-mysql config");
2997
+ if (req.provider !== "mysql") throw new Error("mysql provider received a non-mysql request");
2998
+ mysqlUserNameSchema.parse(req.roleName);
2999
+ mysqlNativeVerifierSchema.parse(req.verifier);
3000
+ const expiresAt = new Date(ctx.nowMs + req.ttlSeconds * 1e3).toISOString();
3001
+ const vars = {
3002
+ name: req.roleName,
3003
+ verifier: req.verifier
3004
+ };
3005
+ const { create, revoke } = statementsFor$2(config);
3006
+ const parameters = config.executor === "remote" && config.provisionerId ? { parameters: {
3007
+ name: req.roleName,
3008
+ verifier: req.verifier,
3009
+ validUntil: expiresAt
3010
+ } } : {};
3011
+ return {
3012
+ resourceRef: req.roleName,
3013
+ expiresAt,
3014
+ provision: {
3015
+ kind: "sql",
3016
+ ...parameters,
3017
+ statements: create.map((s) => render$2(s, vars))
3018
+ },
3019
+ revoke: {
3020
+ kind: "sql",
3021
+ ...parameters,
3022
+ statements: revoke.map((s) => render$2(s, {
3023
+ name: req.roleName,
3024
+ verifier: ""
3025
+ }))
3026
+ }
3027
+ };
3028
+ },
3029
+ renderRevoke(config, resourceRef) {
3030
+ if (config.provider !== "mysql") throw new Error("mysqlProvider given a non-mysql config");
3031
+ mysqlUserNameSchema.parse(resourceRef);
3032
+ return {
3033
+ kind: "sql",
3034
+ statements: statementsFor$2(config).revoke.map((s) => render$2(s, {
3035
+ name: resourceRef,
3036
+ verifier: ""
3037
+ }))
3038
+ };
3039
+ }
3040
+ };
3041
+ // ../../packages/core/src/providers/postgres.ts
3042
+ /**
3043
+ * Tier-1 Postgres provider. The consumer generated the password and its SCRAM
3044
+ * verifier locally (see @seekrit/crypto `scramSha256Verifier`); we only ever
3045
+ * see the verifier, which `CREATE ROLE … PASSWORD` stores verbatim and which
3046
+ * cannot be used to authenticate. So the handed-out credential is
3047
+ * zero-knowledge end to end — the provider merely renders SQL.
3048
+ *
3049
+ * Per-lease inputs are strict tokens (validated role name + verifier) and the
3050
+ * SQL is either an admin-configured template (custom) or derived from a preset
3051
+ * access level backed by a shared group role, so substitution cannot inject SQL.
3052
+ */
3053
+ const DEFAULT_CREATE$1 = [`CREATE ROLE "{{name}}" LOGIN PASSWORD '{{verifier}}' VALID UNTIL '{{valid_until}}';`];
3054
+ const DEFAULT_REVOKE$1 = [`DROP ROLE IF EXISTS "{{name}}";`];
3055
+ function render$1(template, vars) {
3056
+ return template.replace(/\{\{(name|verifier|valid_until)\}\}/g, (_, key) => vars[key] ?? "");
3057
+ }
3058
+ /**
3059
+ * The concrete create/revoke templates for a target. Read-only and read-write
3060
+ * create the temp role `IN ROLE <group>` so it inherits the group's grants;
3061
+ * custom (or a legacy target with no access level) uses the admin's statements,
3062
+ * falling back to a bare CREATE ROLE.
3063
+ */
3064
+ function statementsFor$1(config) {
3065
+ if (config.accessLevel === "readonly" || config.accessLevel === "readwrite") return {
3066
+ create: [`CREATE ROLE "{{name}}" LOGIN PASSWORD '{{verifier}}' VALID UNTIL '{{valid_until}}' IN ROLE ${POSTGRES_GROUP_ROLES[config.accessLevel]};`],
3067
+ revoke: DEFAULT_REVOKE$1
3068
+ };
3069
+ return {
3070
+ create: config.createStatements ?? DEFAULT_CREATE$1,
3071
+ revoke: config.revokeStatements ?? DEFAULT_REVOKE$1
3072
+ };
3073
+ }
3074
+ const postgresProvider = {
3075
+ kind: "postgres",
3076
+ zkTier: 1,
3077
+ render(config, req, ctx) {
3078
+ if (req.provider !== "postgres") throw new Error("postgres provider received a non-postgres request");
3079
+ postgresRoleNameSchema.parse(req.roleName);
3080
+ scramVerifierSchema.parse(req.verifier);
3081
+ const expiresAt = new Date(ctx.nowMs + req.ttlSeconds * 1e3).toISOString();
3082
+ const vars = {
3083
+ name: req.roleName,
3084
+ verifier: req.verifier,
3085
+ valid_until: expiresAt
3086
+ };
3087
+ const { create, revoke } = statementsFor$1(config);
3088
+ const parameters = config.executor === "remote" && config.provisionerId ? { parameters: {
3089
+ name: req.roleName,
3090
+ verifier: req.verifier,
3091
+ validUntil: expiresAt
3092
+ } } : {};
3093
+ return {
3094
+ resourceRef: req.roleName,
3095
+ expiresAt,
3096
+ provision: {
3097
+ kind: "sql",
3098
+ ...parameters,
3099
+ statements: create.map((s) => render$1(s, vars))
3100
+ },
3101
+ revoke: {
3102
+ kind: "sql",
3103
+ ...parameters,
3104
+ statements: revoke.map((s) => render$1(s, {
3105
+ name: req.roleName,
3106
+ verifier: "",
3107
+ valid_until: ""
3108
+ }))
3109
+ }
3110
+ };
3111
+ },
3112
+ renderRevoke(config, resourceRef) {
3113
+ postgresRoleNameSchema.parse(resourceRef);
3114
+ return {
3115
+ kind: "sql",
3116
+ statements: statementsFor$1(config).revoke.map((s) => render$1(s, {
3117
+ name: resourceRef,
3118
+ verifier: "",
3119
+ valid_until: ""
3120
+ }))
3121
+ };
3122
+ }
3123
+ };
2421
3124
  /**
2422
3125
  * The one-time setup SQL an admin runs to create the shared group role that a
2423
3126
  * read-only / read-write target's leased credentials inherit. Idempotent (safe
@@ -2448,6 +3151,156 @@ function postgresGroupBootstrapSql(config) {
2448
3151
  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};`);
2449
3152
  return lines.join("\n");
2450
3153
  }
3154
+ // ../../packages/core/src/providers/redis.ts
3155
+ /**
3156
+ * Tier-1 Redis (6+) provider. The consumer generated the password and its
3157
+ * SHA-256 hex digest locally (see @seekrit/crypto `redisSha256Verifier`); we
3158
+ * only ever see the digest, which `ACL SETUSER … on #<hex>` stores verbatim and
3159
+ * which cannot be used to authenticate (Redis `AUTH` hashes the plaintext it
3160
+ * receives and compares, so the stored digest is a preimage). So the handed-out
3161
+ * credential is zero-knowledge end to end — the provider merely renders
3162
+ * commands.
3163
+ *
3164
+ * Unlike Postgres there is no account-level expiry: the broker's alarm-driven
3165
+ * `ACL DELUSER` is the sole expiry mechanism (as with MySQL). Preset access
3166
+ * levels apply inline ACL rules (Redis ACL has no role inheritance), so there is
3167
+ * no shared group role / setup step.
3168
+ *
3169
+ * Each rendered statement is a single Redis command line; the executor
3170
+ * whitespace-tokenizes it into RESP arguments. The per-lease inputs are strict
3171
+ * tokens (validated user name + hex digest) with no whitespace or ACL-rule
3172
+ * characters, so substitution cannot inject an extra argument or permission.
3173
+ */
3174
+ const DEFAULT_CREATE = ["ACL SETUSER {{name}} on #{{verifier}}"];
3175
+ const DEFAULT_REVOKE = ["ACL DELUSER {{name}}"];
3176
+ /**
3177
+ * Inline ACL rules each preset grants: all keys (`~*`), the read (or read+write)
3178
+ * command category, and `+@connection` so client-handshake commands (HELLO,
3179
+ * PING, AUTH) work. No pub/sub channels (`&*`) — the categories don't enable
3180
+ * pub/sub anyway, and `&*` needs Redis 6.2+.
3181
+ */
3182
+ const PRESET_RULES = {
3183
+ readonly: "~* +@read +@connection",
3184
+ readwrite: "~* +@read +@write +@connection"
3185
+ };
3186
+ function render(template, vars) {
3187
+ return template.replace(/\{\{(name|verifier)\}\}/g, (_, key) => key === "name" ? vars.name : vars.verifier);
3188
+ }
3189
+ /**
3190
+ * Concrete create/revoke command lines for a target. Presets synthesize an
3191
+ * `ACL SETUSER` that enables the user (`on`), sets its password digest, and
3192
+ * grants the preset's inline rules; custom (or a legacy target with no access
3193
+ * level) uses the admin's statements, falling back to a no-privilege user.
3194
+ */
3195
+ function statementsFor(config) {
3196
+ if (config.accessLevel === "readonly" || config.accessLevel === "readwrite") return {
3197
+ create: [`ACL SETUSER {{name}} on #{{verifier}} ${PRESET_RULES[config.accessLevel]}`],
3198
+ revoke: DEFAULT_REVOKE
3199
+ };
3200
+ return {
3201
+ create: config.createStatements ?? DEFAULT_CREATE,
3202
+ revoke: config.revokeStatements ?? DEFAULT_REVOKE
3203
+ };
3204
+ }
3205
+ const redisProvider = {
3206
+ kind: "redis",
3207
+ zkTier: 1,
3208
+ render(config, req, ctx) {
3209
+ if (config.provider !== "redis") throw new Error("redisProvider given a non-redis config");
3210
+ if (req.provider !== "redis") throw new Error("redis provider received a non-redis request");
3211
+ redisUserNameSchema.parse(req.roleName);
3212
+ redisSha256VerifierSchema.parse(req.verifier);
3213
+ const expiresAt = new Date(ctx.nowMs + req.ttlSeconds * 1e3).toISOString();
3214
+ const vars = {
3215
+ name: req.roleName,
3216
+ verifier: req.verifier
3217
+ };
3218
+ const { create, revoke } = statementsFor(config);
3219
+ const parameters = config.executor === "remote" && config.provisionerId ? { parameters: {
3220
+ name: req.roleName,
3221
+ verifier: req.verifier,
3222
+ validUntil: expiresAt
3223
+ } } : {};
3224
+ return {
3225
+ resourceRef: req.roleName,
3226
+ expiresAt,
3227
+ provision: {
3228
+ kind: "sql",
3229
+ ...parameters,
3230
+ statements: create.map((s) => render(s, vars))
3231
+ },
3232
+ revoke: {
3233
+ kind: "sql",
3234
+ ...parameters,
3235
+ statements: revoke.map((s) => render(s, {
3236
+ name: req.roleName,
3237
+ verifier: ""
3238
+ }))
3239
+ }
3240
+ };
3241
+ },
3242
+ renderRevoke(config, resourceRef) {
3243
+ if (config.provider !== "redis") throw new Error("redisProvider given a non-redis config");
3244
+ redisUserNameSchema.parse(resourceRef);
3245
+ return {
3246
+ kind: "sql",
3247
+ statements: statementsFor(config).revoke.map((s) => render(s, {
3248
+ name: resourceRef,
3249
+ verifier: ""
3250
+ }))
3251
+ };
3252
+ }
3253
+ };
3254
+ // ../../packages/core/src/providers/ssh.ts
3255
+ /**
3256
+ * Tier-1 SSH certificate-authority provider. The consumer generated an
3257
+ * ephemeral keypair locally and sent only its public key; `render` produces a
3258
+ * validated {@link SshCertSpec} that the broker's `SshCaExecutor` signs with the
3259
+ * CA key. So render itself is pure and secret-free — it never sees the CA key,
3260
+ * and the consumer's private key never leaves their machine.
3261
+ *
3262
+ * A clock-skew grace is applied to `validAfter` so freshly minted certs work on
3263
+ * hosts whose clocks lag slightly. Revocation is a no-op: certificates are
3264
+ * self-contained, so v1 relies on short TTLs (a KRL is a follow-up).
3265
+ */
3266
+ /** Backdate the validity window so minor host clock skew doesn't reject certs. */
3267
+ const CLOCK_SKEW_GRACE_SECONDS = 60;
3268
+ const sshProvider = {
3269
+ kind: "ssh",
3270
+ zkTier: 1,
3271
+ render(config, req, ctx) {
3272
+ if (req.provider !== "ssh") throw new Error("ssh provider received a non-ssh request");
3273
+ const cfg = config;
3274
+ sshPublicKeySchema.parse(req.publicKey);
3275
+ const principals = req.principals.map((p) => sshPrincipalSchema.parse(p));
3276
+ if (cfg.allowedPrincipals?.length) {
3277
+ for (const p of principals) if (!cfg.allowedPrincipals.includes(p)) throw new Error(`principal "${p}" is not allowed by this target`);
3278
+ }
3279
+ const ttl = cfg.maxTtlSeconds ? Math.min(req.ttlSeconds, cfg.maxTtlSeconds) : req.ttlSeconds;
3280
+ const nowSec = Math.floor(ctx.nowMs / 1e3);
3281
+ const validAfter = nowSec - CLOCK_SKEW_GRACE_SECONDS;
3282
+ const validBefore = nowSec + ttl;
3283
+ return {
3284
+ resourceRef: ctx.leaseId,
3285
+ expiresAt: (/* @__PURE__ */ new Date(validBefore * 1e3)).toISOString(),
3286
+ provision: {
3287
+ kind: "ssh-sign",
3288
+ spec: {
3289
+ userPublicKey: req.publicKey,
3290
+ keyId: ctx.leaseId,
3291
+ principals,
3292
+ validAfter,
3293
+ validBefore,
3294
+ extensions: cfg.extensions
3295
+ }
3296
+ },
3297
+ revoke: { kind: "noop" }
3298
+ };
3299
+ },
3300
+ renderRevoke() {
3301
+ return { kind: "noop" };
3302
+ }
3303
+ };
2451
3304
  /**
2452
3305
  * The one-time host setup an admin runs so a target's certificates are accepted.
2453
3306
  * Analogue of `postgresGroupBootstrapSql` — displayed for the admin to run, not
@@ -2467,6 +3320,57 @@ function sshHostSetupInstructions(config) {
2467
3320
  "sudo systemctl reload sshd"
2468
3321
  ].join("\n");
2469
3322
  }
3323
+ // ../../packages/core/src/providers/index.ts
3324
+ /** Registry — new providers register here so callers resolve by kind. */
3325
+ const leaseProviders = {
3326
+ postgres: postgresProvider,
3327
+ mysql: mysqlProvider,
3328
+ redis: redisProvider,
3329
+ ssh: sshProvider,
3330
+ aws: awsProvider,
3331
+ gcp: gcpProvider,
3332
+ mongodb: mongoProvider,
3333
+ elasticsearch: managedLeaseProvider("elasticsearch"),
3334
+ clickhouse: managedLeaseProvider("clickhouse"),
3335
+ rabbitmq: managedLeaseProvider("rabbitmq"),
3336
+ milvus: managedLeaseProvider("milvus"),
3337
+ "mongodb-atlas": managedLeaseProvider("mongodb-atlas"),
3338
+ couchbase: managedLeaseProvider("couchbase"),
3339
+ snowflake: managedLeaseProvider("snowflake"),
3340
+ elasticache: managedLeaseProvider("elasticache"),
3341
+ memorydb: managedLeaseProvider("memorydb"),
3342
+ openrouter: managedLeaseProvider("openrouter"),
3343
+ tailscale: managedLeaseProvider("tailscale")
3344
+ };
3345
+ function leaseProviderFor(kind) {
3346
+ return leaseProviders[kind];
3347
+ }
3348
+ // ../../packages/core/src/provisioners.ts
3349
+ const provisionerIdSchema = z$1.string().regex(/^prv_[A-Za-z0-9_-]{32}$/);
3350
+ z$1.object({
3351
+ id: provisionerIdSchema,
3352
+ name: z$1.string().trim().min(1).max(128),
3353
+ transport: z$1.enum(["pull", "push"]).default("pull"),
3354
+ provisionerUrl: managedEndpointSchema.optional(),
3355
+ publicKeyJwk: p256PublicKeyJwkSchema.refine((value) => !("d" in JSON.parse(value)), "Supply only the public key"),
3356
+ wrappedSigningKey: z$1.string().max(16384).startsWith("wd1."),
3357
+ bootstrap: z$1.string().max(4096).startsWith("pb1.")
3358
+ }).strict().superRefine((v, ctx) => {
3359
+ if (v.transport === "push" && !v.provisionerUrl) ctx.addIssue({
3360
+ code: "custom",
3361
+ path: ["provisionerUrl"],
3362
+ message: "Inbound mode requires an HTTPS endpoint."
3363
+ });
3364
+ if (v.transport === "pull" && v.provisionerUrl) ctx.addIssue({
3365
+ code: "custom",
3366
+ path: ["provisionerUrl"],
3367
+ message: "Outbound mode does not use an endpoint."
3368
+ });
3369
+ });
3370
+ function provisionerIdFor(config) {
3371
+ if (!config || typeof config !== "object" || !("executor" in config) || config.executor !== "remote" || !("provisionerId" in config)) return void 0;
3372
+ return typeof config.provisionerId === "string" ? config.provisionerId : void 0;
3373
+ }
2470
3374
  // ../../packages/core/src/slug.ts
2471
3375
  /**
2472
3376
  * The slug rule, with no dependencies — so a caller that cannot afford zod (the
@@ -2547,6 +3451,12 @@ const AUDIT_ACTIONS = [
2547
3451
  "lease.created",
2548
3452
  "lease.revoked",
2549
3453
  "lease.expired",
3454
+ "lease.provisioner_job_claimed",
3455
+ "lease.provisioner_registered",
3456
+ "lease.provisioner_access_granted",
3457
+ "lease.provisioner_access_revoked",
3458
+ "lease.provisioner_deleted",
3459
+ "lease.provisioner_job_completed",
2550
3460
  "log_sink.configured",
2551
3461
  "log_sink.deleted",
2552
3462
  "log_sink.test",
@@ -5552,6 +6462,15 @@ function policySignerJwk(publicKeyJwk) {
5552
6462
  y: jwk.y
5553
6463
  };
5554
6464
  }
6465
+ // ../../packages/crypto/src/provisioner.ts
6466
+ async function sealProvisionerTarget(publicKeyJwk, input) {
6467
+ const wrapped = await wrapDek(utf8Encode(JSON.stringify({
6468
+ version: 2,
6469
+ ...input
6470
+ })), publicKeyJwk);
6471
+ if (wrapped.length > 49152) throw new Error("Target configuration is too large (48 KiB encrypted limit).");
6472
+ return wrapped;
6473
+ }
5555
6474
  // ../../packages/crypto/src/shamir.ts
5556
6475
  /**
5557
6476
  * Shamir's Secret Sharing over GF(2^8) — the same field AES uses, with the
@@ -6140,7 +7059,7 @@ async function createAgentTaskToken() {
6140
7059
  };
6141
7060
  }
6142
7061
  // package.json
6143
- var version = "2.7.0";
7062
+ var version = "2.8.0";
6144
7063
  // ../../packages/api-client/src/index.ts
6145
7064
  /**
6146
7065
  * The proposer's note travels as a header, not a body field: an unprotected
@@ -6161,17 +7080,78 @@ var SeekritApiError = class extends Error {
6161
7080
  this.code = code;
6162
7081
  }
6163
7082
  };
6164
- var SeekritClient = class {
7083
+ var SeekritClient = class SeekritClient {
6165
7084
  baseUrl;
7085
+ getLeaseSigner;
6166
7086
  auth;
6167
7087
  fetchImpl;
6168
7088
  client;
6169
7089
  constructor(options) {
6170
7090
  this.baseUrl = options.baseUrl.replace(/\/$/, "");
6171
7091
  this.auth = options.auth;
7092
+ this.getLeaseSigner = options.getLeaseSigner;
6172
7093
  this.fetchImpl = options.fetch ?? ((...args) => fetch(...args));
6173
7094
  this.client = options.client;
6174
7095
  }
7096
+ /** Reuse this session while signing leases with its unlocked customer key. */
7097
+ withLeaseSigner(getLeaseSigner) {
7098
+ return new SeekritClient({
7099
+ baseUrl: this.baseUrl,
7100
+ auth: this.auth,
7101
+ fetch: this.fetchImpl,
7102
+ client: this.client,
7103
+ getLeaseSigner
7104
+ });
7105
+ }
7106
+ getProvisionerAccess(orgId, id) {
7107
+ return this.request("GET", `/v1/orgs/${orgId}/leases/provisioners/${id}/access`);
7108
+ }
7109
+ grantProvisionerAccess(orgId, id, chain) {
7110
+ return this.request("POST", `/v1/orgs/${orgId}/leases/provisioners/${id}/access`, chain);
7111
+ }
7112
+ revokeProvisionerAccess(orgId, id, type, principalId) {
7113
+ return this.request("DELETE", `/v1/orgs/${orgId}/leases/provisioners/${id}/access/${type}/${principalId}`);
7114
+ }
7115
+ async authorizeLease(orgId, input) {
7116
+ if (input.authorization) return input;
7117
+ const prep = await this.request("GET", `/v1/orgs/${orgId}/leases/targets/${input.targetId}/authorization`);
7118
+ if (!prep.required) return input;
7119
+ const token = this.auth.type === "bearer" ? this.auth.token : this.auth.type === "dynamic" ? await this.auth.getToken() : null;
7120
+ const signingKey = token && isServiceToken(token) ? await importPolicySigningKey(JSON.stringify(await crypto.subtle.exportKey("jwk", (await parseServiceToken(token)).privateKey))) : await this.getLeaseSigner?.();
7121
+ if (!signingKey) throw new Error("Unlock your Seekrit key to request a customer-provisioned lease.");
7122
+ const access = await verifyProvisionerChain(prep.chain, orgId, prep.provisionerId);
7123
+ if (!access.targets.includes("*") && !access.targets.includes(input.targetId)) throw new Error("Target is outside your provisioner access grant.");
7124
+ const leaseId = randomId("lea");
7125
+ const issuedAt = Date.now();
7126
+ const rendered = leaseProviderFor(input.provider).render(prep.config, input, {
7127
+ leaseId,
7128
+ nowMs: issuedAt
7129
+ });
7130
+ const expiresAt = Date.parse(rendered.expiresAt);
7131
+ if (expiresAt > access.expiresAt) throw new Error("Your access grant expires before this lease. Use a shorter duration or ask an administrator to renew it.");
7132
+ const proof = {
7133
+ version: 1,
7134
+ orgId,
7135
+ provisionerId: prep.provisionerId,
7136
+ targetId: input.targetId,
7137
+ provider: input.provider,
7138
+ leaseId,
7139
+ instanceId: prep.instanceId,
7140
+ nonce: crypto.randomUUID(),
7141
+ targetDigest: prep.targetDigest,
7142
+ action: provisionerTransportAction(rendered.provision),
7143
+ ..."recipientPublicKey" in input ? { recipientPublicKey: input.recipientPublicKey } : {},
7144
+ issuedAt,
7145
+ expiresAt
7146
+ };
7147
+ return {
7148
+ ...input,
7149
+ authorization: {
7150
+ ...prep.chain,
7151
+ proof: await signProvisionerEnvelope("pr1", signingKey, proof)
7152
+ }
7153
+ };
7154
+ }
6175
7155
  async request(method, path, body, extraHeaders) {
6176
7156
  const headers = {
6177
7157
  accept: "application/json",
@@ -6844,6 +7824,15 @@ var SeekritClient = class {
6844
7824
  getLeaseBrokerKey(orgId) {
6845
7825
  return this.request("GET", `/v1/orgs/${orgId}/leases/broker-key`);
6846
7826
  }
7827
+ listProvisioners(orgId) {
7828
+ return this.request("GET", `/v1/orgs/${orgId}/leases/provisioners`);
7829
+ }
7830
+ registerProvisioner(orgId, input) {
7831
+ return this.request("POST", `/v1/orgs/${orgId}/leases/provisioners`, input);
7832
+ }
7833
+ deleteProvisioner(orgId, id) {
7834
+ return this.request("DELETE", `/v1/orgs/${orgId}/leases/provisioners/${id}`);
7835
+ }
6847
7836
  listLeaseTargets(orgId) {
6848
7837
  return this.request("GET", `/v1/orgs/${orgId}/leases/targets`);
6849
7838
  }
@@ -6905,7 +7894,7 @@ var SeekritClient = class {
6905
7894
  return this.request("GET", `/v1/orgs/${orgId}/leases`);
6906
7895
  }
6907
7896
  mintLease(orgId, input) {
6908
- return this.request("POST", `/v1/orgs/${orgId}/leases`, input);
7897
+ return this.authorizeLease(orgId, input).then((body) => this.request("POST", `/v1/orgs/${orgId}/leases`, body));
6909
7898
  }
6910
7899
  revokeLease(orgId, leaseId) {
6911
7900
  return this.request("DELETE", `/v1/orgs/${orgId}/leases/${leaseId}`);
@@ -7325,15 +8314,17 @@ function tryBuildContext(dotenvVars = {}) {
7325
8314
  email: devUser
7326
8315
  };
7327
8316
  else return null;
7328
- return {
8317
+ const ctx = {
7329
8318
  client: new SeekritClient({
7330
8319
  baseUrl: apiUrl,
7331
8320
  auth,
7332
- client: CLI_CLIENT
8321
+ client: CLI_CLIENT,
8322
+ getLeaseSigner: async () => importPolicySigningKey(JSON.stringify(await crypto.subtle.exportKey("jwk", await getPrivateKey(ctx))))
7333
8323
  }),
7334
8324
  auth,
7335
8325
  apiUrl
7336
8326
  };
8327
+ return ctx;
7337
8328
  }
7338
8329
  function buildContext() {
7339
8330
  const ctx = tryBuildContext();
@@ -12881,21 +13872,56 @@ function dynamicTtl(value) {
12881
13872
  function registerDynamicCommands(program) {
12882
13873
  const dynamic = program.command("dynamic").description("manage provider-issued temporary credentials");
12883
13874
  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) => {
13875
+ 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 (or legacy provisioner signing key) from stdin").action(async (options) => {
12885
13876
  const config = managedTargetConfigSchema.parse(JSON.parse(readFileSync(options.config, "utf8")));
12886
- if (process.stdin.isTTY) fail("pipe the admin credential on stdin");
13877
+ const provisionerId = provisionerIdFor(config);
13878
+ const credentialLabel = config.executor === "remote" && !provisionerId ? "provisioner signing key" : "admin credential";
13879
+ if (process.stdin.isTTY) fail(`pipe the ${credentialLabel} on stdin`);
12887
13880
  const secret = (await readStdin()).trim();
12888
- if (!secret) fail("admin credential is empty");
13881
+ if (!secret) fail(`${credentialLabel} is empty`);
13882
+ if (config.executor === "remote" && !provisionerId) {
13883
+ let length = 0;
13884
+ try {
13885
+ length = atob(secret).length;
13886
+ } catch {}
13887
+ if (length < 32) fail("use a base64 signing key containing at least 32 random bytes (seekrit provisioner keygen)");
13888
+ }
12889
13889
  const ctx = buildContext();
12890
13890
  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);
13891
+ const id = randomId("lt");
13892
+ let wrappedAdminSecret;
13893
+ if (provisionerId) {
13894
+ const { provisioners } = await ctx.client.listProvisioners(org.id);
13895
+ const provisioner = provisioners.find((p) => p.id === provisionerId);
13896
+ if (!provisioner) fail("provisioner not found");
13897
+ wrappedAdminSecret = await sealProvisionerTarget(provisioner.publicKeyJwk, {
13898
+ orgId: org.id,
13899
+ provisionerId,
13900
+ targetId: id,
13901
+ config,
13902
+ adminCredential: secret
13903
+ });
13904
+ } else {
13905
+ const { publicKeyJwk } = await ctx.client.getLeaseBrokerKey(org.id);
13906
+ wrappedAdminSecret = await wrapDek(new TextEncoder().encode(secret), publicKeyJwk);
13907
+ }
12893
13908
  const { target } = await ctx.client.registerLeaseTarget(org.id, {
13909
+ id,
12894
13910
  name: options.name,
12895
13911
  config,
12896
13912
  wrappedAdminSecret
12897
13913
  });
12898
- console.log(JSON.stringify(target));
13914
+ console.log(JSON.stringify(config.executor === "remote" && !provisionerId ? {
13915
+ ...target,
13916
+ connectionToken: createProvisionerConnection({
13917
+ orgId: org.id,
13918
+ targetId: target.id,
13919
+ provider: config.provider,
13920
+ apiUrl: ctx.apiUrl,
13921
+ signingKey: secret,
13922
+ mode: config.transport
13923
+ })
13924
+ } : target));
12899
13925
  });
12900
13926
  targets.command("list").option("--org <slug>", "organization").action(async (options) => {
12901
13927
  const ctx = buildContext();
@@ -14744,7 +15770,7 @@ function collect$5(value, acc) {
14744
15770
  * release-please-config.json), so the pin follows the crate without anyone
14745
15771
  * remembering to move it.
14746
15772
  */
14747
- const PROXY_VERSION = "0.16.0";
15773
+ const PROXY_VERSION = "0.16.1";
14748
15774
  const BIN = "seekrit-proxy";
14749
15775
  /**
14750
15776
  * Host → Rust target triple.
@@ -14780,7 +15806,7 @@ function versionPrefix(version) {
14780
15806
  return version.startsWith("v") ? version : `v${version}`;
14781
15807
  }
14782
15808
  function resolveVersion(explicit) {
14783
- return explicit ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.16.0";
15809
+ return explicit ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.16.1";
14784
15810
  }
14785
15811
  function resolveBaseUrl(explicit) {
14786
15812
  return (explicit ?? process.env.SEEKRIT_PROXY_BASE_URL ?? "https://proxy.seekrit.dev").replace(/\/+$/, "");
@@ -16763,10 +17789,10 @@ Next:
16763
17789
  });
16764
17790
  console.log(path);
16765
17791
  const { target } = detectTarget();
16766
- process.stderr.write(`seekrit-proxy ${versionPrefix(options.proxyVersion ?? "0.16.0")} (${target})\n`);
17792
+ process.stderr.write(`seekrit-proxy ${versionPrefix(options.proxyVersion ?? "0.16.1")} (${target})\n`);
16767
17793
  });
16768
17794
  proxy.command("where").description("show which binary `seekrit proxy run` would use, without fetching it").option("--proxy-version <version>", `version to report (default: ${PROXY_VERSION})`).option("--json", "machine-readable output").action((options) => {
16769
- const version = options.proxyVersion ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.16.0";
17795
+ const version = options.proxyVersion ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.16.1";
16770
17796
  const override = process.env.SEEKRIT_PROXY_BIN;
16771
17797
  const { target, exe } = detectTarget();
16772
17798
  const path = override ?? proxyBinaryPath(version, target, exe);
@@ -16825,7 +17851,7 @@ Next:
16825
17851
  process.stdout.write(renderComposeSnippet(plan, {
16826
17852
  service: options.service,
16827
17853
  workload: options.workload,
16828
- image: options.image ?? `seekritdev/proxy:0.16.0`,
17854
+ image: options.image ?? `seekritdev/proxy:0.16.1`,
16829
17855
  publish: Boolean(options.publish)
16830
17856
  }));
16831
17857
  });