@seekrit/cli 1.9.4 → 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 +6 -4
- package/dist/index.js +448 -96
- package/dist/{mcp-ZYu1Q6Ft.js → mcp-D7rsPeBQ.js} +10 -16
- package/package.json +21 -5
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.
|
|
34
|
-
#
|
|
35
|
-
|
|
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` |
|
|
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.
|
|
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
|
-
|
|
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))
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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-
|
|
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,
|
|
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
|
|
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
|
+
"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"
|
|
@@ -24,12 +40,12 @@
|
|
|
24
40
|
"dependencies": {
|
|
25
41
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
26
42
|
"commander": "^15.0.0",
|
|
27
|
-
"zod": "^4.6.
|
|
43
|
+
"zod": "^4.6.7"
|
|
28
44
|
},
|
|
29
45
|
"devDependencies": {
|
|
30
|
-
"@types/node": "^26.6.
|
|
31
|
-
"tsdown": "^0.23.
|
|
32
|
-
"vitest": "^5.0.
|
|
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"
|