@seekrit/cli 0.31.0 → 0.33.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 +92 -4
- package/dist/{mcp-DLplPOvz.js → mcp-DR-zla_u.js} +69 -49
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -16,6 +16,12 @@ const ENTITLEMENT_KEYS = Object.keys({
|
|
|
16
16
|
description: "Client-side managed keys for application-layer encryption and signing.",
|
|
17
17
|
default: true
|
|
18
18
|
},
|
|
19
|
+
"feature.honey_tokens": {
|
|
20
|
+
kind: "feature",
|
|
21
|
+
label: "Honey tokens",
|
|
22
|
+
description: "Decoy credentials that alert the moment anyone tries to use them.",
|
|
23
|
+
default: true
|
|
24
|
+
},
|
|
19
25
|
"feature.leases": {
|
|
20
26
|
kind: "feature",
|
|
21
27
|
label: "Temporary access",
|
|
@@ -122,7 +128,7 @@ const PLAN_FAMILIES = {
|
|
|
122
128
|
id: "free",
|
|
123
129
|
name: "Free",
|
|
124
130
|
description: "Get started with the essentials.",
|
|
125
|
-
current:
|
|
131
|
+
current: 3,
|
|
126
132
|
hidden: false
|
|
127
133
|
},
|
|
128
134
|
team: {
|
|
@@ -1119,6 +1125,9 @@ const AUDIT_ACTIONS = [
|
|
|
1119
1125
|
"token.created",
|
|
1120
1126
|
"token.revoked",
|
|
1121
1127
|
"token.deleted",
|
|
1128
|
+
"honey_token.created",
|
|
1129
|
+
"honey_token.deleted",
|
|
1130
|
+
"honey_token.tripped",
|
|
1122
1131
|
"cli_session.approved",
|
|
1123
1132
|
"cli_session.denied",
|
|
1124
1133
|
"cli_session.revoked",
|
|
@@ -1177,7 +1186,8 @@ const NOTIFICATION_TYPES = [
|
|
|
1177
1186
|
"org_welcome",
|
|
1178
1187
|
"token_expiring",
|
|
1179
1188
|
"lease_expired",
|
|
1180
|
-
"sync_failed"
|
|
1189
|
+
"sync_failed",
|
|
1190
|
+
"honey_token_tripped"
|
|
1181
1191
|
];
|
|
1182
1192
|
/** UI-facing copy + default for each notification type. */
|
|
1183
1193
|
const NOTIFICATION_TYPE_META = {
|
|
@@ -1225,6 +1235,11 @@ const NOTIFICATION_TYPE_META = {
|
|
|
1225
1235
|
label: "Third-party sync failed",
|
|
1226
1236
|
description: "A sync to an external destination failed repeatedly and stopped retrying. The destination is now holding stale values.",
|
|
1227
1237
|
defaultEnabled: true
|
|
1238
|
+
},
|
|
1239
|
+
honey_token_tripped: {
|
|
1240
|
+
label: "Honey token tripped",
|
|
1241
|
+
description: "Someone tried to use one of your organization's decoy credentials (throttled per token). Nothing legitimate holds one, so every trip is worth reading.",
|
|
1242
|
+
defaultEnabled: true
|
|
1228
1243
|
}
|
|
1229
1244
|
};
|
|
1230
1245
|
//#endregion
|
|
@@ -1332,6 +1347,14 @@ z.object({
|
|
|
1332
1347
|
environmentId: z.string().min(1).nullish(),
|
|
1333
1348
|
expiresAt: z.iso.datetime().nullish()
|
|
1334
1349
|
});
|
|
1350
|
+
z.object({
|
|
1351
|
+
name: nameSchema,
|
|
1352
|
+
tokenId: z.string().regex(/^skt_[0-9A-Za-z]+$/),
|
|
1353
|
+
/** SHA-256 hash (base64url) of the full token string. */
|
|
1354
|
+
tokenHash: z.string().min(1),
|
|
1355
|
+
/** Where the decoy was planted, as a reminder for whoever reads the alert. */
|
|
1356
|
+
placement: z.string().max(200).nullish()
|
|
1357
|
+
});
|
|
1335
1358
|
const kmsKeyPurposeSchema = z.enum(["encrypt", "sign"]);
|
|
1336
1359
|
const kmsKeySpecSchema = z.enum(["aes-256-gcm", "ecdsa-p256"]);
|
|
1337
1360
|
/** A wrapped key grant supplied by the client (server never sees plaintext material). */
|
|
@@ -2684,6 +2707,14 @@ async function createServiceToken() {
|
|
|
2684
2707
|
publicKeyJwk
|
|
2685
2708
|
};
|
|
2686
2709
|
}
|
|
2710
|
+
async function createHoneyToken() {
|
|
2711
|
+
const { token, tokenId, tokenHash } = await createServiceToken();
|
|
2712
|
+
return {
|
|
2713
|
+
token,
|
|
2714
|
+
tokenId,
|
|
2715
|
+
tokenHash
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2687
2718
|
async function parseServiceToken(token) {
|
|
2688
2719
|
const match = /^(skt_[0-9A-Za-z]+)_([A-Za-z0-9_-]+)$/.exec(token);
|
|
2689
2720
|
if (!match) throw new SeekritCryptoError("MALFORMED_TOKEN", "not a valid seekrit service token");
|
|
@@ -2720,7 +2751,7 @@ function isCliSessionToken(value) {
|
|
|
2720
2751
|
}
|
|
2721
2752
|
//#endregion
|
|
2722
2753
|
//#region package.json
|
|
2723
|
-
var version = "0.
|
|
2754
|
+
var version = "0.33.0";
|
|
2724
2755
|
//#endregion
|
|
2725
2756
|
//#region ../../packages/api-client/src/index.ts
|
|
2726
2757
|
var SeekritApiError = class extends Error {
|
|
@@ -3032,6 +3063,16 @@ var SeekritClient = class {
|
|
|
3032
3063
|
deleteToken(orgId, tokenId) {
|
|
3033
3064
|
return this.request("DELETE", `/v1/orgs/${orgId}/tokens/${tokenId}/permanent`);
|
|
3034
3065
|
}
|
|
3066
|
+
listHoneyTokens(orgId) {
|
|
3067
|
+
return this.request("GET", `/v1/orgs/${orgId}/honey-tokens`);
|
|
3068
|
+
}
|
|
3069
|
+
createHoneyToken(orgId, input) {
|
|
3070
|
+
return this.request("POST", `/v1/orgs/${orgId}/honey-tokens`, input);
|
|
3071
|
+
}
|
|
3072
|
+
/** Delete a decoy outright — there is no access to revoke first. */
|
|
3073
|
+
deleteHoneyToken(orgId, honeyTokenId) {
|
|
3074
|
+
return this.request("DELETE", `/v1/orgs/${orgId}/honey-tokens/${honeyTokenId}`);
|
|
3075
|
+
}
|
|
3035
3076
|
/** Keys the caller can see: all org keys for admins, granted keys otherwise. */
|
|
3036
3077
|
listKmsKeys(orgId) {
|
|
3037
3078
|
return this.request("GET", `/v1/orgs/${orgId}/kms/keys`);
|
|
@@ -5233,6 +5274,52 @@ function registerGroupCommands(program) {
|
|
|
5233
5274
|
});
|
|
5234
5275
|
}
|
|
5235
5276
|
//#endregion
|
|
5277
|
+
//#region src/honey.ts
|
|
5278
|
+
/**
|
|
5279
|
+
* Honey tokens — decoy credentials that unlock nothing and alert when used.
|
|
5280
|
+
*
|
|
5281
|
+
* Minted client-side like real tokens (the API only ever sees a hash), printed
|
|
5282
|
+
* once, and then planted wherever a thief would rummage. The CLI is the natural
|
|
5283
|
+
* home for this: `seekrit honey-token create` piped straight into the file,
|
|
5284
|
+
* commit, config, or CI variable you want watched.
|
|
5285
|
+
*/
|
|
5286
|
+
function registerHoneyTokenCommands(program) {
|
|
5287
|
+
const honey = program.command("honey-token").description("plant decoy credentials that alert when anyone tries to use them");
|
|
5288
|
+
honey.command("create").description("mint a decoy credential; prints it once").requiredOption("--name <name>", "display name, e.g. legacy-ci-bait").option("--org <slug>").option("--placement <note>", "where you're planting it (echoed in the alert email)").action(async (options) => {
|
|
5289
|
+
const ctx = buildContext();
|
|
5290
|
+
const orgRef = await resolveOrg(ctx, options.org);
|
|
5291
|
+
const created = await createHoneyToken();
|
|
5292
|
+
await ctx.client.createHoneyToken(orgRef.id, {
|
|
5293
|
+
name: options.name,
|
|
5294
|
+
tokenId: created.tokenId,
|
|
5295
|
+
tokenHash: created.tokenHash,
|
|
5296
|
+
placement: options.placement ?? null
|
|
5297
|
+
});
|
|
5298
|
+
console.error("decoy created — save it now, it is not stored. Plant it somewhere a thief would look, NOT anywhere your own tooling reads: a deploy script that tries it by mistake trips the alarm just as loudly. It grants nothing.");
|
|
5299
|
+
console.log(created.token);
|
|
5300
|
+
});
|
|
5301
|
+
honey.command("list").alias("ls").description("list decoy credentials and whether any have been tripped").option("--org <slug>").option("--json", "print the raw API response").action(async (options) => {
|
|
5302
|
+
const ctx = buildContext();
|
|
5303
|
+
const orgRef = await resolveOrg(ctx, options.org);
|
|
5304
|
+
const { honeyTokens } = await ctx.client.listHoneyTokens(orgRef.id);
|
|
5305
|
+
emit(options, { honeyTokens }, () => printTable(honeyTokens, [
|
|
5306
|
+
col("name", (t) => t.name),
|
|
5307
|
+
col("status", (t) => t.tripCount > 0 ? `TRIPPED ${t.tripCount}×` : "untouched"),
|
|
5308
|
+
col("planted in", (t) => t.placement ?? "—"),
|
|
5309
|
+
col("last tripped", (t) => t.lastTrippedAt ?? "never"),
|
|
5310
|
+
col("last source", (t) => t.lastTripIp ?? "—"),
|
|
5311
|
+
col("id", (t) => t.id)
|
|
5312
|
+
], "no decoys planted — create one with `seekrit honey-token create`"));
|
|
5313
|
+
});
|
|
5314
|
+
honey.command("delete <honeyTokenId>").alias("rm").description("delete a decoy (stops it alerting)").option("--org <slug>").option("--yes", "skip the confirmation prompt").action(async (honeyTokenId, options) => {
|
|
5315
|
+
const ctx = buildContext();
|
|
5316
|
+
const orgRef = await resolveOrg(ctx, options.org);
|
|
5317
|
+
await confirmDestructive(options.yes, `Delete ${honeyTokenId}? Wherever you planted it goes back to being unwatched — pull the bait too.`);
|
|
5318
|
+
await ctx.client.deleteHoneyToken(orgRef.id, honeyTokenId);
|
|
5319
|
+
console.error(`${honeyTokenId} deleted`);
|
|
5320
|
+
});
|
|
5321
|
+
}
|
|
5322
|
+
//#endregion
|
|
5236
5323
|
//#region src/logsink.ts
|
|
5237
5324
|
/** Collect repeated `--header Name: value` flags into a map. */
|
|
5238
5325
|
function collectHeader(value, acc = {}) {
|
|
@@ -7307,6 +7394,7 @@ program.command("export").description("print decrypted secrets (dotenv, json, or
|
|
|
7307
7394
|
console.log(formatSecrets(values, options.format));
|
|
7308
7395
|
});
|
|
7309
7396
|
registerAccessCommands(program);
|
|
7397
|
+
registerHoneyTokenCommands(program);
|
|
7310
7398
|
const token = program.command("token").description("manage service tokens (CI, docker, agents)");
|
|
7311
7399
|
token.command("create").description("create a service token (runtime, or --admin for provisioning); prints it once").requiredOption("--name <name>", "display name, e.g. ci-deploy").option("--org <slug>").option("--app <slug>", "application to bind the token to (runtime tokens)").option("--env <slug>", "environment to bind the token to (runtime tokens)").option("--admin", "mint an org-scoped admin token that can provision structure (no env binding required)").option("--allow <group=env>", "also grant an alternate group slice (for `run --with`)", collectKv).option("--no-grant", "skip auto-granting the env + composed group keys").action(async (options) => {
|
|
7312
7400
|
const ctx = buildContext();
|
|
@@ -7395,7 +7483,7 @@ registerMongoCommands(program);
|
|
|
7395
7483
|
registerKmsCommands(program);
|
|
7396
7484
|
registerRecoveryCommands(program);
|
|
7397
7485
|
program.command("mcp").description("run an MCP server over stdio so AI agents can drive seekrit").action(async () => {
|
|
7398
|
-
const { runMcpServer } = await import("./mcp-
|
|
7486
|
+
const { runMcpServer } = await import("./mcp-DR-zla_u.js");
|
|
7399
7487
|
await runMcpServer();
|
|
7400
7488
|
});
|
|
7401
7489
|
registerAuditCommands(program);
|
|
@@ -26,6 +26,16 @@ function errText(err) {
|
|
|
26
26
|
isError: true
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
/** Read-only and safe to repeat — every list/inspect tool. */
|
|
30
|
+
const ro = {
|
|
31
|
+
readOnly: true,
|
|
32
|
+
idempotent: true
|
|
33
|
+
};
|
|
34
|
+
/** Removes or overwrites something; repeating it lands in the same state. */
|
|
35
|
+
const destructive = {
|
|
36
|
+
destructive: true,
|
|
37
|
+
idempotent: true
|
|
38
|
+
};
|
|
29
39
|
/**
|
|
30
40
|
* Short primer surfaced as the MCP server `instructions`. Most clients show
|
|
31
41
|
* this to the model on connect, so it has to orient an agent that lands here
|
|
@@ -194,10 +204,17 @@ async function runMcpServer(options = {}) {
|
|
|
194
204
|
version: options.version ?? version
|
|
195
205
|
}, { instructions: serverInstructions() });
|
|
196
206
|
/** Register a tool whose handler returns data (serialized) or throws (→ isError). */
|
|
197
|
-
const tool = (name, description, shape, handler) => {
|
|
207
|
+
const tool = (name, description, hints, shape, handler) => {
|
|
198
208
|
server.registerTool(name, {
|
|
199
209
|
description,
|
|
200
|
-
inputSchema: shape
|
|
210
|
+
inputSchema: shape,
|
|
211
|
+
annotations: {
|
|
212
|
+
title: name,
|
|
213
|
+
readOnlyHint: hints.readOnly ?? false,
|
|
214
|
+
destructiveHint: hints.destructive ?? false,
|
|
215
|
+
idempotentHint: hints.idempotent ?? false,
|
|
216
|
+
openWorldHint: true
|
|
217
|
+
}
|
|
201
218
|
}, (async (args) => {
|
|
202
219
|
try {
|
|
203
220
|
return jsonText(await handler(args));
|
|
@@ -215,7 +232,7 @@ async function runMcpServer(options = {}) {
|
|
|
215
232
|
openWorldHint: false
|
|
216
233
|
}
|
|
217
234
|
}, async () => jsonText(getStartedText()));
|
|
218
|
-
tool("whoami", "Show the authenticated identity, its role, and accessible orgs.", {}, async () => {
|
|
235
|
+
tool("whoami", "Show the authenticated identity, its role, and accessible orgs.", ro, {}, async () => {
|
|
219
236
|
const ctx = getCtx();
|
|
220
237
|
if (isTokenAuth(ctx) && ctx.auth.type === "bearer") {
|
|
221
238
|
const { tokenId } = await parseServiceToken(ctx.auth.token);
|
|
@@ -236,13 +253,13 @@ async function runMcpServer(options = {}) {
|
|
|
236
253
|
...await ctx.client.me()
|
|
237
254
|
};
|
|
238
255
|
});
|
|
239
|
-
tool("list_orgs", "List organizations the caller can access.", {}, async () => (await getCtx().client.listOrgs()).orgs);
|
|
240
|
-
tool("list_apps", "List applications in an organization.", { org: z.string().optional() }, async ({ org }) => {
|
|
256
|
+
tool("list_orgs", "List organizations the caller can access.", ro, {}, async () => (await getCtx().client.listOrgs()).orgs);
|
|
257
|
+
tool("list_apps", "List applications in an organization.", ro, { org: z.string().optional() }, async ({ org }) => {
|
|
241
258
|
const ctx = getCtx();
|
|
242
259
|
const orgRef = await resolveOrg(ctx, org);
|
|
243
260
|
return (await ctx.client.listApps(orgRef.id)).apps;
|
|
244
261
|
});
|
|
245
|
-
tool("list_envs", "List environments of an application.", {
|
|
262
|
+
tool("list_envs", "List environments of an application.", ro, {
|
|
246
263
|
org: z.string().optional(),
|
|
247
264
|
app: z.string()
|
|
248
265
|
}, async ({ org, app }) => {
|
|
@@ -253,7 +270,7 @@ async function runMcpServer(options = {}) {
|
|
|
253
270
|
if (!appRow) throw new Error(`no app "${app}" in ${orgRef.slug}`);
|
|
254
271
|
return (await ctx.client.listEnvs(orgRef.id, appRow.id)).environments;
|
|
255
272
|
});
|
|
256
|
-
tool("list_branches", "List ephemeral branch configs in an application (optionally just one environment's).", {
|
|
273
|
+
tool("list_branches", "List ephemeral branch configs in an application (optionally just one environment's).", ro, {
|
|
257
274
|
org: z.string().optional(),
|
|
258
275
|
app: z.string(),
|
|
259
276
|
env: z.string().optional()
|
|
@@ -271,12 +288,12 @@ async function runMcpServer(options = {}) {
|
|
|
271
288
|
});
|
|
272
289
|
return (await ctx.client.listBranches(parent.orgId, parent.envId)).branches;
|
|
273
290
|
});
|
|
274
|
-
tool("list_groups", "List shared groups (reusable secret bags) in an organization.", { org: z.string().optional() }, async ({ org }) => {
|
|
291
|
+
tool("list_groups", "List shared groups (reusable secret bags) in an organization.", ro, { org: z.string().optional() }, async ({ org }) => {
|
|
275
292
|
const ctx = getCtx();
|
|
276
293
|
const orgRef = await resolveOrg(ctx, org);
|
|
277
294
|
return (await ctx.client.listGroups(orgRef.id)).groups;
|
|
278
295
|
});
|
|
279
|
-
tool("list_group_envs", "List a group's environments (per-slug value sets).", {
|
|
296
|
+
tool("list_group_envs", "List a group's environments (per-slug value sets).", ro, {
|
|
280
297
|
org: z.string().optional(),
|
|
281
298
|
group: z.string()
|
|
282
299
|
}, async ({ org, group }) => {
|
|
@@ -287,7 +304,7 @@ async function runMcpServer(options = {}) {
|
|
|
287
304
|
});
|
|
288
305
|
return (await ctx.client.listGroupEnvs(g.orgId, g.id)).environments;
|
|
289
306
|
});
|
|
290
|
-
tool("list_env_groups", "List the groups composed into an application environment (precedence order).", {
|
|
307
|
+
tool("list_env_groups", "List the groups composed into an application environment (precedence order).", ro, {
|
|
291
308
|
org: z.string().optional(),
|
|
292
309
|
app: z.string(),
|
|
293
310
|
env: z.string()
|
|
@@ -300,17 +317,17 @@ async function runMcpServer(options = {}) {
|
|
|
300
317
|
});
|
|
301
318
|
return (await ctx.client.listEnvGroups(target.orgId, target.envId)).groups;
|
|
302
319
|
});
|
|
303
|
-
tool("list_members", "List organization members and their public keys (for granting access).", { org: z.string().optional() }, async ({ org }) => {
|
|
320
|
+
tool("list_members", "List organization members and their public keys (for granting access).", ro, { org: z.string().optional() }, async ({ org }) => {
|
|
304
321
|
const ctx = getCtx();
|
|
305
322
|
const orgRef = await resolveOrg(ctx, org);
|
|
306
323
|
return (await ctx.client.listMembers(orgRef.id)).members;
|
|
307
324
|
});
|
|
308
|
-
tool("kms_list_keys", "List managed KMS keys the caller can see (metadata only).", { org: z.string().optional() }, async ({ org }) => {
|
|
325
|
+
tool("kms_list_keys", "List managed KMS keys the caller can see (metadata only).", ro, { org: z.string().optional() }, async ({ org }) => {
|
|
309
326
|
const ctx = getCtx();
|
|
310
327
|
const orgRef = await resolveOrg(ctx, org);
|
|
311
328
|
return (await ctx.client.listKmsKeys(orgRef.id)).keys;
|
|
312
329
|
});
|
|
313
|
-
tool("kms_create_key", "Create an org-scoped managed key. Material is generated locally and wrapped to each grantee (self plus any listed users/tokens); the server never sees it. Use the CLI for app/group-scoped keys.", {
|
|
330
|
+
tool("kms_create_key", "Create an org-scoped managed key. Material is generated locally and wrapped to each grantee (self plus any listed users/tokens); the server never sees it. Use the CLI for app/group-scoped keys.", { idempotent: false }, {
|
|
314
331
|
org: z.string().optional(),
|
|
315
332
|
name: z.string(),
|
|
316
333
|
purpose: z.enum(["encrypt", "sign"]),
|
|
@@ -351,7 +368,7 @@ async function runMcpServer(options = {}) {
|
|
|
351
368
|
});
|
|
352
369
|
return key;
|
|
353
370
|
});
|
|
354
|
-
tool("kms_grant", "Grant a principal (user email or token id) use of a key's current version.", {
|
|
371
|
+
tool("kms_grant", "Grant a principal (user email or token id) use of a key's current version.", { idempotent: true }, {
|
|
355
372
|
org: z.string().optional(),
|
|
356
373
|
key: z.string(),
|
|
357
374
|
user: z.string().optional(),
|
|
@@ -376,7 +393,7 @@ async function runMcpServer(options = {}) {
|
|
|
376
393
|
key: k.name
|
|
377
394
|
};
|
|
378
395
|
});
|
|
379
|
-
tool("kms_encrypt", "Encrypt a value under a managed encrypt key; returns a ce1 ciphertext blob. `context` (if given) is bound as AAD and must be supplied identically to decrypt.", {
|
|
396
|
+
tool("kms_encrypt", "Encrypt a value under a managed encrypt key; returns a ce1 ciphertext blob. `context` (if given) is bound as AAD and must be supplied identically to decrypt.", { readOnly: true }, {
|
|
380
397
|
org: z.string().optional(),
|
|
381
398
|
key: z.string(),
|
|
382
399
|
plaintext: z.string(),
|
|
@@ -393,7 +410,7 @@ async function runMcpServer(options = {}) {
|
|
|
393
410
|
version: currentVersion
|
|
394
411
|
}, plaintext, context ?? "") };
|
|
395
412
|
});
|
|
396
|
-
tool("kms_decrypt", "Decrypt a ce1 blob. Supply the same `context` used to encrypt.", {
|
|
413
|
+
tool("kms_decrypt", "Decrypt a ce1 blob. Supply the same `context` used to encrypt.", ro, {
|
|
397
414
|
org: z.string().optional(),
|
|
398
415
|
key: z.string(),
|
|
399
416
|
ciphertext: z.string(),
|
|
@@ -407,7 +424,7 @@ async function runMcpServer(options = {}) {
|
|
|
407
424
|
const { material } = await kmsRecoverMaterial(ctx, orgRef.id, k.id, ref.version);
|
|
408
425
|
return { plaintext: await kmsDecrypt(material, ciphertext, context ?? "") };
|
|
409
426
|
});
|
|
410
|
-
tool("kms_generate_data_key", "Generate a data key under a managed encrypt key (envelope encryption). Returns the plaintext key (base64) and its wrapped form to store.", {
|
|
427
|
+
tool("kms_generate_data_key", "Generate a data key under a managed encrypt key (envelope encryption). Returns the plaintext key (base64) and its wrapped form to store.", { readOnly: true }, {
|
|
411
428
|
org: z.string().optional(),
|
|
412
429
|
key: z.string()
|
|
413
430
|
}, async ({ org, key }) => {
|
|
@@ -426,7 +443,7 @@ async function runMcpServer(options = {}) {
|
|
|
426
443
|
wrapped: dk.wrapped
|
|
427
444
|
};
|
|
428
445
|
});
|
|
429
|
-
tool("kms_sign", "Sign a message with a managed signing key; returns an sg1 signature blob.", {
|
|
446
|
+
tool("kms_sign", "Sign a message with a managed signing key; returns an sg1 signature blob.", { readOnly: true }, {
|
|
430
447
|
org: z.string().optional(),
|
|
431
448
|
key: z.string(),
|
|
432
449
|
message: z.string()
|
|
@@ -442,7 +459,7 @@ async function runMcpServer(options = {}) {
|
|
|
442
459
|
version: currentVersion
|
|
443
460
|
}, message) };
|
|
444
461
|
});
|
|
445
|
-
tool("kms_verify", "Verify an sg1 signature over a message using a signing key's published public key (no grant needed).", {
|
|
462
|
+
tool("kms_verify", "Verify an sg1 signature over a message using a signing key's published public key (no grant needed).", ro, {
|
|
446
463
|
org: z.string().optional(),
|
|
447
464
|
key: z.string(),
|
|
448
465
|
signature: z.string(),
|
|
@@ -457,7 +474,7 @@ async function runMcpServer(options = {}) {
|
|
|
457
474
|
if (!pub) throw new Error(`no published public key for version ${ref.version}`);
|
|
458
475
|
return { valid: await verifyMessage(await importVerifyingKey(pub), signature, message) };
|
|
459
476
|
});
|
|
460
|
-
tool("list_secrets", "List secret names + versions in an environment (never values).", targetShape, async (o) => {
|
|
477
|
+
tool("list_secrets", "List secret names + versions in an environment (never values).", ro, targetShape, async (o) => {
|
|
461
478
|
const ctx = getCtx();
|
|
462
479
|
const { orgId, envId } = await resolveTargetEnv(ctx, o);
|
|
463
480
|
const { secrets } = await ctx.client.listSecrets(orgId, envId);
|
|
@@ -467,12 +484,12 @@ async function runMcpServer(options = {}) {
|
|
|
467
484
|
updatedAt: s.updatedAt
|
|
468
485
|
}));
|
|
469
486
|
});
|
|
470
|
-
tool("list_tokens", "List an organization's service tokens (never the secret token strings).", { org: z.string().optional() }, async ({ org }) => {
|
|
487
|
+
tool("list_tokens", "List an organization's service tokens (never the secret token strings).", ro, { org: z.string().optional() }, async ({ org }) => {
|
|
471
488
|
const ctx = getCtx();
|
|
472
489
|
const orgRef = await resolveOrg(ctx, org);
|
|
473
490
|
return (await ctx.client.listTokens(orgRef.id)).tokens;
|
|
474
491
|
});
|
|
475
|
-
tool("audit", "Read the organization's audit trail (most recent first).", {
|
|
492
|
+
tool("audit", "Read the organization's audit trail (most recent first).", ro, {
|
|
476
493
|
org: z.string().optional(),
|
|
477
494
|
limit: z.number().int().min(1).max(200).optional(),
|
|
478
495
|
action: z.string().optional().describe("filter by action, e.g. secret.updated")
|
|
@@ -484,14 +501,14 @@ async function runMcpServer(options = {}) {
|
|
|
484
501
|
action
|
|
485
502
|
})).entries;
|
|
486
503
|
});
|
|
487
|
-
tool("create_org", "Create an organization. Requires a user session — service tokens cannot own a Stytch org.", {
|
|
504
|
+
tool("create_org", "Create an organization. Requires a user session — service tokens cannot own a Stytch org.", { idempotent: false }, {
|
|
488
505
|
name: z.string(),
|
|
489
506
|
slug: z.string()
|
|
490
507
|
}, async ({ name, slug }) => (await getCtx().client.createOrg({
|
|
491
508
|
name,
|
|
492
509
|
slug
|
|
493
510
|
})).org);
|
|
494
|
-
tool("create_app", "Create an application in an organization.", {
|
|
511
|
+
tool("create_app", "Create an application in an organization.", { idempotent: false }, {
|
|
495
512
|
org: z.string().optional(),
|
|
496
513
|
name: z.string(),
|
|
497
514
|
slug: z.string()
|
|
@@ -503,7 +520,7 @@ async function runMcpServer(options = {}) {
|
|
|
503
520
|
slug
|
|
504
521
|
})).app;
|
|
505
522
|
});
|
|
506
|
-
tool("create_group", "Create a shared group (reusable secret bag) in an organization.", {
|
|
523
|
+
tool("create_group", "Create a shared group (reusable secret bag) in an organization.", { idempotent: false }, {
|
|
507
524
|
org: z.string().optional(),
|
|
508
525
|
name: z.string(),
|
|
509
526
|
slug: z.string()
|
|
@@ -515,7 +532,7 @@ async function runMcpServer(options = {}) {
|
|
|
515
532
|
slug
|
|
516
533
|
})).group;
|
|
517
534
|
});
|
|
518
|
-
tool("create_env", "Create an application environment. Generates the data key locally and grants it to the caller.", {
|
|
535
|
+
tool("create_env", "Create an application environment. Generates the data key locally and grants it to the caller.", { idempotent: false }, {
|
|
519
536
|
org: z.string().optional(),
|
|
520
537
|
app: z.string(),
|
|
521
538
|
name: z.string(),
|
|
@@ -533,7 +550,7 @@ async function runMcpServer(options = {}) {
|
|
|
533
550
|
wrappedDek
|
|
534
551
|
})).environment;
|
|
535
552
|
});
|
|
536
|
-
tool("create_branch", "Fork an environment into an ephemeral branch (a per-PR / preview config). The branch inherits the parent's secrets by layering at read time — nothing is copied or re-encrypted — and holds only the values you override on it. Generates the branch's data key locally, grants it to the caller, and shares it with the parent's other readers.", {
|
|
553
|
+
tool("create_branch", "Fork an environment into an ephemeral branch (a per-PR / preview config). The branch inherits the parent's secrets by layering at read time — nothing is copied or re-encrypted — and holds only the values you override on it. Generates the branch's data key locally, grants it to the caller, and shares it with the parent's other readers.", { idempotent: false }, {
|
|
537
554
|
org: z.string().optional(),
|
|
538
555
|
app: z.string(),
|
|
539
556
|
from: z.string().describe("the environment to branch"),
|
|
@@ -568,7 +585,7 @@ async function runMcpServer(options = {}) {
|
|
|
568
585
|
grants
|
|
569
586
|
})).branch;
|
|
570
587
|
});
|
|
571
|
-
tool("delete_branch", "Tear down a branch config and every value it overrode. The parent environment is untouched.", {
|
|
588
|
+
tool("delete_branch", "Tear down a branch config and every value it overrode. The parent environment is untouched.", destructive, {
|
|
572
589
|
org: z.string().optional(),
|
|
573
590
|
app: z.string(),
|
|
574
591
|
branch: z.string()
|
|
@@ -582,7 +599,7 @@ async function runMcpServer(options = {}) {
|
|
|
582
599
|
await ctx.client.deleteBranch(appRef.orgId, target.id);
|
|
583
600
|
return { deleted: target.slug };
|
|
584
601
|
});
|
|
585
|
-
tool("create_group_env", "Create a group environment. Generates the data key locally and grants it to the caller.", {
|
|
602
|
+
tool("create_group_env", "Create a group environment. Generates the data key locally and grants it to the caller.", { idempotent: false }, {
|
|
586
603
|
org: z.string().optional(),
|
|
587
604
|
group: z.string(),
|
|
588
605
|
name: z.string(),
|
|
@@ -600,7 +617,7 @@ async function runMcpServer(options = {}) {
|
|
|
600
617
|
wrappedDek
|
|
601
618
|
})).environment;
|
|
602
619
|
});
|
|
603
|
-
tool("compose_group", "Compose a group into an application environment (higher position wins on name clashes).", {
|
|
620
|
+
tool("compose_group", "Compose a group into an application environment (higher position wins on name clashes).", { idempotent: true }, {
|
|
604
621
|
org: z.string().optional(),
|
|
605
622
|
app: z.string(),
|
|
606
623
|
env: z.string(),
|
|
@@ -622,7 +639,7 @@ async function runMcpServer(options = {}) {
|
|
|
622
639
|
position
|
|
623
640
|
})).group;
|
|
624
641
|
});
|
|
625
|
-
tool("uncompose_group", "Remove a composed group from an application environment.", {
|
|
642
|
+
tool("uncompose_group", "Remove a composed group from an application environment.", { idempotent: true }, {
|
|
626
643
|
org: z.string().optional(),
|
|
627
644
|
app: z.string(),
|
|
628
645
|
env: z.string(),
|
|
@@ -641,7 +658,7 @@ async function runMcpServer(options = {}) {
|
|
|
641
658
|
await ctx.client.unlinkEnvGroup(target.orgId, target.envId, g.id);
|
|
642
659
|
return { ok: true };
|
|
643
660
|
});
|
|
644
|
-
tool("set_secret", "Encrypt a value locally and store it in an environment. A value may reference another secret as ${OTHER_SECRET}: the reference is stored literally and expanded whenever the secret is read, so it tracks the referenced value. Write $${OTHER_SECRET} for a literal.", {
|
|
661
|
+
tool("set_secret", "Encrypt a value locally and store it in an environment. A value may reference another secret as ${OTHER_SECRET}: the reference is stored literally and expanded whenever the secret is read, so it tracks the referenced value. Write $${OTHER_SECRET} for a literal.", { idempotent: false }, {
|
|
645
662
|
...targetShape,
|
|
646
663
|
name: z.string(),
|
|
647
664
|
value: z.string()
|
|
@@ -655,7 +672,7 @@ async function runMcpServer(options = {}) {
|
|
|
655
672
|
name: o.name
|
|
656
673
|
};
|
|
657
674
|
});
|
|
658
|
-
tool("get_secret", "Return one secret. By default only reports presence + version; pass reveal:true to decrypt the plaintext into this response (avoid unless the value is actually needed — prefer run_command). A revealed current value has its ${OTHER_SECRET} references expanded against this environment's own secrets; pass raw:true for the stored text instead. Pass `version` to read an earlier version instead of the current one (always as stored, never expanded).", {
|
|
675
|
+
tool("get_secret", "Return one secret. By default only reports presence + version; pass reveal:true to decrypt the plaintext into this response (avoid unless the value is actually needed — prefer run_command). A revealed current value has its ${OTHER_SECRET} references expanded against this environment's own secrets; pass raw:true for the stored text instead. Pass `version` to read an earlier version instead of the current one (always as stored, never expanded).", ro, {
|
|
659
676
|
...targetShape,
|
|
660
677
|
name: z.string(),
|
|
661
678
|
reveal: z.boolean().optional(),
|
|
@@ -692,7 +709,7 @@ async function runMcpServer(options = {}) {
|
|
|
692
709
|
revealed: true
|
|
693
710
|
};
|
|
694
711
|
});
|
|
695
|
-
tool("list_secret_versions", "List a secret's version history: who wrote each version, when, and which ones were restores. Never returns values — pair it with restore_secret to roll back, or get_secret(version, reveal:true) to inspect one.", {
|
|
712
|
+
tool("list_secret_versions", "List a secret's version history: who wrote each version, when, and which ones were restores. Never returns values — pair it with restore_secret to roll back, or get_secret(version, reveal:true) to inspect one.", ro, {
|
|
696
713
|
...targetShape,
|
|
697
714
|
name: z.string(),
|
|
698
715
|
limit: z.number().int().min(1).max(200).optional().describe("default 20")
|
|
@@ -710,7 +727,7 @@ async function runMcpServer(options = {}) {
|
|
|
710
727
|
}))
|
|
711
728
|
};
|
|
712
729
|
});
|
|
713
|
-
tool("restore_secret", "Roll a secret back to an earlier version. The stored ciphertext is replayed as a NEW version (history is append-only, nothing is overwritten). Keyless — no decryption happens, so this works even without a key.", {
|
|
730
|
+
tool("restore_secret", "Roll a secret back to an earlier version. The stored ciphertext is replayed as a NEW version (history is append-only, nothing is overwritten). Keyless — no decryption happens, so this works even without a key.", { idempotent: false }, {
|
|
714
731
|
...targetShape,
|
|
715
732
|
name: z.string(),
|
|
716
733
|
version: z.number().int().positive()
|
|
@@ -725,7 +742,7 @@ async function runMcpServer(options = {}) {
|
|
|
725
742
|
version: secret.version
|
|
726
743
|
};
|
|
727
744
|
});
|
|
728
|
-
tool("delete_secret", "Delete a secret from an environment.", {
|
|
745
|
+
tool("delete_secret", "Delete a secret from an environment.", destructive, {
|
|
729
746
|
...targetShape,
|
|
730
747
|
name: z.string()
|
|
731
748
|
}, async (o) => {
|
|
@@ -738,6 +755,9 @@ async function runMcpServer(options = {}) {
|
|
|
738
755
|
};
|
|
739
756
|
});
|
|
740
757
|
tool("run_command", "Run a command with the resolved secrets injected as environment variables, and return its exit code + captured output. Secret VALUES are never returned — this is the preferred way to use secrets. process env > .env > app env > groups.", {
|
|
758
|
+
destructive: true,
|
|
759
|
+
idempotent: false
|
|
760
|
+
}, {
|
|
741
761
|
command: z.string().describe("executable to run"),
|
|
742
762
|
args: z.array(z.string()).optional(),
|
|
743
763
|
org: z.string().optional(),
|
|
@@ -760,7 +780,7 @@ async function runMcpServer(options = {}) {
|
|
|
760
780
|
injectedVarCount: Object.keys(values).length
|
|
761
781
|
};
|
|
762
782
|
});
|
|
763
|
-
tool("export_env", "Write the resolved secrets to a dotenv file on disk and return the variable names written (never the values). Use to materialize a .env for local tooling.", {
|
|
783
|
+
tool("export_env", "Write the resolved secrets to a dotenv file on disk and return the variable names written (never the values). Use to materialize a .env for local tooling.", destructive, {
|
|
764
784
|
file: z.string().describe("path to write, e.g. .env"),
|
|
765
785
|
org: z.string().optional(),
|
|
766
786
|
app: z.string().optional(),
|
|
@@ -779,7 +799,7 @@ async function runMcpServer(options = {}) {
|
|
|
779
799
|
names: Object.keys(values).sort()
|
|
780
800
|
};
|
|
781
801
|
});
|
|
782
|
-
tool("create_token", "Mint a service token, printed once. Runtime tokens bind to one app environment (auto-granted its keys, so a command/agent can decrypt it). Pass admin:true for an org-scoped provisioning token (create apps/groups/envs, grant, mint tokens) — admin tokens need no env binding.", {
|
|
802
|
+
tool("create_token", "Mint a service token, printed once. Runtime tokens bind to one app environment (auto-granted its keys, so a command/agent can decrypt it). Pass admin:true for an org-scoped provisioning token (create apps/groups/envs, grant, mint tokens) — admin tokens need no env binding.", { idempotent: false }, {
|
|
783
803
|
name: z.string().describe("display name, e.g. ci-deploy or agent-session"),
|
|
784
804
|
org: z.string().optional(),
|
|
785
805
|
app: z.string().optional().describe("bind to this app (runtime tokens)"),
|
|
@@ -830,7 +850,7 @@ async function runMcpServer(options = {}) {
|
|
|
830
850
|
note: "save this now — the secret token string is not stored and cannot be retrieved"
|
|
831
851
|
};
|
|
832
852
|
});
|
|
833
|
-
tool("revoke_token", "Revoke a service token by id.", {
|
|
853
|
+
tool("revoke_token", "Revoke a service token by id.", destructive, {
|
|
834
854
|
org: z.string().optional(),
|
|
835
855
|
tokenId: z.string()
|
|
836
856
|
}, async ({ org, tokenId }) => {
|
|
@@ -842,7 +862,7 @@ async function runMcpServer(options = {}) {
|
|
|
842
862
|
tokenId
|
|
843
863
|
};
|
|
844
864
|
});
|
|
845
|
-
tool("grant_env", "Grant a member (by email) or service token (by id) access to an environment's data key. Re-wraps the DEK to the grantee — the caller must already hold the key.", {
|
|
865
|
+
tool("grant_env", "Grant a member (by email) or service token (by id) access to an environment's data key. Re-wraps the DEK to the grantee — the caller must already hold the key.", { idempotent: true }, {
|
|
846
866
|
...targetShape,
|
|
847
867
|
user: z.string().optional().describe("org member email"),
|
|
848
868
|
token: z.string().optional().describe("service token id (skt_…)")
|
|
@@ -887,12 +907,12 @@ async function runMcpServer(options = {}) {
|
|
|
887
907
|
principalId
|
|
888
908
|
};
|
|
889
909
|
});
|
|
890
|
-
tool("list_pg_targets", "List registered Postgres provisioning targets for temporary credentials.", { org: z.string().optional() }, async ({ org }) => {
|
|
910
|
+
tool("list_pg_targets", "List registered Postgres provisioning targets for temporary credentials.", ro, { org: z.string().optional() }, async ({ org }) => {
|
|
891
911
|
const ctx = getCtx();
|
|
892
912
|
const orgRef = await resolveOrg(ctx, org);
|
|
893
913
|
return (await ctx.client.listLeaseTargets(orgRef.id)).targets;
|
|
894
914
|
});
|
|
895
|
-
tool("create_pg_lease", "Mint a short-lived Postgres credential against a target and return a ready-to-use connection URL. The password is generated on THIS machine and only its SCRAM verifier is sent to the API — the plaintext never reaches seekrit or Postgres at rest. The role auto-expires; revoke early with revoke_pg_lease.", {
|
|
915
|
+
tool("create_pg_lease", "Mint a short-lived Postgres credential against a target and return a ready-to-use connection URL. The password is generated on THIS machine and only its SCRAM verifier is sent to the API — the plaintext never reaches seekrit or Postgres at rest. The role auto-expires; revoke early with revoke_pg_lease.", { idempotent: false }, {
|
|
896
916
|
org: z.string().optional(),
|
|
897
917
|
target: z.string().describe("target id or name"),
|
|
898
918
|
role: z.string().optional().describe("role name to create (default: random tmp_ name)"),
|
|
@@ -921,12 +941,12 @@ async function runMcpServer(options = {}) {
|
|
|
921
941
|
note: "short-lived credential; it auto-expires and the plaintext is not stored anywhere"
|
|
922
942
|
};
|
|
923
943
|
});
|
|
924
|
-
tool("list_pg_leases", "List Postgres leases (the ledger — never secret material).", { org: z.string().optional() }, async ({ org }) => {
|
|
944
|
+
tool("list_pg_leases", "List Postgres leases (the ledger — never secret material).", ro, { org: z.string().optional() }, async ({ org }) => {
|
|
925
945
|
const ctx = getCtx();
|
|
926
946
|
const orgRef = await resolveOrg(ctx, org);
|
|
927
947
|
return (await ctx.client.listLeases(orgRef.id)).leases;
|
|
928
948
|
});
|
|
929
|
-
tool("revoke_pg_lease", "Revoke a Postgres lease now (drops the role immediately).", {
|
|
949
|
+
tool("revoke_pg_lease", "Revoke a Postgres lease now (drops the role immediately).", destructive, {
|
|
930
950
|
org: z.string().optional(),
|
|
931
951
|
leaseId: z.string()
|
|
932
952
|
}, async ({ org, leaseId }) => {
|
|
@@ -938,12 +958,12 @@ async function runMcpServer(options = {}) {
|
|
|
938
958
|
leaseId
|
|
939
959
|
};
|
|
940
960
|
});
|
|
941
|
-
tool("list_mysql_targets", "List registered MySQL/MariaDB provisioning targets for temporary credentials.", { org: z.string().optional() }, async ({ org }) => {
|
|
961
|
+
tool("list_mysql_targets", "List registered MySQL/MariaDB provisioning targets for temporary credentials.", ro, { org: z.string().optional() }, async ({ org }) => {
|
|
942
962
|
const ctx = getCtx();
|
|
943
963
|
const orgRef = await resolveOrg(ctx, org);
|
|
944
964
|
return (await ctx.client.listLeaseTargets(orgRef.id)).targets.filter((t) => t.provider === "mysql");
|
|
945
965
|
});
|
|
946
|
-
tool("create_mysql_lease", "Mint a short-lived MySQL/MariaDB credential against a target and return a ready-to-use connection URL. The password is generated on THIS machine and only its mysql_native_password hash is sent to the API — the plaintext never reaches seekrit or MySQL at rest. The user auto-expires; revoke early with revoke_mysql_lease.", {
|
|
966
|
+
tool("create_mysql_lease", "Mint a short-lived MySQL/MariaDB credential against a target and return a ready-to-use connection URL. The password is generated on THIS machine and only its mysql_native_password hash is sent to the API — the plaintext never reaches seekrit or MySQL at rest. The user auto-expires; revoke early with revoke_mysql_lease.", { idempotent: false }, {
|
|
947
967
|
org: z.string().optional(),
|
|
948
968
|
target: z.string().describe("target id or name"),
|
|
949
969
|
user: z.string().optional().describe("user name to create (default: random tmp_ name)"),
|
|
@@ -973,12 +993,12 @@ async function runMcpServer(options = {}) {
|
|
|
973
993
|
note: "short-lived credential; it auto-expires and the plaintext is not stored anywhere"
|
|
974
994
|
};
|
|
975
995
|
});
|
|
976
|
-
tool("list_mysql_leases", "List MySQL/MariaDB leases (the ledger — never secret material).", { org: z.string().optional() }, async ({ org }) => {
|
|
996
|
+
tool("list_mysql_leases", "List MySQL/MariaDB leases (the ledger — never secret material).", ro, { org: z.string().optional() }, async ({ org }) => {
|
|
977
997
|
const ctx = getCtx();
|
|
978
998
|
const orgRef = await resolveOrg(ctx, org);
|
|
979
999
|
return (await ctx.client.listLeases(orgRef.id)).leases.filter((l) => l.provider === "mysql");
|
|
980
1000
|
});
|
|
981
|
-
tool("revoke_mysql_lease", "Revoke a MySQL/MariaDB lease now (drops the user immediately).", {
|
|
1001
|
+
tool("revoke_mysql_lease", "Revoke a MySQL/MariaDB lease now (drops the user immediately).", destructive, {
|
|
982
1002
|
org: z.string().optional(),
|
|
983
1003
|
leaseId: z.string()
|
|
984
1004
|
}, async ({ org, leaseId }) => {
|
|
@@ -990,7 +1010,7 @@ async function runMcpServer(options = {}) {
|
|
|
990
1010
|
leaseId
|
|
991
1011
|
};
|
|
992
1012
|
});
|
|
993
|
-
tool("configure_project", "Link a directory to an org/app by writing seekrit.json (like `seekrit init`). The environment is chosen by the service token at runtime.", {
|
|
1013
|
+
tool("configure_project", "Link a directory to an org/app by writing seekrit.json (like `seekrit init`). The environment is chosen by the service token at runtime.", destructive, {
|
|
994
1014
|
org: z.string(),
|
|
995
1015
|
app: z.string(),
|
|
996
1016
|
dir: z.string().optional()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seekrit/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"@types/node": "^26.1.0",
|
|
28
28
|
"tsdown": "^0.22.3",
|
|
29
29
|
"vitest": "^4.1.9",
|
|
30
|
-
"@seekrit/core": "0.0.1",
|
|
31
30
|
"@seekrit/api-client": "0.0.1",
|
|
32
|
-
"@seekrit/crypto": "0.0.1"
|
|
31
|
+
"@seekrit/crypto": "0.0.1",
|
|
32
|
+
"@seekrit/core": "0.0.1"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsdown",
|