@seekrit/cli 0.32.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 +91 -3
- 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();
|
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": {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"tsdown": "^0.22.3",
|
|
29
29
|
"vitest": "^4.1.9",
|
|
30
30
|
"@seekrit/api-client": "0.0.1",
|
|
31
|
-
"@seekrit/
|
|
32
|
-
"@seekrit/
|
|
31
|
+
"@seekrit/crypto": "0.0.1",
|
|
32
|
+
"@seekrit/core": "0.0.1"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsdown",
|