@seekrit/cli 0.15.0 → 0.16.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 +269 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13,7 +13,8 @@ z.enum([
13
13
  "ssh",
14
14
  "redis",
15
15
  "aws",
16
- "gcp"
16
+ "gcp",
17
+ "mongodb"
17
18
  ]);
18
19
  const executorModeSchema = z.enum(["in_do", "remote"]);
19
20
  /**
@@ -113,6 +114,22 @@ const p256PublicKeyJwkSchema = z.string().max(2048).refine((s) => {
113
114
  return false;
114
115
  }
115
116
  }, "must be a JWK-serialized P-256 public key");
117
+ z.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "must be 1–64 chars of [A-Za-z0-9_-]");
118
+ /**
119
+ * A MongoDB database name — the db a preset role is granted on, or the
120
+ * authentication database a leased user is created in. MongoDB forbids
121
+ * `/\. "$*<>:|?` and the empty string in db names; we keep to a safe subset.
122
+ */
123
+ const mongoDatabaseNameSchema = z.string().regex(/^[A-Za-z0-9_-]{1,63}$/, "must be 1–63 chars of [A-Za-z0-9_-]");
124
+ /**
125
+ * A single MongoDB role grant `{ role, db }` for a `custom` target — e.g.
126
+ * `{ role: "readWrite", db: "app" }` or a user-defined role. Admin-supplied
127
+ * trusted input, set once at registration; still bounded structurally.
128
+ */
129
+ const mongoRoleSchema = z.object({
130
+ role: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "must be a role name"),
131
+ db: mongoDatabaseNameSchema
132
+ });
116
133
  const postgresAccessLevelSchema = z.enum([
117
134
  "readonly",
118
135
  "readwrite",
@@ -133,6 +150,11 @@ const redisAccessLevelSchema = z.enum([
133
150
  "readwrite",
134
151
  "custom"
135
152
  ]);
153
+ const mongoAccessLevelSchema = z.enum([
154
+ "readonly",
155
+ "readwrite",
156
+ "custom"
157
+ ]);
136
158
  const connectionSchema = z.object({
137
159
  host: z.string().min(1),
138
160
  port: z.number().int().min(1).max(65535),
@@ -210,13 +232,24 @@ const gcpTargetConfigSchema = z.object({
210
232
  delegates: z.array(gcpServiceAccountEmailSchema).max(8).optional(),
211
233
  maxTtlSeconds: z.number().int().min(60).max(GCP_MAX_TTL_SECONDS).optional()
212
234
  });
235
+ const mongoTargetConfigSchema = z.object({
236
+ provider: z.literal("mongodb"),
237
+ executor: z.literal("in_do"),
238
+ accessLevel: mongoAccessLevelSchema.optional(),
239
+ connection: connectionSchema,
240
+ authSource: mongoDatabaseNameSchema.optional(),
241
+ roles: z.array(mongoRoleSchema).min(1).max(32).optional(),
242
+ tls: z.boolean().optional(),
243
+ maxTtlSeconds: z.number().int().min(60).max(3600 * 24 * 7).optional()
244
+ });
213
245
  const leaseTargetConfigSchema = z.discriminatedUnion("provider", [
214
246
  postgresTargetConfigSchema,
215
247
  mysqlTargetConfigSchema,
216
248
  redisTargetConfigSchema,
217
249
  sshTargetConfigSchema,
218
250
  awsTargetConfigSchema,
219
- gcpTargetConfigSchema
251
+ gcpTargetConfigSchema,
252
+ mongoTargetConfigSchema
220
253
  ]);
221
254
  z.object({
222
255
  name: z.string().trim().min(1).max(128),
@@ -309,13 +342,27 @@ const mintGcpLeaseSchema = z.object({
309
342
  recipientPublicKey: p256PublicKeyJwkSchema,
310
343
  ttlSeconds: z.number().int().min(60).max(GCP_MAX_TTL_SECONDS)
311
344
  });
345
+ /**
346
+ * Client → API: mint a MongoDB lease. Like AWS (tier 2), the client generates
347
+ * an ephemeral P-256 keypair locally and sends only the public key; the broker
348
+ * generates the password, runs `createUser`, and returns the credential wrapped
349
+ * to that key. The private key never leaves the requesting machine, so the
350
+ * plaintext credential is only decryptable there.
351
+ */
352
+ const mintMongoLeaseSchema = z.object({
353
+ provider: z.literal("mongodb"),
354
+ targetId: z.string().min(1),
355
+ recipientPublicKey: p256PublicKeyJwkSchema,
356
+ ttlSeconds: ttlSecondsSchema
357
+ });
312
358
  z.discriminatedUnion("provider", [
313
359
  mintPostgresLeaseSchema,
314
360
  mintMysqlLeaseSchema,
315
361
  mintRedisLeaseSchema,
316
362
  mintSshLeaseSchema,
317
363
  mintAwsLeaseSchema,
318
- mintGcpLeaseSchema
364
+ mintGcpLeaseSchema,
365
+ mintMongoLeaseSchema
319
366
  ]);
320
367
  //#endregion
321
368
  //#region ../../packages/core/src/providers/aws.ts
@@ -369,6 +416,32 @@ function gcpSetupInstructions(config) {
369
416
  ].join("\n");
370
417
  }
371
418
  //#endregion
419
+ //#region ../../packages/core/src/providers/mongodb.ts
420
+ /**
421
+ * The one-time setup an admin performs so seekrit can provision users: create a
422
+ * dedicated provisioning user with `userAdmin` on the target database (or
423
+ * `userAdminAnyDatabase` if leases span databases). Printed for the admin to run
424
+ * in `mongosh`, never executed by seekrit — the analogue of
425
+ * `awsTrustPolicyInstructions` / `sshHostSetupInstructions`.
426
+ */
427
+ function mongoAdminSetupInstructions(config) {
428
+ const authSource = config.authSource ?? "admin";
429
+ const db = config.connection.database;
430
+ return [
431
+ `// Run once in mongosh against the "${authSource}" database. Creates the`,
432
+ "// provisioning user whose connection string you register as the admin secret.",
433
+ `use ${authSource}`,
434
+ "db.createUser({",
435
+ " user: \"seekrit_provisioner\",",
436
+ " pwd: passwordPrompt(),",
437
+ ` roles: [{ role: "userAdmin", db: "${db}" }],`,
438
+ "})",
439
+ "",
440
+ `// Register it: seekrit mongodb target add --name ${db} \\`,
441
+ `// --uri "mongodb://seekrit_provisioner:<pwd>@${config.connection.host}:${config.connection.port}/?authSource=${authSource}"`
442
+ ].join("\n");
443
+ }
444
+ //#endregion
372
445
  //#region ../../packages/core/src/providers/postgres.ts
373
446
  /**
374
447
  * The one-time setup SQL an admin runs to create the shared group role that a
@@ -445,6 +518,8 @@ const NOTIFICATION_TYPES = [
445
518
  /** URL-safe identifier segment: `my-app`, `production`, … */
446
519
  const slugSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "must be lowercase alphanumeric with hyphens");
447
520
  const nameSchema = z.string().trim().min(1).max(128);
521
+ /** An email address, normalized to trimmed lowercase before validation. */
522
+ const emailSchema = z.string().trim().toLowerCase().pipe(z.email().max(320));
448
523
  /** Env-var style secret name: FOO, DATABASE_URL, apiKey2 … */
449
524
  const secretNameSchema = z.string().min(1).max(256).regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "must be a valid environment variable name");
450
525
  z.enum([
@@ -452,6 +527,8 @@ z.enum([
452
527
  "admin",
453
528
  "member"
454
529
  ]);
530
+ /** Role a person may be invited at — never `owner` (ownership isn't invitable). */
531
+ const inviteRoleSchema = z.enum(["admin", "member"]);
455
532
  const principalTypeSchema = z.enum(["user", "service_token"]);
456
533
  /** Org-level capability a service token can hold (never `owner`). */
457
534
  const serviceTokenRoleSchema = z.enum(["admin", "member"]);
@@ -463,6 +540,10 @@ z.object({
463
540
  name: nameSchema,
464
541
  slug: slugSchema
465
542
  });
543
+ z.object({
544
+ email: emailSchema,
545
+ role: inviteRoleSchema.default("member")
546
+ });
466
547
  z.object({
467
548
  name: nameSchema,
468
549
  slug: slugSchema
@@ -784,6 +865,41 @@ async function unwrapGcpCredential(wrapped, privateKeyJwk) {
784
865
  };
785
866
  }
786
867
  //#endregion
868
+ //#region ../../packages/crypto/src/mongodb.ts
869
+ /** Generate the ephemeral P-256 keypair a client uses to receive one MongoDB lease. */
870
+ async function generateMongoRecipientKeyPair() {
871
+ return generateKeyPair();
872
+ }
873
+ /**
874
+ * Unwrap the `wd1.` blob returned by a MongoDB lease mint using the ephemeral
875
+ * private key generated for that lease, yielding the plaintext credential.
876
+ */
877
+ async function unwrapMongoCredential(wrapped, privateKeyJwk) {
878
+ const bytes = await unwrapDek(wrapped, await importPrivateKey(privateKeyJwk));
879
+ let parsed;
880
+ try {
881
+ parsed = JSON.parse(utf8Decode(bytes));
882
+ } catch {
883
+ throw new SeekritCryptoError("DECRYPT_FAILED", "unwrapped MongoDB credential was not valid JSON");
884
+ }
885
+ const c = parsed;
886
+ if (typeof c.username !== "string" || typeof c.password !== "string" || typeof c.database !== "string" || typeof c.authSource !== "string" || typeof c.host !== "string" || typeof c.port !== "number" || typeof c.expiration !== "string") throw new SeekritCryptoError("DECRYPT_FAILED", "unwrapped MongoDB credential is missing fields");
887
+ return {
888
+ username: c.username,
889
+ password: c.password,
890
+ database: c.database,
891
+ authSource: c.authSource,
892
+ host: c.host,
893
+ port: c.port,
894
+ expiration: c.expiration,
895
+ uri: mongoConnectionUri(c)
896
+ };
897
+ }
898
+ /** Build a `mongodb://user:pass@host:port/db?authSource=…` URI from a credential. */
899
+ function mongoConnectionUri(cred) {
900
+ return `mongodb://${encodeURIComponent(cred.username)}:${encodeURIComponent(cred.password)}@${cred.host}:${cred.port}/${cred.database}?authSource=${cred.authSource}`;
901
+ }
902
+ //#endregion
787
903
  //#region ../../packages/crypto/src/mysql.ts
788
904
  /**
789
905
  * Client-side construction of a MySQL/MariaDB `mysql_native_password`
@@ -1228,7 +1344,7 @@ function isServiceToken(value) {
1228
1344
  }
1229
1345
  //#endregion
1230
1346
  //#region package.json
1231
- var version = "0.15.0";
1347
+ var version = "0.16.0";
1232
1348
  //#endregion
1233
1349
  //#region ../../packages/api-client/src/index.ts
1234
1350
  var SeekritApiError = class extends Error {
@@ -1301,6 +1417,15 @@ var SeekritClient = class {
1301
1417
  listMembers(orgId) {
1302
1418
  return this.request("GET", `/v1/orgs/${orgId}/members`);
1303
1419
  }
1420
+ listInvites(orgId) {
1421
+ return this.request("GET", `/v1/orgs/${orgId}/invites`);
1422
+ }
1423
+ createInvite(orgId, input) {
1424
+ return this.request("POST", `/v1/orgs/${orgId}/invites`, input);
1425
+ }
1426
+ revokeInvite(orgId, inviteId) {
1427
+ return this.request("DELETE", `/v1/orgs/${orgId}/invites/${inviteId}`);
1428
+ }
1304
1429
  listApps(orgId) {
1305
1430
  return this.request("GET", `/v1/orgs/${orgId}/apps`);
1306
1431
  }
@@ -1677,7 +1802,7 @@ async function resolveGroup(ctx, opts) {
1677
1802
  * `sts:AssumeRole` on the target role.
1678
1803
  */
1679
1804
  /** Parse a duration like `30m`, `1h`, or a bare seconds count. */
1680
- function parseTtlSeconds$5(input) {
1805
+ function parseTtlSeconds$6(input) {
1681
1806
  const m = /^(\d+)\s*([smhd]?)$/.exec(input.trim());
1682
1807
  if (!m) fail(`invalid --ttl "${input}" (try 30m, 1h, 12h)`);
1683
1808
  return Number(m[1]) * ({
@@ -1717,7 +1842,7 @@ function registerAwsCommands(program) {
1717
1842
  region: options.region,
1718
1843
  ...options.externalId ? { externalId: options.externalId } : {},
1719
1844
  ...sessionPolicy ? { sessionPolicy } : {},
1720
- ...options.maxTtl ? { maxTtlSeconds: parseTtlSeconds$5(options.maxTtl) } : {}
1845
+ ...options.maxTtl ? { maxTtlSeconds: parseTtlSeconds$6(options.maxTtl) } : {}
1721
1846
  };
1722
1847
  const baseCredential = resolveBaseCredential(options);
1723
1848
  const { publicKeyJwk } = await ctx.client.getLeaseBrokerKey(org.id);
@@ -1765,7 +1890,7 @@ function registerAwsCommands(program) {
1765
1890
  if (!t) fail(`no target "${targetRef}" in ${org.slug}`);
1766
1891
  const cfg = t.config;
1767
1892
  if (cfg.provider !== "aws") fail(`"${t.name}" is not an aws target (see \`seekrit pg\`)`);
1768
- const ttlSeconds = parseTtlSeconds$5(options.ttl);
1893
+ const ttlSeconds = parseTtlSeconds$6(options.ttl);
1769
1894
  if (ttlSeconds < 900) fail(`--ttl must be at least ${900 / 60}m (STS minimum)`);
1770
1895
  const recipient = await generateAwsRecipientKeyPair();
1771
1896
  const { aws: leased } = await ctx.client.mintLease(org.id, {
@@ -1870,7 +1995,7 @@ function formatSecrets(values, format) {
1870
1995
  * `roles/iam.serviceAccountTokenCreator` on the target.
1871
1996
  */
1872
1997
  /** Parse a duration like `30m`, `1h`, or a bare seconds count. */
1873
- function parseTtlSeconds$4(input) {
1998
+ function parseTtlSeconds$5(input) {
1874
1999
  const m = /^(\d+)\s*([smhd]?)$/.exec(input.trim());
1875
2000
  if (!m) fail(`invalid --ttl "${input}" (try 30m, 1h, 12h)`);
1876
2001
  return Number(m[1]) * ({
@@ -1914,7 +2039,7 @@ function registerGcpCommands(program) {
1914
2039
  serviceAccount: options.serviceAccount,
1915
2040
  ...options.scope?.length ? { scopes: options.scope } : {},
1916
2041
  ...options.delegate?.length ? { delegates: options.delegate } : {},
1917
- ...options.maxTtl ? { maxTtlSeconds: parseTtlSeconds$4(options.maxTtl) } : {}
2042
+ ...options.maxTtl ? { maxTtlSeconds: parseTtlSeconds$5(options.maxTtl) } : {}
1918
2043
  };
1919
2044
  const keyJson = resolveServiceAccountKey(options);
1920
2045
  const { publicKeyJwk } = await ctx.client.getLeaseBrokerKey(org.id);
@@ -1961,7 +2086,7 @@ function registerGcpCommands(program) {
1961
2086
  const t = targets.find((x) => x.id === targetRef || x.name === targetRef);
1962
2087
  if (!t) fail(`no target "${targetRef}" in ${org.slug}`);
1963
2088
  if (t.config.provider !== "gcp") fail(`"${t.name}" is not a gcp target (see \`seekrit aws\`)`);
1964
- const ttlSeconds = parseTtlSeconds$4(options.ttl);
2089
+ const ttlSeconds = parseTtlSeconds$5(options.ttl);
1965
2090
  if (ttlSeconds < 60) fail(`--ttl must be at least 60s`);
1966
2091
  if (ttlSeconds > 43200) fail(`--ttl must be at most ${GCP_MAX_TTL_SECONDS / 3600}h`);
1967
2092
  const recipient = await generateGcpRecipientKeyPair();
@@ -1996,6 +2121,139 @@ function registerGcpCommands(program) {
1996
2121
  });
1997
2122
  }
1998
2123
  //#endregion
2124
+ //#region src/mongodb.ts
2125
+ /**
2126
+ * `seekrit mongodb` — temporary MongoDB credentials (Vault-style dynamic
2127
+ * secrets), the tier-2 sibling of `seekrit aws`.
2128
+ *
2129
+ * MongoDB hashes the password server-side (no verifier injection), so minting
2130
+ * generates an ephemeral P-256 keypair on THIS machine and sends only the public
2131
+ * key; the broker creates the user and returns the credential wrapped to that
2132
+ * key, so the control plane only ever relays ciphertext and only this machine
2133
+ * can unwrap it. Registering a target wraps the admin connection string to the
2134
+ * broker's public key locally, so the control plane never sees it either.
2135
+ */
2136
+ /** Parse a duration like `30m`, `1h`, or a bare seconds count. */
2137
+ function parseTtlSeconds$4(input) {
2138
+ const m = /^(\d+)\s*([smhd]?)$/.exec(input.trim());
2139
+ if (!m) fail(`invalid --ttl "${input}" (try 30m, 1h, 8h)`);
2140
+ return Number(m[1]) * ({
2141
+ s: 1,
2142
+ m: 60,
2143
+ h: 3600,
2144
+ d: 86400
2145
+ }[m[2] || "s"] ?? 1);
2146
+ }
2147
+ /** Collect a repeatable option into an array. */
2148
+ function collect$4(value, previous) {
2149
+ return [...previous, value];
2150
+ }
2151
+ /** Parse `readWrite@app` → { role, db } for a custom target. */
2152
+ function parseRole(spec) {
2153
+ const [role, db] = spec.split("@");
2154
+ if (!role || !db) fail(`invalid --role "${spec}" (use role@database, e.g. readWrite@app)`);
2155
+ return {
2156
+ role,
2157
+ db
2158
+ };
2159
+ }
2160
+ /**
2161
+ * The admin connection string the broker provisions with. From `--uri` or
2162
+ * SEEKRIT_MONGODB_ADMIN_URL; never leaves this machine unwrapped — it is wrapped
2163
+ * to the broker key. Needs `userAdmin` on the target database.
2164
+ */
2165
+ function resolveAdminUri(uri) {
2166
+ const value = uri ?? process.env.SEEKRIT_MONGODB_ADMIN_URL;
2167
+ if (!value) fail("provide the admin connection string via --uri or SEEKRIT_MONGODB_ADMIN_URL (mongodb://user:pass@host:port/?authSource=admin; needs userAdmin on the database)");
2168
+ return value;
2169
+ }
2170
+ function registerMongoCommands(program) {
2171
+ const mongo = program.command("mongodb").description("temporary MongoDB credentials (createUser, zero-knowledge delivery)");
2172
+ const target = mongo.command("target").description("manage MongoDB targets");
2173
+ target.command("add").description("register a MongoDB cluster to issue temporary credentials from").requiredOption("--name <name>", "display name, e.g. prod-app").requiredOption("--database <db>", "the database leased users get access to, e.g. app").option("--uri <uri>", "admin connection string (else SEEKRIT_MONGODB_ADMIN_URL)").option("--org <slug>").option("--access <level>", "readonly | readwrite | custom", "readonly").option("--role <role@db>", "grant for a custom target (repeatable)", collect$4, []).option("--auth-source <db>", "authentication database (default admin)").option("--max-ttl <duration>", "clamp requested credential lifetime, e.g. 8h").option("--no-tls", "disable TLS to the cluster (TLS is on by default)").action(async (options) => {
2174
+ const ctx = buildContext();
2175
+ const org = await resolveOrg(ctx, options.org);
2176
+ const adminUri = resolveAdminUri(options.uri);
2177
+ const url = new URL(adminUri);
2178
+ const accessLevel = options.access;
2179
+ if (accessLevel === "custom" && options.role.length === 0) fail("--access custom requires at least one --role role@db");
2180
+ const config = {
2181
+ provider: "mongodb",
2182
+ executor: "in_do",
2183
+ accessLevel,
2184
+ connection: {
2185
+ host: url.hostname,
2186
+ port: url.port ? Number(url.port) : 27017,
2187
+ database: options.database
2188
+ },
2189
+ ...options.authSource ? { authSource: options.authSource } : {},
2190
+ ...accessLevel === "custom" ? { roles: options.role.map(parseRole) } : {},
2191
+ ...options.tls ? {} : { tls: false }
2192
+ };
2193
+ const { publicKeyJwk } = await ctx.client.getLeaseBrokerKey(org.id);
2194
+ const wrappedAdminSecret = await wrapDek(new TextEncoder().encode(adminUri), publicKeyJwk);
2195
+ const { target: created } = await ctx.client.registerLeaseTarget(org.id, {
2196
+ name: options.name,
2197
+ config,
2198
+ wrappedAdminSecret
2199
+ });
2200
+ console.error(`registered MongoDB target ${created.name} (${created.id})`);
2201
+ console.error("\nEnsure a provisioning user exists, then `seekrit mongodb lease`:\n");
2202
+ console.log(mongoAdminSetupInstructions(config));
2203
+ });
2204
+ target.command("list").description("list MongoDB targets").option("--org <slug>").action(async (options) => {
2205
+ const ctx = buildContext();
2206
+ const org = await resolveOrg(ctx, options.org);
2207
+ const { targets } = await ctx.client.listLeaseTargets(org.id);
2208
+ for (const t of targets) {
2209
+ const cfg = t.config;
2210
+ if (cfg.provider !== "mongodb") continue;
2211
+ const host = `${cfg.connection.host}:${cfg.connection.port}`;
2212
+ console.log(`${t.id}\t${t.name}\t${host}\t${cfg.connection.database}\t${cfg.accessLevel ?? "readonly"}`);
2213
+ }
2214
+ });
2215
+ target.command("rm <targetId>").description("delete a MongoDB target").option("--org <slug>").action(async (targetId, options) => {
2216
+ const ctx = buildContext();
2217
+ const org = await resolveOrg(ctx, options.org);
2218
+ await ctx.client.deleteLeaseTarget(org.id, targetId);
2219
+ console.error(`deleted ${targetId}`);
2220
+ });
2221
+ mongo.command("lease <target>").description("mint short-lived MongoDB credentials; prints a ready-to-use connection URI").option("--org <slug>").option("--ttl <duration>", "credential lifetime, e.g. 30m, 1h, 8h", "1h").option("--json", "print the full credential as JSON").action(async (targetRef, options) => {
2222
+ const ctx = buildContext();
2223
+ const org = await resolveOrg(ctx, options.org);
2224
+ const { targets } = await ctx.client.listLeaseTargets(org.id);
2225
+ const t = targets.find((x) => x.id === targetRef || x.name === targetRef);
2226
+ if (!t) fail(`no target "${targetRef}" in ${org.slug}`);
2227
+ if (t.config.provider !== "mongodb") fail(`"${t?.name}" is not a mongodb target (see \`seekrit aws\`)`);
2228
+ const recipient = await generateMongoRecipientKeyPair();
2229
+ const { mongodb: leased } = await ctx.client.mintLease(org.id, {
2230
+ provider: "mongodb",
2231
+ targetId: t.id,
2232
+ recipientPublicKey: recipient.publicKeyJwk,
2233
+ ttlSeconds: parseTtlSeconds$4(options.ttl)
2234
+ });
2235
+ const cred = await unwrapMongoCredential(leased.wrappedCredential, recipient.privateKeyJwk);
2236
+ console.error(`leased ${cred.username} on ${cred.host}:${cred.port}/${cred.database} — expires ${cred.expiration}`);
2237
+ if (options.json) console.log(JSON.stringify(cred, null, 2));
2238
+ else console.log(`export MONGODB_URI='${cred.uri}'`);
2239
+ });
2240
+ mongo.command("leases").description("list MongoDB leases (the ledger — never secret material)").option("--org <slug>").action(async (options) => {
2241
+ const ctx = buildContext();
2242
+ const org = await resolveOrg(ctx, options.org);
2243
+ const { leases } = await ctx.client.listLeases(org.id);
2244
+ for (const l of leases) {
2245
+ if (l.provider !== "mongodb") continue;
2246
+ console.log(`${l.id}\t${l.status}\texpires ${l.expiresAt}`);
2247
+ }
2248
+ });
2249
+ mongo.command("revoke <leaseId>").description("revoke a lease now (drops the MongoDB user immediately)").option("--org <slug>").action(async (leaseId, options) => {
2250
+ const ctx = buildContext();
2251
+ const org = await resolveOrg(ctx, options.org);
2252
+ await ctx.client.revokeLease(org.id, leaseId);
2253
+ console.error(`revoked ${leaseId} (the MongoDB user has been dropped)`);
2254
+ });
2255
+ }
2256
+ //#endregion
1999
2257
  //#region src/provisioner.ts
2000
2258
  /**
2001
2259
  * `seekrit provisioner` — helpers for the self-hosted **remote executor**
@@ -3087,6 +3345,7 @@ registerProvisionerCommands(program);
3087
3345
  registerSshCommands(program);
3088
3346
  registerAwsCommands(program);
3089
3347
  registerGcpCommands(program);
3348
+ registerMongoCommands(program);
3090
3349
  program.command("mcp").description("run an MCP server over stdio so AI agents can drive seekrit").action(async () => {
3091
3350
  const { runMcpServer } = await import("./mcp-ARBuneH3.js");
3092
3351
  await runMcpServer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seekrit/cli",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
5
5
  "type": "module",
6
6
  "publishConfig": {