@seekrit/cli 1.9.3 → 1.9.5

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/README.md CHANGED
@@ -30,9 +30,11 @@ seekrit login --token skt_…
30
30
  # leaves this machine). Only needed once per account.
31
31
  seekrit keys setup
32
32
 
33
- # 3. Link the current directory to an org / app. Writes seekrit.json, which is
34
- # safe to commit. The environment comes from the token at runtime.
35
- seekrit init --org acme --app storefront
33
+ # 3. Set the current directory up. Creates an app named after the project (and a
34
+ # development environment) if there is none, and writes seekrit.json, which
35
+ # is safe to commit. With no credential at all it signs up first — add
36
+ # --org-name "<the real project>" --yes to do that without a prompt.
37
+ seekrit init # or: seekrit init --org acme --app storefront
36
38
 
37
39
  # 4. Work with secrets. A service token names its own environment; on a user
38
40
  # session, add --env production.
@@ -56,7 +58,7 @@ Run `seekrit <command> --help` for full flags. The
56
58
  | `login [--token \| --dev-user] [--api-url]` | Save credentials to the global config. |
57
59
  | `whoami` | Show the authenticated identity and org membership. |
58
60
  | `keys setup` | Generate your keypair and protect it with a passphrase. |
59
- | `init --org --app` | Link this directory to an org/app (`seekrit.json`). The environment comes from the token. |
61
+ | `init [--org] [--app] [--env] [--from-env] [--invite]` | Set this directory up: sign up if there is no credential, create the app and a `development` environment if missing, write `seekrit.json`. |
60
62
  | `org create` / `app create` / `env create` | Create organizations, apps, and environments. |
61
63
  | `secrets list` | List secret names (never values). |
62
64
  | `secrets get <name> [--raw]` | Decrypt and print one secret value (`--raw` skips `${OTHER_SECRET}` expansion). |
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSyn
4
4
  import { z } from "zod";
5
5
  import { Command } from "commander";
6
6
  import { arch, homedir, hostname, platform, tmpdir, userInfo } from "node:os";
7
- import { dirname, join, parse, resolve } from "node:path";
7
+ import { basename, dirname, join, parse, resolve } from "node:path";
8
8
  import { createInterface } from "node:readline";
9
9
  import { Writable } from "node:stream";
10
10
  import { createHash } from "node:crypto";
@@ -1062,6 +1062,141 @@ function describeChange(kind, name, restoreFrom) {
1062
1062
  }
1063
1063
  }
1064
1064
  //#endregion
1065
+ //#region ../../packages/core/src/connect.ts
1066
+ /** Workload authentication is separate from secret-decryption principals. */
1067
+ const workloadPublicKeySchema = z.object({
1068
+ kty: z.literal("EC"),
1069
+ crv: z.literal("P-256"),
1070
+ x: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
1071
+ y: z.string().regex(/^[A-Za-z0-9_-]{43}$/)
1072
+ }).strict();
1073
+ const audienceSchema = z.string().min(1).max(256).regex(/^[^\s]+$/);
1074
+ /** HTTPS endpoint syntax. Resolved-address safety is enforced by the server runtime. */
1075
+ const connectUrlSchema = z.string().max(2048).url().refine((value) => {
1076
+ try {
1077
+ const u = new URL(value);
1078
+ return u.protocol === "https:" && !u.username && !u.password && !u.hash && !u.search && !u.port && u.hostname.length <= 253 && u.hostname.split(".").every((label) => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label)) && u.hostname.includes(".") && !/^[0-9.]+$/.test(u.hostname) && !u.hostname.endsWith(".") && !/(^|\.)(localhost|local|localdomain|internal|invalid|home|home\.arpa|onion)$/.test(u.hostname);
1079
+ } catch {
1080
+ return false;
1081
+ }
1082
+ }, "use a public HTTPS hostname without credentials, query, fragment, or port");
1083
+ const federationPolicySchema = z.object({
1084
+ issuer: connectUrlSchema,
1085
+ jwksUri: connectUrlSchema,
1086
+ audience: audienceSchema,
1087
+ subject: z.string().min(1).max(512),
1088
+ claims: z.record(z.string().regex(/^[a-zA-Z][a-zA-Z0-9_]{0,63}$/), z.string().min(1).max(512)).refine((claims) => Object.keys(claims).length <= 20 && Object.keys(claims).every((key) => ![
1089
+ "iss",
1090
+ "aud",
1091
+ "sub",
1092
+ "exp",
1093
+ "iat",
1094
+ "nbf"
1095
+ ].includes(key))).default({})
1096
+ }).strict();
1097
+ z.object({
1098
+ name: z.string().trim().min(1).max(100),
1099
+ environmentId: z.string().min(1).max(128),
1100
+ publicKey: workloadPublicKeySchema.optional(),
1101
+ federation: federationPolicySchema.optional(),
1102
+ audiences: z.array(audienceSchema).max(20).default([]),
1103
+ maxTtlSeconds: z.number().int().min(60).max(900).default(300)
1104
+ }).strict().refine((v) => !!v.publicKey !== !!v.federation, "choose a public key or a federation policy");
1105
+ z.object({
1106
+ enabled: z.boolean().optional(),
1107
+ publicKey: workloadPublicKeySchema.optional()
1108
+ }).strict().refine((v) => v.enabled !== void 0 || v.publicKey !== void 0);
1109
+ const githubPermissionsSchema = z.record(z.string().regex(/^[a-z_]{1,64}$/), z.enum(["read", "write"])).refine((p) => Object.keys(p).length > 0 && Object.keys(p).length <= 40, "specify between 1 and 40 permissions");
1110
+ const githubScopeSchema = z.object({
1111
+ repositoryIds: z.array(z.number().int().positive().max(Number.MAX_SAFE_INTEGER)).min(1).max(100),
1112
+ permissions: githubPermissionsSchema
1113
+ }).strict();
1114
+ /** Exact paths or explicitly allowed descendants, without URL decoding ambiguity. */
1115
+ const connectPathSchema = z.string().min(1).max(1024).refine((path) => /^\/[A-Za-z0-9_./~-]*$/.test(path) && !path.includes("//") && !path.split("/").some((part) => part === "." || part === ".."), "use an absolute API path without encoded characters or dot segments");
1116
+ const proxyRuleSchema = z.object({
1117
+ path: connectPathSchema,
1118
+ descendants: z.boolean().default(false),
1119
+ methods: z.array(z.enum([
1120
+ "GET",
1121
+ "POST",
1122
+ "PUT",
1123
+ "PATCH",
1124
+ "DELETE",
1125
+ "HEAD"
1126
+ ])).min(1).max(6)
1127
+ }).strict();
1128
+ const staticScopeSchema = z.object({ rules: z.array(proxyRuleSchema).min(1).max(30) }).strict();
1129
+ const oauthScopeSchema = z.object({ scopes: z.array(z.string().regex(/^[\x21\x23-\x5b\x5d-\x7e]{1,200}$/)).min(1).max(50).refine((scopes) => new Set(scopes).size === scopes.length, "scopes must be unique") }).strict();
1130
+ const oauthConfigSchema = oauthScopeSchema.extend({
1131
+ provider: z.literal("oauth"),
1132
+ grantType: z.enum(["client_credentials", "authorization_code"]),
1133
+ clientId: z.string().min(1).max(512),
1134
+ tokenEndpoint: connectUrlSchema,
1135
+ authorizationEndpoint: connectUrlSchema.optional(),
1136
+ tokenAuthMethod: z.enum(["client_secret_basic", "client_secret_post"]).default("client_secret_basic")
1137
+ });
1138
+ const customApiConfigSchema = staticScopeSchema.extend({
1139
+ provider: z.literal("custom_api"),
1140
+ origin: connectUrlSchema.refine((value) => {
1141
+ try {
1142
+ return new URL(value).pathname === "/";
1143
+ } catch {
1144
+ return false;
1145
+ }
1146
+ }, "use an origin without a path"),
1147
+ headerName: z.string().regex(/^[A-Za-z][A-Za-z0-9-]{0,63}$/).refine((v) => !/^(accept$|user-agent$|host|cookie|content-|proxy-|forwarded|x-forwarded-|cf-|connection|transfer-encoding|upgrade|trailer|te$)/i.test(v)),
1148
+ headerPrefix: z.string().max(64).regex(/^[\x20-\x7e]*$/).default("Bearer ")
1149
+ });
1150
+ const connectConfigSchema = z.discriminatedUnion("provider", [
1151
+ customApiConfigSchema,
1152
+ oauthConfigSchema,
1153
+ githubScopeSchema.extend({
1154
+ provider: z.literal("github_app"),
1155
+ appId: z.string().regex(/^[A-Za-z0-9_.-]{1,128}$/),
1156
+ installationId: z.string().regex(/^[1-9][0-9]{0,19}$/)
1157
+ }),
1158
+ staticScopeSchema.extend({
1159
+ provider: z.literal("static_key"),
1160
+ service: z.literal("resend"),
1161
+ rules: z.array(proxyRuleSchema.refine((rule) => rule.path === "/emails" || rule.path.startsWith("/emails/"), "the Resend connector supports only /emails and its descendants")).min(1).max(30)
1162
+ })
1163
+ ]).refine((v) => v.provider !== "oauth" || v.grantType !== "authorization_code" || !!v.authorizationEndpoint, "authorization-code connections require an authorization endpoint");
1164
+ const connectGrantPolicySchema = z.discriminatedUnion("provider", [
1165
+ staticScopeSchema.extend({ provider: z.literal("custom_api") }),
1166
+ oauthScopeSchema.extend({ provider: z.literal("oauth") }),
1167
+ githubScopeSchema.extend({ provider: z.literal("github_app") }),
1168
+ staticScopeSchema.extend({ provider: z.literal("static_key") })
1169
+ ]);
1170
+ z.object({
1171
+ name: z.string().trim().min(1).max(100),
1172
+ config: connectConfigSchema,
1173
+ credentialWrapped: z.string().startsWith("wd1.").max(32e3)
1174
+ }).strict();
1175
+ z.object({
1176
+ enabled: z.boolean().optional(),
1177
+ credentialWrapped: z.string().startsWith("wd1.").max(32e3).optional()
1178
+ }).strict().refine((v) => v.enabled !== void 0 || v.credentialWrapped !== void 0);
1179
+ z.object({
1180
+ workloadId: z.string().min(1).max(128),
1181
+ connectionId: z.string().min(1).max(128),
1182
+ policy: connectGrantPolicySchema
1183
+ }).strict();
1184
+ z.object({
1185
+ path: connectPathSchema,
1186
+ method: z.enum([
1187
+ "GET",
1188
+ "POST",
1189
+ "PUT",
1190
+ "PATCH",
1191
+ "DELETE",
1192
+ "HEAD"
1193
+ ]).default("GET"),
1194
+ query: z.record(z.string().min(1).max(100), z.string().max(2048)).default({}),
1195
+ /** JSON only; callers cannot inject authorization or forwarding headers. */
1196
+ body: z.string().max(65536).optional()
1197
+ }).strict().refine((v) => !["GET", "HEAD"].includes(v.method) || v.body === void 0, "GET and HEAD cannot carry a body");
1198
+ z.object({ audience: audienceSchema }).strict();
1199
+ //#endregion
1065
1200
  //#region ../../packages/core/src/dotenv.ts
1066
1201
  /**
1067
1202
  * `.env` parsing and serialization — the canonical definition of what a `.env`
@@ -1937,6 +2072,17 @@ function sshHostSetupInstructions(config) {
1937
2072
  //#endregion
1938
2073
  //#region ../../packages/core/src/types.ts
1939
2074
  const AUDIT_ACTIONS = [
2075
+ "workload.created",
2076
+ "workload.updated",
2077
+ "workload.session_issued",
2078
+ "workload.identity_issued",
2079
+ "connect.connection_created",
2080
+ "connect.connection_updated",
2081
+ "connect.grant_created",
2082
+ "connect.grant_revoked",
2083
+ "connect.token_issued",
2084
+ "connect.proxy_requested",
2085
+ "connect.access_denied",
1940
2086
  "org.created",
1941
2087
  "org.updated",
1942
2088
  "org.member_added",
@@ -5540,7 +5686,7 @@ async function createAgentTaskToken() {
5540
5686
  }
5541
5687
  //#endregion
5542
5688
  //#region package.json
5543
- var version = "1.9.3";
5689
+ var version = "1.9.5";
5544
5690
  //#endregion
5545
5691
  //#region ../../packages/api-client/src/index.ts
5546
5692
  /**
@@ -5945,6 +6091,42 @@ var SeekritClient = class {
5945
6091
  deleteHoneyToken(orgId, honeyTokenId) {
5946
6092
  return this.request("DELETE", `/v1/orgs/${orgId}/honey-tokens/${honeyTokenId}`);
5947
6093
  }
6094
+ getConnect(orgId) {
6095
+ return this.request("GET", `/v1/orgs/${orgId}/connect`);
6096
+ }
6097
+ getConnectBrokerKey(orgId) {
6098
+ return this.request("GET", `/v1/orgs/${orgId}/connect/broker-key`);
6099
+ }
6100
+ createWorkload(orgId, input) {
6101
+ return this.request("POST", `/v1/orgs/${orgId}/connect/workloads`, input);
6102
+ }
6103
+ updateWorkload(orgId, id, input) {
6104
+ return this.request("PATCH", `/v1/orgs/${orgId}/connect/workloads/${encodeURIComponent(id)}`, input);
6105
+ }
6106
+ createConnectConnection(orgId, input) {
6107
+ return this.request("POST", `/v1/orgs/${orgId}/connect/connections`, input);
6108
+ }
6109
+ updateConnectConnection(orgId, id, input) {
6110
+ return this.request("PATCH", `/v1/orgs/${orgId}/connect/connections/${encodeURIComponent(id)}`, input);
6111
+ }
6112
+ createConnectGrant(orgId, input) {
6113
+ return this.request("POST", `/v1/orgs/${orgId}/connect/grants`, input);
6114
+ }
6115
+ startConnectOAuth(orgId, id) {
6116
+ return this.request("POST", `/v1/orgs/${orgId}/connect/connections/${encodeURIComponent(id)}/oauth/start`, {});
6117
+ }
6118
+ finishConnectOAuth(orgId, id, state, code) {
6119
+ return this.request("POST", `/v1/orgs/${orgId}/connect/connections/${encodeURIComponent(id)}/oauth/finish`, {
6120
+ state,
6121
+ code
6122
+ });
6123
+ }
6124
+ disconnectConnectOAuth(orgId, id) {
6125
+ return this.request("POST", `/v1/orgs/${orgId}/connect/connections/${encodeURIComponent(id)}/oauth/disconnect`, {});
6126
+ }
6127
+ revokeConnectGrant(orgId, id) {
6128
+ return this.request("DELETE", `/v1/orgs/${orgId}/connect/grants/${encodeURIComponent(id)}`);
6129
+ }
5948
6130
  listAgents(orgId) {
5949
6131
  return this.request("GET", `/v1/orgs/${orgId}/agents`);
5950
6132
  }
@@ -6352,6 +6534,10 @@ async function unauthenticatedPost(baseUrl, path, body, fetchImpl, client) {
6352
6534
  function startCliLogin(baseUrl, input, options = {}) {
6353
6535
  return unauthenticatedPost(baseUrl, "/v1/cli-login", input, options.fetch ?? ((...args) => fetch(...args)), options.client);
6354
6536
  }
6537
+ /** Create a workspace and a machine credential with no existing account. */
6538
+ function signup(baseUrl, input, options = {}) {
6539
+ return unauthenticatedPost(baseUrl, "/v1/signup", input, options.fetch ?? ((...args) => fetch(...args)), options.client);
6540
+ }
6355
6541
  /** Ask whether a human has authorized the request yet. */
6356
6542
  function pollCliLogin(baseUrl, code, options = {}) {
6357
6543
  const fetchImpl = options.fetch ?? ((...args) => fetch(...args));
@@ -6500,6 +6686,7 @@ function promptEnter(question) {
6500
6686
  });
6501
6687
  });
6502
6688
  }
6689
+ /** Ask a yes/no question on the terminal; anything but `y`/`yes` is no. */
6503
6690
  function askYesNo(question) {
6504
6691
  const rl = createInterface({
6505
6692
  input: process.stdin,
@@ -6650,6 +6837,23 @@ async function getPolicySigner(ctx) {
6650
6837
  jwk: policySignerJwk(privateKeyJwk)
6651
6838
  };
6652
6839
  }
6840
+ /**
6841
+ * The calling principal's public key JWK — what a freshly generated DEK is
6842
+ * wrapped to when creating an environment. Service tokens carry their private
6843
+ * key, so the public half is derived from it; users publish theirs at key
6844
+ * setup. Shared by `seekrit env create`, `seekrit init`, and the MCP server so
6845
+ * a token can create an environment everywhere, not only over MCP.
6846
+ */
6847
+ async function principalPublicKeyJwk(ctx) {
6848
+ if (ctx.auth.type === "bearer" && isServiceToken(ctx.auth.token)) {
6849
+ const { privateKey } = await parseServiceToken(ctx.auth.token);
6850
+ const { d: _d, key_ops: _ops, ext: _ext, ...pub } = await crypto.subtle.exportKey("jwk", privateKey);
6851
+ return JSON.stringify(pub);
6852
+ }
6853
+ const { user } = await ctx.client.me();
6854
+ if (!user.publicKeyJwk) fail("this user has no keypair yet — run `seekrit keys setup` first");
6855
+ return user.publicKeyJwk;
6856
+ }
6653
6857
  /** Recover one environment's DEK for the current principal. */
6654
6858
  async function getDek(ctx, orgId, envId) {
6655
6859
  const [{ wrappedDek }, privateKey] = await Promise.all([ctx.client.getMyEnvKey(orgId, envId), getPrivateKey(ctx)]);
@@ -6752,7 +6956,11 @@ async function boundEnvTarget(ctx, opts) {
6752
6956
  try {
6753
6957
  ({scope} = await ctx.client.resolve(opts.branch ? { branch: opts.branch } : {}));
6754
6958
  } catch (err) {
6755
- fail(`specify --env — this token has no environment of its own to fall back to (${err instanceof Error ? err.message : String(err)})`);
6959
+ if (err instanceof SeekritApiError && err.status < 500) {
6960
+ if (opts.env) return null;
6961
+ fail(`specify --env — this token has no environment of its own to fall back to (${err.message})`);
6962
+ }
6963
+ throw err;
6756
6964
  }
6757
6965
  const env = scope.branchOf ?? {
6758
6966
  envId: scope.envId,
@@ -6779,7 +6987,10 @@ async function boundEnvTarget(ctx, opts) {
6779
6987
  * it this way; this is the same rule, in one place, for both.
6780
6988
  */
6781
6989
  async function resolveEnvTarget(ctx, opts) {
6782
- if (!opts.app && !opts.group && isTokenAuth(ctx)) return boundEnvTarget(ctx, opts);
6990
+ if (!opts.app && !opts.group && isTokenAuth(ctx)) {
6991
+ const bound = await boundEnvTarget(ctx, opts);
6992
+ if (bound) return bound;
6993
+ }
6783
6994
  const org = await resolveOrg(ctx, opts.org);
6784
6995
  if (!opts.env) fail("specify --env");
6785
6996
  if (opts.group) {
@@ -8781,10 +8992,8 @@ function registerAppCommands(program) {
8781
8992
  const { apps } = await ctx.client.listApps(orgRef.id);
8782
8993
  const appRow = apps.find((a) => a.slug === options.app || a.id === options.app);
8783
8994
  if (!appRow) fail(`no app "${options.app}" in ${orgRef.slug}`);
8784
- const { user } = await ctx.client.me();
8785
- if (!user.publicKeyJwk) fail("run `seekrit keys setup` first");
8786
8995
  const dek = generateDek();
8787
- const wrappedDek = await wrapDek(dek, user.publicKeyJwk);
8996
+ const wrappedDek = await wrapDek(dek, await principalPublicKeyJwk(ctx));
8788
8997
  const recoveryWrappedDek = await recoveryWrapForNewEnv(ctx, orgRef.id, dek);
8789
8998
  const created = await ctx.client.createEnv(orgRef.id, appRow.id, {
8790
8999
  name: options.name,
@@ -11854,74 +12063,6 @@ function registerHoneyTokenCommands(program) {
11854
12063
  });
11855
12064
  }
11856
12065
  //#endregion
11857
- //#region src/logsink.ts
11858
- /** Collect repeated `--header Name: value` flags into a map. */
11859
- function collectHeader(value, acc = {}) {
11860
- const sep = value.indexOf(":");
11861
- if (sep <= 0) fail(`expected "Name: value", got "${value}"`);
11862
- acc[value.slice(0, sep).trim()] = value.slice(sep + 1).trim();
11863
- return acc;
11864
- }
11865
- /**
11866
- * The org's OTLP audit-log export — near-real-time shipping of every audit row
11867
- * to a customer SIEM. Header *values* are write-only: they are encrypted at
11868
- * rest and never echoed back, so `show` reports only the header names.
11869
- */
11870
- function registerLogSinkCommands(program) {
11871
- const sink = program.command("log-sink").description("stream the audit trail to your own OTLP collector (SIEM)");
11872
- sink.command("show", { isDefault: true }).description("show the configured log sink and its delivery health").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("--json", "print the raw API response").action(async (options) => {
11873
- const ctx = buildContext();
11874
- const ref = await resolveOrg(ctx, options.org);
11875
- const { sink: config } = await ctx.client.getLogSink(ref.id);
11876
- emit(options, { sink: config }, () => {
11877
- if (!config) {
11878
- console.error("no log sink configured — set one with `seekrit log-sink set`");
11879
- return;
11880
- }
11881
- printFields([
11882
- ["endpoint", config.endpoint],
11883
- ["enabled", config.enabled ? "yes" : "no"],
11884
- ["headers", config.headerNames.length > 0 ? config.headerNames.join(", ") : "none"],
11885
- ["last success", config.lastSuccessAt ?? "never"],
11886
- ["last attempt", config.lastAttemptAt ?? "never"],
11887
- ["last error", config.lastError ?? "none"]
11888
- ]);
11889
- });
11890
- });
11891
- sink.command("set <endpoint>").description("point the audit export at an OTLP/HTTP logs endpoint").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("--header <name: value>", "auth header to send (repeatable; values are write-only)", collectHeader).option("--clear-headers", "send no headers at all (drops the stored ones)").option("--disabled", "save the config but stop shipping").action(async (endpoint, options) => {
11892
- if (options.header && options.clearHeaders) fail("pass either --header or --clear-headers, not both");
11893
- const ctx = buildContext();
11894
- const ref = await resolveOrg(ctx, options.org);
11895
- const headers = options.clearHeaders ? {} : options.header;
11896
- const { sink: config } = await ctx.client.setLogSink(ref.id, {
11897
- endpoint,
11898
- headers,
11899
- enabled: !options.disabled
11900
- });
11901
- console.error(`log sink → ${config.endpoint} (${config.enabled ? "enabled" : "disabled"}) — test it with \`seekrit log-sink test\``);
11902
- });
11903
- sink.command("test").description("send a probe to the configured endpoint and report the result").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("--json", "print the raw API response").action(async (options) => {
11904
- const ctx = buildContext();
11905
- const ref = await resolveOrg(ctx, options.org);
11906
- const result = await ctx.client.testLogSink(ref.id);
11907
- emit(options, result, () => {
11908
- printFields([
11909
- ["result", result.ok ? "ok" : "failed"],
11910
- ["status", result.status],
11911
- ["error", result.error]
11912
- ]);
11913
- });
11914
- if (!result.ok) process.exitCode = 1;
11915
- });
11916
- sink.command("rm").alias("delete").description("stop exporting the audit trail and forget the endpoint").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("-y, --yes", "skip the confirmation prompt").action(async (options) => {
11917
- const ctx = buildContext();
11918
- const ref = await resolveOrg(ctx, options.org);
11919
- await confirmDestructive(options.yes, `Remove ${ref.slug}'s audit log export?`);
11920
- await ctx.client.deleteLogSink(ref.id);
11921
- console.error("log sink removed");
11922
- });
11923
- }
11924
- //#endregion
11925
12066
  //#region src/m2m.ts
11926
12067
  /**
11927
12068
  * Resolve M2M client credentials from (in order) the process environment, a
@@ -11997,6 +12138,228 @@ async function ensureM2mAdminToken(dotenvVars = {}) {
11997
12138
  process.env.SEEKRIT_TOKEN = token;
11998
12139
  return token;
11999
12140
  }
12141
+ /** Lowercase, hyphenated, at most 40 characters — what `slugSchema` accepts. */
12142
+ function slugify(name) {
12143
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/g, "") || "workspace";
12144
+ }
12145
+ /** `acme-storefront` → `Acme Storefront`: a slug-ish name as a human reads it. */
12146
+ function titleCase(name) {
12147
+ return name.split(/[^A-Za-z0-9]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
12148
+ }
12149
+ /**
12150
+ * The project's name, from whichever manifest the directory carries — the
12151
+ * package name without its npm scope, the Python or Rust package name, the last
12152
+ * segment of a Go module path — falling back to the directory's own name. The
12153
+ * manifests are read leniently (a regex, not a parser) because a malformed one
12154
+ * should cost the default, not the command.
12155
+ */
12156
+ function detectProjectName(dir = process.cwd()) {
12157
+ const read = (file) => {
12158
+ const path = join(dir, file);
12159
+ if (!existsSync(path)) return null;
12160
+ try {
12161
+ return readFileSync(path, "utf8");
12162
+ } catch {
12163
+ return null;
12164
+ }
12165
+ };
12166
+ const pkg = read("package.json");
12167
+ if (pkg) try {
12168
+ const name = JSON.parse(pkg).name;
12169
+ if (typeof name === "string" && name.trim()) return name.replace(/^@[^/]+\//, "");
12170
+ } catch {}
12171
+ const pyName = read("pyproject.toml")?.match(/^\[project\][^[]*?^name\s*=\s*"([^"]+)"/ms)?.[1];
12172
+ if (pyName) return pyName;
12173
+ const cargoName = read("Cargo.toml")?.match(/^\[package\][^[]*?^name\s*=\s*"([^"]+)"/ms)?.[1];
12174
+ if (cargoName) return cargoName;
12175
+ const goModule = read("go.mod")?.match(/^module\s+(\S+)/m)?.[1];
12176
+ if (goModule) return goModule.split("/").pop() ?? goModule;
12177
+ return basename(dir);
12178
+ }
12179
+ /**
12180
+ * What `init` would sign up as, from the flags and the directory. Pure, so the
12181
+ * naming rules are testable without a terminal or an API.
12182
+ */
12183
+ function planWorkspace(options, project) {
12184
+ const orgName = options.orgName?.trim() || titleCase(project);
12185
+ const orgSlug = options.org ?? slugify(options.orgName?.trim() || project);
12186
+ if (!slugSchema.safeParse(orgSlug).success) fail(`"${orgSlug}" is not a valid org slug (lowercase letters, numbers, and hyphens) — pass --org <slug>`);
12187
+ return {
12188
+ orgName,
12189
+ orgSlug
12190
+ };
12191
+ }
12192
+ /** Sign up, save the credential, mint the admin token, and return a context. */
12193
+ async function signupAndAuthenticate(options, project) {
12194
+ const { orgName, orgSlug } = planWorkspace(options, project);
12195
+ const apiUrl = options.apiUrl ?? process.env.SEEKRIT_API_URL ?? readGlobalConfig().apiUrl ?? "https://api.seekrit.dev";
12196
+ if (!Boolean(options.yes || options.orgName)) {
12197
+ const question = `No credentials found. Create a new workspace "${orgName}" (${orgSlug}) at ${apiUrl}?`;
12198
+ if (!process.stdin.isTTY) fail(`${question} Re-run with --yes, name it with --org-name "<the real project>", or sign in first with \`seekrit login\`.`);
12199
+ if (!await askYesNo(question)) fail("aborted — nothing was created");
12200
+ }
12201
+ let result;
12202
+ try {
12203
+ result = await signup(apiUrl, {
12204
+ orgName,
12205
+ orgSlug
12206
+ }, { client: `cli/${version}` });
12207
+ } catch (err) {
12208
+ if (err instanceof SeekritApiError) fail(`signup failed: ${err.message}`);
12209
+ throw err;
12210
+ }
12211
+ writeGlobalConfig({
12212
+ clientId: result.m2m.clientId,
12213
+ clientSecret: result.m2m.clientSecret,
12214
+ ...options.apiUrl ? { apiUrl: options.apiUrl } : {}
12215
+ });
12216
+ console.error(`created workspace ${result.org.slug} (${result.org.name}) — machine credential saved to ~/.config/seekrit/config.json`);
12217
+ await ensureM2mAdminToken();
12218
+ return {
12219
+ ctx: buildContext(),
12220
+ orgSlug: result.org.slug
12221
+ };
12222
+ }
12223
+ async function runInit(options) {
12224
+ const project = detectProjectName();
12225
+ let ctx = tryBuildContext();
12226
+ let createdOrgSlug;
12227
+ if (!ctx) ({ctx, orgSlug: createdOrgSlug} = await signupAndAuthenticate(options, project));
12228
+ const orgRef = await resolveOrg(ctx, options.org ?? createdOrgSlug);
12229
+ const appSlug = options.app ?? slugify(project);
12230
+ const { apps } = await ctx.client.listApps(orgRef.id);
12231
+ let app = apps.find((a) => a.slug === appSlug || a.id === appSlug);
12232
+ if (!app) {
12233
+ if (!slugSchema.safeParse(appSlug).success) fail(`"${appSlug}" is not a valid app slug — pass --app <slug>`);
12234
+ ({app} = await ctx.client.createApp(orgRef.id, {
12235
+ name: options.app ? titleCase(options.app) : titleCase(project),
12236
+ slug: appSlug
12237
+ }));
12238
+ console.error(`created app ${app.slug}`);
12239
+ }
12240
+ const { environments } = await ctx.client.listEnvs(orgRef.id, app.id);
12241
+ const envSlug = options.env ?? "development";
12242
+ let env = environments.find((e) => e.slug === envSlug || e.id === envSlug);
12243
+ if (!env && (options.env || environments.length === 0)) {
12244
+ if (!slugSchema.safeParse(envSlug).success) fail(`"${envSlug}" is not a valid environment slug — pass --env <slug>`);
12245
+ const dek = generateDek();
12246
+ const wrappedDek = await wrapDek(dek, await principalPublicKeyJwk(ctx));
12247
+ const recoveryWrappedDek = await recoveryWrapForNewEnv(ctx, orgRef.id, dek);
12248
+ ({environment: env} = await ctx.client.createEnv(orgRef.id, app.id, {
12249
+ name: titleCase(envSlug),
12250
+ slug: envSlug,
12251
+ wrappedDek,
12252
+ recoveryWrappedDek
12253
+ }));
12254
+ console.error(`created environment ${app.slug}/${env.slug} (data key generated locally)`);
12255
+ }
12256
+ const path = writeProjectConfig({
12257
+ org: orgRef.slug,
12258
+ app: app.slug
12259
+ });
12260
+ console.error(`linked ${orgRef.slug}/${app.slug} → ${path}`);
12261
+ if (options.fromEnv) {
12262
+ if (!env) fail(`--from-env needs an environment — pass --env <slug>`);
12263
+ if (!existsSync(options.fromEnv)) fail(`no such file: ${options.fromEnv}`);
12264
+ const entries = parseDotenv(readFileSync(options.fromEnv, "utf8"));
12265
+ const names = Object.keys(entries);
12266
+ if (names.length === 0) fail(`no variables found in ${options.fromEnv}`);
12267
+ const invalid = names.filter((name) => !secretNameSchema.safeParse(name).success);
12268
+ if (invalid.length > 0) fail(`invalid secret name(s): ${invalid.join(", ")} (must match [A-Za-z_][A-Za-z0-9_]*)`);
12269
+ const result = await importSecrets(ctx, orgRef.id, env.id, entries);
12270
+ if (result.queued.length > 0) console.error(`queued ${result.queued.length} change request(s) for ${app.slug}/${env.slug} — nothing is stored until a human approves them`);
12271
+ else console.error(`imported ${names.length} secret(s) from ${options.fromEnv} into ${app.slug}/${env.slug} — delete the file now; the values live here`);
12272
+ }
12273
+ if (options.invite) {
12274
+ const { invite } = await ctx.client.createInvite(orgRef.id, {
12275
+ email: options.invite,
12276
+ role: "admin"
12277
+ });
12278
+ console.error(`invited ${invite.email} as admin — they claim ${orgRef.slug} on first sign-in at the dashboard. Once they have set up keys, grant them the environment: seekrit grant --app ${app.slug} --env ${env?.slug ?? "<env>"} --user ${invite.email}`);
12279
+ }
12280
+ const target = env ? `${app.slug}/${env.slug}` : app.slug;
12281
+ const envFlag = env ? ` --env ${env.slug}` : " --env <slug>";
12282
+ console.error([
12283
+ "",
12284
+ `ready: ${orgRef.slug}/${target}`,
12285
+ ` seekrit secrets set DATABASE_URL${envFlag} # store a value (prompted, never echoed)`,
12286
+ ` seekrit run${envFlag} -- <your command> # inject secrets into a process`,
12287
+ ...options.invite ? [] : [` seekrit init --invite you@example.com # hand the workspace to a human`]
12288
+ ].join("\n"));
12289
+ }
12290
+ function registerInitCommand(program) {
12291
+ program.command("init").description("set this directory up: sign up if needed, create the app and a development environment, write seekrit.json").option("--org <slug>", "organization slug (an existing org, or the one to create)").option("--org-name <name>", "display name for a new workspace — the real project or company (e.g. \"Acme Storefront\")").option("--app <slug>", "application slug (defaults to the project name; created if missing)").option("--env <slug>", "environment to use or create (a new app gets `development`)").option("--from-env <file>", "import a .env file into that environment").option("--invite <email>", "invite a human as admin so they can claim the workspace").option("--api-url <url>", "API base URL").option("-y, --yes", "create a workspace without asking").action(async (options) => {
12292
+ await runInit(options);
12293
+ });
12294
+ }
12295
+ //#endregion
12296
+ //#region src/logsink.ts
12297
+ /** Collect repeated `--header Name: value` flags into a map. */
12298
+ function collectHeader(value, acc = {}) {
12299
+ const sep = value.indexOf(":");
12300
+ if (sep <= 0) fail(`expected "Name: value", got "${value}"`);
12301
+ acc[value.slice(0, sep).trim()] = value.slice(sep + 1).trim();
12302
+ return acc;
12303
+ }
12304
+ /**
12305
+ * The org's OTLP audit-log export — near-real-time shipping of every audit row
12306
+ * to a customer SIEM. Header *values* are write-only: they are encrypted at
12307
+ * rest and never echoed back, so `show` reports only the header names.
12308
+ */
12309
+ function registerLogSinkCommands(program) {
12310
+ const sink = program.command("log-sink").description("stream the audit trail to your own OTLP collector (SIEM)");
12311
+ sink.command("show", { isDefault: true }).description("show the configured log sink and its delivery health").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("--json", "print the raw API response").action(async (options) => {
12312
+ const ctx = buildContext();
12313
+ const ref = await resolveOrg(ctx, options.org);
12314
+ const { sink: config } = await ctx.client.getLogSink(ref.id);
12315
+ emit(options, { sink: config }, () => {
12316
+ if (!config) {
12317
+ console.error("no log sink configured — set one with `seekrit log-sink set`");
12318
+ return;
12319
+ }
12320
+ printFields([
12321
+ ["endpoint", config.endpoint],
12322
+ ["enabled", config.enabled ? "yes" : "no"],
12323
+ ["headers", config.headerNames.length > 0 ? config.headerNames.join(", ") : "none"],
12324
+ ["last success", config.lastSuccessAt ?? "never"],
12325
+ ["last attempt", config.lastAttemptAt ?? "never"],
12326
+ ["last error", config.lastError ?? "none"]
12327
+ ]);
12328
+ });
12329
+ });
12330
+ sink.command("set <endpoint>").description("point the audit export at an OTLP/HTTP logs endpoint").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("--header <name: value>", "auth header to send (repeatable; values are write-only)", collectHeader).option("--clear-headers", "send no headers at all (drops the stored ones)").option("--disabled", "save the config but stop shipping").action(async (endpoint, options) => {
12331
+ if (options.header && options.clearHeaders) fail("pass either --header or --clear-headers, not both");
12332
+ const ctx = buildContext();
12333
+ const ref = await resolveOrg(ctx, options.org);
12334
+ const headers = options.clearHeaders ? {} : options.header;
12335
+ const { sink: config } = await ctx.client.setLogSink(ref.id, {
12336
+ endpoint,
12337
+ headers,
12338
+ enabled: !options.disabled
12339
+ });
12340
+ console.error(`log sink → ${config.endpoint} (${config.enabled ? "enabled" : "disabled"}) — test it with \`seekrit log-sink test\``);
12341
+ });
12342
+ sink.command("test").description("send a probe to the configured endpoint and report the result").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("--json", "print the raw API response").action(async (options) => {
12343
+ const ctx = buildContext();
12344
+ const ref = await resolveOrg(ctx, options.org);
12345
+ const result = await ctx.client.testLogSink(ref.id);
12346
+ emit(options, result, () => {
12347
+ printFields([
12348
+ ["result", result.ok ? "ok" : "failed"],
12349
+ ["status", result.status],
12350
+ ["error", result.error]
12351
+ ]);
12352
+ });
12353
+ if (!result.ok) process.exitCode = 1;
12354
+ });
12355
+ sink.command("rm").alias("delete").description("stop exporting the audit trail and forget the endpoint").option("--org <slug>", "organization slug (defaults to seekrit.json, or your only org)").option("-y, --yes", "skip the confirmation prompt").action(async (options) => {
12356
+ const ctx = buildContext();
12357
+ const ref = await resolveOrg(ctx, options.org);
12358
+ await confirmDestructive(options.yes, `Remove ${ref.slug}'s audit log export?`);
12359
+ await ctx.client.deleteLogSink(ref.id);
12360
+ console.error("log sink removed");
12361
+ });
12362
+ }
12000
12363
  //#endregion
12001
12364
  //#region src/mongodb.ts
12002
12365
  /**
@@ -12352,7 +12715,7 @@ function collect$5(value, acc) {
12352
12715
  * release-please-config.json), so the pin follows the crate without anyone
12353
12716
  * remembering to move it.
12354
12717
  */
12355
- const PROXY_VERSION = "0.12.4";
12718
+ const PROXY_VERSION = "0.12.7";
12356
12719
  const BIN = "seekrit-proxy";
12357
12720
  /**
12358
12721
  * Host → Rust target triple.
@@ -12388,7 +12751,7 @@ function versionPrefix(version) {
12388
12751
  return version.startsWith("v") ? version : `v${version}`;
12389
12752
  }
12390
12753
  function resolveVersion(explicit) {
12391
- return explicit ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.12.4";
12754
+ return explicit ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.12.7";
12392
12755
  }
12393
12756
  function resolveBaseUrl(explicit) {
12394
12757
  return (explicit ?? process.env.SEEKRIT_PROXY_BASE_URL ?? "https://proxy.seekrit.dev").replace(/\/+$/, "");
@@ -12546,7 +12909,7 @@ const PLUGIN_BIN = "seekrit-nomad-secrets";
12546
12909
  * entry in release-please-config.json), so the pin follows the crate without
12547
12910
  * anyone remembering to move it — the same arrangement `PROXY_VERSION` has.
12548
12911
  */
12549
- const NOMAD_PLUGIN_VERSION = "0.2.2";
12912
+ const NOMAD_PLUGIN_VERSION = "0.2.5";
12550
12913
  /** Public artifact host. Mirrors GitHub Releases; public, no egress fees. */
12551
12914
  const DEFAULT_BASE_URL = "https://nomad.seekrit.dev";
12552
12915
  /** Where the plugin reads its node config, matching `config::DEFAULT_CONFIG_PATH`. */
@@ -12633,7 +12996,7 @@ async function fetchBytes(url) {
12633
12996
  * let Nomad fingerprint a half-written executable.
12634
12997
  */
12635
12998
  async function installPlugin(pluginDir, options = {}) {
12636
- const version = options.version ?? "0.2.2";
12999
+ const version = options.version ?? "0.2.5";
12637
13000
  const baseUrl = (options.baseUrl ?? "https://nomad.seekrit.dev").replace(/\/+$/, "");
12638
13001
  const { target, exe } = detectTarget();
12639
13002
  if (exe !== "") fail("Nomad secret provider plugins are not supported on Windows clients");
@@ -14338,10 +14701,10 @@ Next:
14338
14701
  });
14339
14702
  console.log(path);
14340
14703
  const { target } = detectTarget();
14341
- process.stderr.write(`seekrit-proxy ${versionPrefix(options.proxyVersion ?? "0.12.4")} (${target})\n`);
14704
+ process.stderr.write(`seekrit-proxy ${versionPrefix(options.proxyVersion ?? "0.12.7")} (${target})\n`);
14342
14705
  });
14343
14706
  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) => {
14344
- const version = options.proxyVersion ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.12.4";
14707
+ const version = options.proxyVersion ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.12.7";
14345
14708
  const override = process.env.SEEKRIT_PROXY_BIN;
14346
14709
  const { target, exe } = detectTarget();
14347
14710
  const path = override ?? proxyBinaryPath(version, target, exe);
@@ -14400,7 +14763,7 @@ Next:
14400
14763
  process.stdout.write(renderComposeSnippet(plan, {
14401
14764
  service: options.service,
14402
14765
  workload: options.workload,
14403
- image: options.image ?? `seekritdev/proxy:0.12.4`,
14766
+ image: options.image ?? `seekritdev/proxy:0.12.7`,
14404
14767
  publish: Boolean(options.publish)
14405
14768
  }));
14406
14769
  });
@@ -15863,18 +16226,7 @@ program.command("keys").description("manage your encryption keys").command("setu
15863
16226
  });
15864
16227
  console.error("keys generated and uploaded — your passphrase never leaves this machine");
15865
16228
  });
15866
- program.command("init").description("link this directory to an org/app (environment comes from the token)").requiredOption("--org <slug>", "organization slug").requiredOption("--app <slug>", "application slug").action(async (options) => {
15867
- const ctx = buildContext();
15868
- const org = await resolveOrg(ctx, options.org);
15869
- const { apps } = await ctx.client.listApps(org.id);
15870
- const app = apps.find((a) => a.slug === options.app || a.id === options.app);
15871
- if (!app) fail(`no app "${options.app}" in ${org.slug}`);
15872
- const path = writeProjectConfig({
15873
- org: org.slug,
15874
- app: app.slug
15875
- });
15876
- console.error(`linked ${org.slug}/${app.slug} → ${path} (environment is selected by the service token at runtime)`);
15877
- });
16229
+ registerInitCommand(program);
15878
16230
  registerOrgCommands(program);
15879
16231
  registerAppCommands(program);
15880
16232
  registerGroupCommands(program);
@@ -16367,7 +16719,7 @@ registerKmsCommands(program);
16367
16719
  registerRotationCommands(program);
16368
16720
  registerRecoveryCommands(program);
16369
16721
  program.command("mcp").description("run an MCP server over stdio so AI agents can drive seekrit").action(async () => {
16370
- const { runMcpServer } = await import("./mcp-ZYu1Q6Ft.js");
16722
+ const { runMcpServer } = await import("./mcp-D7rsPeBQ.js");
16371
16723
  await runMcpServer();
16372
16724
  });
16373
16725
  registerAuditCommands(program);
@@ -16382,4 +16734,4 @@ program.parseAsync(argv).catch((err) => {
16382
16734
  fail(describeError(err));
16383
16735
  });
16384
16736
  //#endregion
16385
- export { signatureKeyRef as A, generateDek as B, createServiceToken as C, importSigningKey as D, generateSigningKeyMaterial as E, generateEncryptKeyMaterial as F, parseBranchTtl as H, kmsBlobKeyRef as I, kmsDecrypt as L, generatePostgresCredential as M, generateMysqlCredential as N, importVerifyingKey as O, generateDataKey as P, kmsEncrypt as R, version as S, parseServiceToken as T, toBase64 as V, getDek as _, kmsResolveRecipient as a, setFailThrows as b, fetchDecryptedSecrets as c, resolveApp as d, resolveAppEnv as f, resolveOrg as g, resolveGroup as h, kmsResolveKey as i, verifyMessage as j, signMessage as k, fetchDecryptedVersion as l, resolveEnvTarget as m, kmsCallerIdentity as n, syncGrantsForGroupLink as o, resolveBranch as p, kmsRecoverMaterial as r, encryptAndSetSecret as s, ensureM2mAdminToken as t, materializeEnv as u, isTokenAuth as v, isServiceToken as w, writeProjectConfig as x, tryBuildContext as y, wrapDek as z };
16737
+ export { signatureKeyRef as A, generateDek as B, version as C, importSigningKey as D, generateSigningKeyMaterial as E, generateEncryptKeyMaterial as F, parseBranchTtl as H, kmsBlobKeyRef as I, kmsDecrypt as L, generatePostgresCredential as M, generateMysqlCredential as N, importVerifyingKey as O, generateDataKey as P, kmsEncrypt as R, writeProjectConfig as S, parseServiceToken as T, toBase64 as V, getDek as _, kmsResolveRecipient as a, tryBuildContext as b, fetchDecryptedSecrets as c, resolveApp as d, resolveAppEnv as f, resolveOrg as g, resolveGroup as h, kmsResolveKey as i, verifyMessage as j, signMessage as k, fetchDecryptedVersion as l, resolveEnvTarget as m, kmsCallerIdentity as n, syncGrantsForGroupLink as o, resolveBranch as p, kmsRecoverMaterial as r, encryptAndSetSecret as s, ensureM2mAdminToken as t, materializeEnv as u, isTokenAuth as v, createServiceToken as w, setFailThrows as x, principalPublicKeyJwk as y, wrapDek as z };
@@ -1,4 +1,4 @@
1
- import { A as signatureKeyRef, B as generateDek, C as createServiceToken, D as importSigningKey, E as generateSigningKeyMaterial, F as generateEncryptKeyMaterial, H as parseBranchTtl, I as kmsBlobKeyRef, L as kmsDecrypt, M as generatePostgresCredential, N as generateMysqlCredential, O as importVerifyingKey, P as generateDataKey, R as kmsEncrypt, S as version, T as parseServiceToken, V as toBase64, _ as getDek, a as kmsResolveRecipient, b as setFailThrows, c as fetchDecryptedSecrets, d as resolveApp, f as resolveAppEnv, g as resolveOrg, h as resolveGroup, i as kmsResolveKey, j as verifyMessage, k as signMessage, l as fetchDecryptedVersion, m as resolveEnvTarget, n as kmsCallerIdentity, o as syncGrantsForGroupLink, p as resolveBranch, r as kmsRecoverMaterial, s as encryptAndSetSecret, t as ensureM2mAdminToken, u as materializeEnv, v as isTokenAuth, w as isServiceToken, x as writeProjectConfig, y as tryBuildContext, z as wrapDek } from "./index.js";
1
+ import { A as signatureKeyRef, B as generateDek, C as version, D as importSigningKey, E as generateSigningKeyMaterial, F as generateEncryptKeyMaterial, H as parseBranchTtl, I as kmsBlobKeyRef, L as kmsDecrypt, M as generatePostgresCredential, N as generateMysqlCredential, O as importVerifyingKey, P as generateDataKey, R as kmsEncrypt, S as writeProjectConfig, T as parseServiceToken, V as toBase64, _ as getDek, a as kmsResolveRecipient, b as tryBuildContext, c as fetchDecryptedSecrets, d as resolveApp, f as resolveAppEnv, g as resolveOrg, h as resolveGroup, i as kmsResolveKey, j as verifyMessage, k as signMessage, l as fetchDecryptedVersion, m as resolveEnvTarget, n as kmsCallerIdentity, o as syncGrantsForGroupLink, p as resolveBranch, r as kmsRecoverMaterial, s as encryptAndSetSecret, t as ensureM2mAdminToken, u as materializeEnv, v as isTokenAuth, w as createServiceToken, x as setFailThrows, y as principalPublicKeyJwk, z as wrapDek } from "./index.js";
2
2
  import { spawn } from "node:child_process";
3
3
  import { z } from "zod";
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -50,9 +50,18 @@ function serverInstructions() {
50
50
  "Auth comes from SEEKRIT_TOKEN (a skt_… token), SEEKRIT_CLIENT_ID +",
51
51
  "SEEKRIT_CLIENT_SECRET (machine credentials — an admin token is minted and",
52
52
  "cached automatically on first use), or the config saved by `seekrit login`.",
53
+ "No credential at all? `npx -y @seekrit/cli init --org-name \"<the real project>\" --yes`",
54
+ "signs up, saves the credential, and creates an app with a development environment.",
53
55
  "Decrypting under a user session (not a token) additionally needs",
54
56
  "SEEKRIT_PASSPHRASE set in this server's env — there's no TTY to prompt on.",
55
57
  "",
58
+ "NOT THE TOOL WHEN: the value is a throwaway for one local experiment (a shell",
59
+ "variable is fine — say so); the runtime already injects secrets natively and the",
60
+ "team uses it (offer seekrit as the source that syncs into it, not a second copy);",
61
+ "the policy forbids any SaaS holding even ciphertext; or what's being stored is",
62
+ "user data rather than configuration. An honest \"you don't need this here\" beats",
63
+ "an install.",
64
+ "",
56
65
  "Model: org → apps/groups → environments → secrets. Call `whoami` first, then",
57
66
  "`get_started` for the recommended end-to-end recipe. Prefer `run_command` over",
58
67
  "`get_secret reveal:true` — it injects secrets into a subprocess so plaintext",
@@ -119,21 +128,6 @@ function getCtx() {
119
128
  function ensureDecryptable(ctx) {
120
129
  if (!isTokenAuth(ctx) && !process.env.SEEKRIT_PASSPHRASE) throw new Error("this operation decrypts data; set SEEKRIT_PASSPHRASE in the MCP server env, or use a service token (SEEKRIT_TOKEN)");
121
130
  }
122
- /**
123
- * The calling principal's public key JWK — needed to wrap a freshly generated
124
- * DEK when creating an environment. Service tokens carry their private key, so
125
- * we derive the public half from it; users publish theirs at key setup.
126
- */
127
- async function principalPublicKeyJwk(ctx) {
128
- if (ctx.auth.type === "bearer" && isServiceToken(ctx.auth.token)) {
129
- const { privateKey } = await parseServiceToken(ctx.auth.token);
130
- const { d: _d, key_ops: _ops, ext: _ext, ...pub } = await crypto.subtle.exportKey("jwk", privateKey);
131
- return JSON.stringify(pub);
132
- }
133
- const { user } = await ctx.client.me();
134
- if (!user.publicKeyJwk) throw new Error("this user has no keypair yet — run `seekrit keys setup` first");
135
- return user.publicKeyJwk;
136
- }
137
131
  /** Run a child command with `env` and capture its output (values never returned). */
138
132
  function runChild(cmd, args, env, cwd) {
139
133
  return new Promise((resolve, reject) => {
package/package.json CHANGED
@@ -1,7 +1,23 @@
1
1
  {
2
2
  "name": "@seekrit/cli",
3
- "version": "1.9.3",
3
+ "version": "1.9.5",
4
4
  "description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
5
+ "homepage": "https://seekrit.dev/docs/guides/cli",
6
+ "keywords": [
7
+ "seekrit",
8
+ "secrets",
9
+ "secrets-manager",
10
+ "dotenv",
11
+ "env",
12
+ "credentials",
13
+ "api-keys",
14
+ "zero-knowledge",
15
+ "end-to-end-encryption",
16
+ "mcp",
17
+ "ai-agents",
18
+ "credential-broker",
19
+ "cli"
20
+ ],
5
21
  "type": "module",
6
22
  "publishConfig": {
7
23
  "access": "public"
@@ -22,14 +38,14 @@
22
38
  "node": ">=20"
23
39
  },
24
40
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "^1.31.0",
41
+ "@modelcontextprotocol/sdk": "^1.32.1",
26
42
  "commander": "^15.0.0",
27
- "zod": "^4.6.5"
43
+ "zod": "^4.6.7"
28
44
  },
29
45
  "devDependencies": {
30
- "@types/node": "^26.6.3",
31
- "tsdown": "^0.23.0",
32
- "vitest": "^5.0.3",
46
+ "@types/node": "^26.6.5",
47
+ "tsdown": "^0.23.1",
48
+ "vitest": "^5.0.5",
33
49
  "@seekrit/api-client": "0.0.1",
34
50
  "@seekrit/core": "0.0.1",
35
51
  "@seekrit/crypto": "0.0.1"