@seekrit/cli 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +407 -220
- package/dist/{mcp-DZoBFmDF.js → mcp-Clul2n4l.js} +17 -27
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -25,11 +25,15 @@ const policyMethodSchema = z.string().trim().toUpperCase().regex(/^[A-Z]{3,10}$/
|
|
|
25
25
|
*/
|
|
26
26
|
const policyPathSchema = z.string().trim().min(1).max(512).startsWith("/", "path pattern must start with /").refine((p) => !p.includes("?"), { message: "path patterns match the path only, not the query" });
|
|
27
27
|
const policySecretNameSchema = z.string().trim().regex(/^[A-Za-z0-9_]+$/, "secret names are letters, digits, and underscores");
|
|
28
|
+
/** A TCP port. 0 is refused: it never matches a connection, so a rule naming it is a mistake. */
|
|
29
|
+
const policyPortSchema = z.number().int().min(1).max(65535);
|
|
28
30
|
const agentPolicyRuleSchema = z.object({
|
|
29
31
|
host: policyHostSchema,
|
|
30
32
|
methods: z.array(policyMethodSchema).max(16).default([]),
|
|
31
33
|
paths: z.array(policyPathSchema).max(64).default([]),
|
|
32
34
|
allow: z.array(policySecretNameSchema).max(64).default([]),
|
|
35
|
+
https_ports: z.array(policyPortSchema).max(32).optional(),
|
|
36
|
+
http_ports: z.array(policyPortSchema).max(32).optional(),
|
|
33
37
|
label: z.string().trim().max(120).optional()
|
|
34
38
|
});
|
|
35
39
|
z.object({
|
|
@@ -48,11 +52,65 @@ z.object({
|
|
|
48
52
|
});
|
|
49
53
|
z.object({
|
|
50
54
|
host: policyHostSchema,
|
|
55
|
+
/** How the agent reaches the host. Omit for a destination the operator fixed. */
|
|
56
|
+
transport: z.object({
|
|
57
|
+
scheme: z.enum(["https", "http"]),
|
|
58
|
+
port: policyPortSchema
|
|
59
|
+
}).optional(),
|
|
51
60
|
method: policyMethodSchema,
|
|
52
61
|
path: z.string().trim().min(1).max(2048),
|
|
53
62
|
secret: policySecretNameSchema.optional()
|
|
54
63
|
});
|
|
55
64
|
/**
|
|
65
|
+
* The ports a rule covers, with the default applied.
|
|
66
|
+
*
|
|
67
|
+
* **Absent does not mean any here**, unlike methods and paths. A bare hostname
|
|
68
|
+
* reads as `https://host`, so a rule naming no ports covers HTTPS on 443 and
|
|
69
|
+
* nothing else: another port on the same name is another service (often an
|
|
70
|
+
* admin one), and cleartext puts the substituted credential on the wire. The
|
|
71
|
+
* default applies only when *neither* list says anything — `http_ports: [8080]`
|
|
72
|
+
* alone means exactly that, not that plus 443.
|
|
73
|
+
*/
|
|
74
|
+
function policyRulePorts(rule) {
|
|
75
|
+
const norm = (ports) => [...new Set(ports ?? [])].sort((a, b) => a - b);
|
|
76
|
+
const https = norm(rule.https_ports);
|
|
77
|
+
const http = norm(rule.http_ports);
|
|
78
|
+
if (https.length === 0 && http.length === 0) return {
|
|
79
|
+
https: [443],
|
|
80
|
+
http: []
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
https,
|
|
84
|
+
http
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** True when a rule covers HTTPS on 443 and nothing else — all v1 can say. */
|
|
88
|
+
function isDefaultPolicyPorts(rule) {
|
|
89
|
+
const { https, http } = policyRulePorts(rule);
|
|
90
|
+
return http.length === 0 && https.length === 1 && https[0] === 443;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* `https:443,8443 http:5000`, or `null` for the default — for the editor's
|
|
94
|
+
* collapsed rows, the publish diff, and the CLI, which only mention ports when
|
|
95
|
+
* a rule says something about them.
|
|
96
|
+
*/
|
|
97
|
+
function describePolicyPorts(rule) {
|
|
98
|
+
if (isDefaultPolicyPorts(rule)) return null;
|
|
99
|
+
const { https, http } = policyRulePorts(rule);
|
|
100
|
+
const parts = [];
|
|
101
|
+
if (https.length > 0) parts.push(`https:${https.join(",")}`);
|
|
102
|
+
if (http.length > 0) parts.push(`http:${http.join(",")}`);
|
|
103
|
+
return parts.join(" ");
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The bundle version to publish these rules as: v1 unless a rule needs ports v1
|
|
107
|
+
* cannot express. Keeping the old version wherever it suffices is what lets a
|
|
108
|
+
* fleet upgrade its proxies after the dashboard rather than in lockstep.
|
|
109
|
+
*/
|
|
110
|
+
function policyBundleVersionFor(rules) {
|
|
111
|
+
return rules.every(isDefaultPolicyPorts) ? 1 : 2;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
56
114
|
* Serialize a bundle to the exact bytes that get signed and transported.
|
|
57
115
|
*
|
|
58
116
|
* Explicit field order rather than a generic sorted-key walk: the field list is
|
|
@@ -61,6 +119,10 @@ z.object({
|
|
|
61
119
|
* arrays are kept, since their emptiness is meaningful (any/any/none).
|
|
62
120
|
*/
|
|
63
121
|
function canonicalizeAgentPolicy(bundle) {
|
|
122
|
+
if (bundle.v < 2) {
|
|
123
|
+
const index = bundle.rules.findIndex((rule) => !isDefaultPolicyPorts(rule));
|
|
124
|
+
if (index !== -1) throw new AgentPolicyError(`rule ${index + 1} names ports, which need a v2 bundle (see policyBundleVersionFor)`);
|
|
125
|
+
}
|
|
64
126
|
const rules = bundle.rules.map((rule) => {
|
|
65
127
|
const out = {
|
|
66
128
|
host: rule.host.trim().toLowerCase(),
|
|
@@ -68,6 +130,11 @@ function canonicalizeAgentPolicy(bundle) {
|
|
|
68
130
|
paths: rule.paths.map((p) => p.trim()),
|
|
69
131
|
allow: [...rule.allow].sort()
|
|
70
132
|
};
|
|
133
|
+
if (bundle.v >= 2) {
|
|
134
|
+
const ports = policyRulePorts(rule);
|
|
135
|
+
out.https_ports = ports.https;
|
|
136
|
+
out.http_ports = ports.http;
|
|
137
|
+
}
|
|
71
138
|
if (rule.label?.trim()) out.label = rule.label.trim();
|
|
72
139
|
return out;
|
|
73
140
|
});
|
|
@@ -158,8 +225,13 @@ function parseAgentPolicyUnverified(envelope) {
|
|
|
158
225
|
} catch (e) {
|
|
159
226
|
throw new AgentPolicyError(`policy bundle body is unreadable: ${e.message}`);
|
|
160
227
|
}
|
|
161
|
-
if (body?.v !== 1) throw new AgentPolicyError(`unsupported policy bundle version ${String(body?.v)}`);
|
|
228
|
+
if (typeof body?.v !== "number" || body.v < 1 || body.v > 2) throw new AgentPolicyError(`unsupported policy bundle version ${String(body?.v)}`);
|
|
162
229
|
if (!Array.isArray(body.rules) || !body.signer?.jwk) throw new AgentPolicyError("policy bundle is missing rules or signer");
|
|
230
|
+
body.rules.forEach((rule, i) => {
|
|
231
|
+
const carriesPorts = rule.https_ports !== void 0 || rule.http_ports !== void 0;
|
|
232
|
+
if (body.v < 2 && carriesPorts) throw new AgentPolicyError(`policy bundle v${body.v} rule ${i + 1} carries https_ports/http_ports, which need a v2 bundle`);
|
|
233
|
+
for (const port of [...rule.https_ports ?? [], ...rule.http_ports ?? []]) if (!policyPortSchema.safeParse(port).success) throw new AgentPolicyError(`policy bundle rule ${i + 1} names an invalid port ${port}`);
|
|
234
|
+
});
|
|
163
235
|
return body;
|
|
164
236
|
}
|
|
165
237
|
/**
|
|
@@ -209,6 +281,11 @@ function ruleCoversPath(rule, path) {
|
|
|
209
281
|
if (rule.paths.length === 0) return true;
|
|
210
282
|
return rule.paths.some((p) => matchPolicyPath(p, path));
|
|
211
283
|
}
|
|
284
|
+
function ruleReaches(rule, transport) {
|
|
285
|
+
if (!transport) return true;
|
|
286
|
+
const ports = policyRulePorts(rule);
|
|
287
|
+
return (transport.scheme === "https" ? ports.https : ports.http).includes(transport.port);
|
|
288
|
+
}
|
|
212
289
|
/**
|
|
213
290
|
* Decide a request against an ordered rule set, first match wins — the same
|
|
214
291
|
* evaluation `RuleSet::decide` performs in the proxy, including which rule
|
|
@@ -218,11 +295,14 @@ function ruleCoversPath(rule, path) {
|
|
|
218
295
|
function evaluatePolicy(rules, query) {
|
|
219
296
|
const host = query.host.trim().toLowerCase();
|
|
220
297
|
let hostMatched = false;
|
|
298
|
+
let portMatched = false;
|
|
221
299
|
let pathMatchedIndex = null;
|
|
222
300
|
for (let i = 0; i < rules.length; i++) {
|
|
223
301
|
const rule = rules[i];
|
|
224
302
|
if (rule.host.trim().toLowerCase() !== host) continue;
|
|
225
303
|
hostMatched = true;
|
|
304
|
+
if (!ruleReaches(rule, query.transport)) continue;
|
|
305
|
+
portMatched = true;
|
|
226
306
|
const pathsOk = ruleCoversPath(rule, query.path);
|
|
227
307
|
if (pathsOk && ruleCoversMethod(rule, query.method)) {
|
|
228
308
|
if (query.secret !== void 0 && !rule.allow.includes(query.secret)) return {
|
|
@@ -240,8 +320,12 @@ function evaluatePolicy(rules, query) {
|
|
|
240
320
|
decision: "method_not_allowed",
|
|
241
321
|
ruleIndex: pathMatchedIndex
|
|
242
322
|
};
|
|
323
|
+
if (!hostMatched) return {
|
|
324
|
+
decision: "no_rule",
|
|
325
|
+
ruleIndex: null
|
|
326
|
+
};
|
|
243
327
|
return {
|
|
244
|
-
decision:
|
|
328
|
+
decision: portMatched ? "path_not_allowed" : "port_not_allowed",
|
|
245
329
|
ruleIndex: null
|
|
246
330
|
};
|
|
247
331
|
}
|
|
@@ -251,6 +335,7 @@ function describePolicyVerdict(verdict, rules) {
|
|
|
251
335
|
switch (verdict.decision) {
|
|
252
336
|
case "allow": return `permitted by rule ${(verdict.ruleIndex ?? 0) + 1}${rule?.label ? ` (${rule.label})` : ""}`;
|
|
253
337
|
case "no_rule": return "no rule covers this host";
|
|
338
|
+
case "port_not_allowed": return "this host has rules, but none cover this scheme and port";
|
|
254
339
|
case "method_not_allowed": return `rule ${(verdict.ruleIndex ?? 0) + 1} covers this path but not this method`;
|
|
255
340
|
case "path_not_allowed": return "this host has rules, but none cover this path";
|
|
256
341
|
case "secret_not_allowed": return `rule ${(verdict.ruleIndex ?? 0) + 1} permits the request but not this secret`;
|
|
@@ -291,6 +376,7 @@ function normalizeRule(rule) {
|
|
|
291
376
|
methods: rule.methods.map((m) => m.toUpperCase()).sort(),
|
|
292
377
|
paths: [...rule.paths],
|
|
293
378
|
allow: [...rule.allow].sort(),
|
|
379
|
+
ports: policyRulePorts(rule),
|
|
294
380
|
label: rule.label?.trim() || void 0
|
|
295
381
|
};
|
|
296
382
|
}
|
|
@@ -319,6 +405,7 @@ const activityEntrySchema = z.object({
|
|
|
319
405
|
decision: z.enum([
|
|
320
406
|
"allow",
|
|
321
407
|
"no_rule",
|
|
408
|
+
"port_not_allowed",
|
|
322
409
|
"method_not_allowed",
|
|
323
410
|
"path_not_allowed",
|
|
324
411
|
"secret_not_allowed",
|
|
@@ -354,8 +441,7 @@ z.object({
|
|
|
354
441
|
*/
|
|
355
442
|
entries: z.array(activityEntrySchema).min(1).max(500)
|
|
356
443
|
});
|
|
357
|
-
|
|
358
|
-
//#region ../../packages/core/src/agent-review.ts
|
|
444
|
+
// ../../packages/core/src/agent-review.ts
|
|
359
445
|
function emptyStats() {
|
|
360
446
|
return {
|
|
361
447
|
allows: 0,
|
|
@@ -565,8 +651,7 @@ z.object({
|
|
|
565
651
|
z.object({
|
|
566
652
|
/** The presented token. In the body, never a URL — it is a credential. */
|
|
567
653
|
token: z.string().trim().min(8).max(512) });
|
|
568
|
-
|
|
569
|
-
//#region ../../packages/core/src/archive.ts
|
|
654
|
+
// ../../packages/core/src/archive.ts
|
|
570
655
|
/**
|
|
571
656
|
* The **break-glass archive** format: one signed JSON file holding everything
|
|
572
657
|
* seekrit stores for an org, in the form seekrit stores it — ciphertext stays
|
|
@@ -951,8 +1036,7 @@ const ENTITLEMENT_KEYS = Object.keys({
|
|
|
951
1036
|
metric: "monthly_resolves"
|
|
952
1037
|
}
|
|
953
1038
|
});
|
|
954
|
-
|
|
955
|
-
//#region ../../packages/core/src/plans.ts
|
|
1039
|
+
// ../../packages/core/src/plans.ts
|
|
956
1040
|
const PLAN_FAMILIES = {
|
|
957
1041
|
free: {
|
|
958
1042
|
id: "free",
|
|
@@ -986,8 +1070,7 @@ const PLAN_FAMILIES = {
|
|
|
986
1070
|
const PLAN_FAMILY_IDS = Object.keys(PLAN_FAMILIES);
|
|
987
1071
|
/** Plan families a plan picker should render. Excludes families marked `hidden`. */
|
|
988
1072
|
const VISIBLE_PLAN_FAMILY_IDS = PLAN_FAMILY_IDS.filter((family) => !PLAN_FAMILIES[family].hidden);
|
|
989
|
-
|
|
990
|
-
//#region ../../packages/core/src/billing.ts
|
|
1073
|
+
// ../../packages/core/src/billing.ts
|
|
991
1074
|
/**
|
|
992
1075
|
* Lifecycle states a subscription can be in. Mirrors the biller's own states
|
|
993
1076
|
* (Stripe) but is provider-neutral so a different biller could map onto it.
|
|
@@ -999,8 +1082,7 @@ const SUBSCRIPTION_STATUSES = [
|
|
|
999
1082
|
"canceled",
|
|
1000
1083
|
"paused"
|
|
1001
1084
|
];
|
|
1002
|
-
|
|
1003
|
-
//#region ../../packages/core/src/branches.ts
|
|
1085
|
+
// ../../packages/core/src/branches.ts
|
|
1004
1086
|
/**
|
|
1005
1087
|
* Branch (ephemeral) environments — a per-PR/preview overlay on an existing
|
|
1006
1088
|
* application environment.
|
|
@@ -1044,8 +1126,7 @@ function parseBranchTtl(input) {
|
|
|
1044
1126
|
if (multiplier === void 0) return null;
|
|
1045
1127
|
return value * multiplier;
|
|
1046
1128
|
}
|
|
1047
|
-
|
|
1048
|
-
//#region ../../packages/core/src/change-requests.ts
|
|
1129
|
+
// ../../packages/core/src/change-requests.ts
|
|
1049
1130
|
const CHANGE_REQUEST_STATUSES = [
|
|
1050
1131
|
"open",
|
|
1051
1132
|
"applied",
|
|
@@ -1061,8 +1142,7 @@ function describeChange(kind, name, restoreFrom) {
|
|
|
1061
1142
|
case "restore": return `restore ${name} to v${restoreFrom ?? "?"}`;
|
|
1062
1143
|
}
|
|
1063
1144
|
}
|
|
1064
|
-
|
|
1065
|
-
//#region ../../packages/core/src/connect.ts
|
|
1145
|
+
// ../../packages/core/src/connect.ts
|
|
1066
1146
|
/** Workload authentication is separate from secret-decryption principals. */
|
|
1067
1147
|
const workloadPublicKeySchema = z.object({
|
|
1068
1148
|
kty: z.literal("EC"),
|
|
@@ -1196,8 +1276,7 @@ z.object({
|
|
|
1196
1276
|
body: z.string().max(65536).optional()
|
|
1197
1277
|
}).strict().refine((v) => !["GET", "HEAD"].includes(v.method) || v.body === void 0, "GET and HEAD cannot carry a body");
|
|
1198
1278
|
z.object({ audience: audienceSchema }).strict();
|
|
1199
|
-
|
|
1200
|
-
//#region ../../packages/core/src/dotenv.ts
|
|
1279
|
+
// ../../packages/core/src/dotenv.ts
|
|
1201
1280
|
/**
|
|
1202
1281
|
* `.env` parsing and serialization — the canonical definition of what a `.env`
|
|
1203
1282
|
* file means to seekrit. `apps/run/src/dotenv.rs` is a faithful port of the
|
|
@@ -1320,8 +1399,7 @@ function skipSpace(content, from, end) {
|
|
|
1320
1399
|
while (i < end && (content[i] === " " || content[i] === " ")) i++;
|
|
1321
1400
|
return i;
|
|
1322
1401
|
}
|
|
1323
|
-
|
|
1324
|
-
//#region ../../packages/core/src/ids.ts
|
|
1402
|
+
// ../../packages/core/src/ids.ts
|
|
1325
1403
|
const ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
1326
1404
|
/**
|
|
1327
1405
|
* Generate a prefixed, URL-safe random ID, e.g. `org_h9K2x…`.
|
|
@@ -1338,8 +1416,7 @@ function randomId(prefix, length = 24) {
|
|
|
1338
1416
|
}
|
|
1339
1417
|
return `${prefix}_${out}`;
|
|
1340
1418
|
}
|
|
1341
|
-
|
|
1342
|
-
//#region ../../packages/core/src/interpolate.ts
|
|
1419
|
+
// ../../packages/core/src/interpolate.ts
|
|
1343
1420
|
/**
|
|
1344
1421
|
* Secret references: `${OTHER_SECRET}` inside a secret value.
|
|
1345
1422
|
*
|
|
@@ -1484,8 +1561,7 @@ function interpolateSecrets(values) {
|
|
|
1484
1561
|
unresolved: [...unresolved].sort()
|
|
1485
1562
|
};
|
|
1486
1563
|
}
|
|
1487
|
-
|
|
1488
|
-
//#region ../../packages/core/src/json-value.ts
|
|
1564
|
+
// ../../packages/core/src/json-value.ts
|
|
1489
1565
|
/**
|
|
1490
1566
|
* Whether a value is worth *offering* a JSON view for: an object or array.
|
|
1491
1567
|
*
|
|
@@ -1939,8 +2015,7 @@ z.discriminatedUnion("provider", [
|
|
|
1939
2015
|
mintGcpLeaseSchema,
|
|
1940
2016
|
mintMongoLeaseSchema
|
|
1941
2017
|
]);
|
|
1942
|
-
|
|
1943
|
-
//#region ../../packages/core/src/providers/aws.ts
|
|
2018
|
+
// ../../packages/core/src/providers/aws.ts
|
|
1944
2019
|
/**
|
|
1945
2020
|
* The one-time IAM setup an admin performs so seekrit can assume the target
|
|
1946
2021
|
* role. Analogue of `sshHostSetupInstructions` / `postgresGroupBootstrapSql` —
|
|
@@ -1967,8 +2042,7 @@ function awsTrustPolicyInstructions(config) {
|
|
|
1967
2042
|
`# on ${config.roleArn}. seekrit only ever calls AssumeRole on this role.`
|
|
1968
2043
|
].join("\n");
|
|
1969
2044
|
}
|
|
1970
|
-
|
|
1971
|
-
//#region ../../packages/core/src/providers/gcp.ts
|
|
2045
|
+
// ../../packages/core/src/providers/gcp.ts
|
|
1972
2046
|
/**
|
|
1973
2047
|
* The one-time IAM setup an admin performs so seekrit can impersonate the target
|
|
1974
2048
|
* service account. Analogue of `awsTrustPolicyInstructions` — printed for the
|
|
@@ -1990,8 +2064,7 @@ function gcpSetupInstructions(config) {
|
|
|
1990
2064
|
"gcloud services enable iamcredentials.googleapis.com"
|
|
1991
2065
|
].join("\n");
|
|
1992
2066
|
}
|
|
1993
|
-
|
|
1994
|
-
//#region ../../packages/core/src/providers/mongodb.ts
|
|
2067
|
+
// ../../packages/core/src/providers/mongodb.ts
|
|
1995
2068
|
/**
|
|
1996
2069
|
* The one-time setup an admin performs so seekrit can provision users: create a
|
|
1997
2070
|
* dedicated provisioning user with `userAdmin` on the target database (or
|
|
@@ -2016,8 +2089,7 @@ function mongoAdminSetupInstructions(config) {
|
|
|
2016
2089
|
`// --uri "mongodb://seekrit_provisioner:<pwd>@${config.connection.host}:${config.connection.port}/?authSource=${authSource}"`
|
|
2017
2090
|
].join("\n");
|
|
2018
2091
|
}
|
|
2019
|
-
|
|
2020
|
-
//#region ../../packages/core/src/providers/postgres.ts
|
|
2092
|
+
// ../../packages/core/src/providers/postgres.ts
|
|
2021
2093
|
/**
|
|
2022
2094
|
* The one-time setup SQL an admin runs to create the shared group role that a
|
|
2023
2095
|
* read-only / read-write target's leased credentials inherit. Idempotent (safe
|
|
@@ -2048,8 +2120,7 @@ function postgresGroupBootstrapSql(config) {
|
|
|
2048
2120
|
if (config.accessLevel === "readwrite") lines.push(`GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA "${schema}" TO ${group};`, `ALTER DEFAULT PRIVILEGES IN SCHEMA "${schema}" GRANT USAGE, SELECT ON SEQUENCES TO ${group};`);
|
|
2049
2121
|
return lines.join("\n");
|
|
2050
2122
|
}
|
|
2051
|
-
|
|
2052
|
-
//#region ../../packages/core/src/providers/ssh.ts
|
|
2123
|
+
// ../../packages/core/src/providers/ssh.ts
|
|
2053
2124
|
/**
|
|
2054
2125
|
* The one-time host setup an admin runs so a target's certificates are accepted.
|
|
2055
2126
|
* Analogue of `postgresGroupBootstrapSql` — displayed for the admin to run, not
|
|
@@ -2069,8 +2140,7 @@ function sshHostSetupInstructions(config) {
|
|
|
2069
2140
|
"sudo systemctl reload sshd"
|
|
2070
2141
|
].join("\n");
|
|
2071
2142
|
}
|
|
2072
|
-
|
|
2073
|
-
//#region ../../packages/core/src/types.ts
|
|
2143
|
+
// ../../packages/core/src/types.ts
|
|
2074
2144
|
const AUDIT_ACTIONS = [
|
|
2075
2145
|
"workload.created",
|
|
2076
2146
|
"workload.updated",
|
|
@@ -2086,6 +2156,8 @@ const AUDIT_ACTIONS = [
|
|
|
2086
2156
|
"org.created",
|
|
2087
2157
|
"org.updated",
|
|
2088
2158
|
"org.member_added",
|
|
2159
|
+
"org.member_role_changed",
|
|
2160
|
+
"app.access_granted",
|
|
2089
2161
|
"org.member_removed",
|
|
2090
2162
|
"org.member_invited",
|
|
2091
2163
|
"org.invite_revoked",
|
|
@@ -2299,8 +2371,7 @@ const NOTIFICATION_TYPE_META = {
|
|
|
2299
2371
|
defaultEnabled: true
|
|
2300
2372
|
}
|
|
2301
2373
|
};
|
|
2302
|
-
|
|
2303
|
-
//#region ../../packages/core/src/schemas.ts
|
|
2374
|
+
// ../../packages/core/src/schemas.ts
|
|
2304
2375
|
/** URL-safe identifier segment: `my-app`, `production`, … */
|
|
2305
2376
|
const slugSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "must be lowercase alphanumeric with hyphens");
|
|
2306
2377
|
const nameSchema = z.string().trim().min(1).max(128);
|
|
@@ -2308,7 +2379,7 @@ const nameSchema = z.string().trim().min(1).max(128);
|
|
|
2308
2379
|
const emailSchema = z.string().trim().toLowerCase().pipe(z.email().max(320));
|
|
2309
2380
|
/** Env-var style secret name: FOO, DATABASE_URL, apiKey2 … */
|
|
2310
2381
|
const secretNameSchema = z.string().min(1).max(256).regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "must be a valid environment variable name");
|
|
2311
|
-
z.enum([
|
|
2382
|
+
const orgRoleSchema = z.enum([
|
|
2312
2383
|
"owner",
|
|
2313
2384
|
"admin",
|
|
2314
2385
|
"member"
|
|
@@ -2347,7 +2418,20 @@ z.object({
|
|
|
2347
2418
|
name: nameSchema,
|
|
2348
2419
|
slug: slugSchema
|
|
2349
2420
|
});
|
|
2421
|
+
/** Compose a group into an application environment (matched by env slug). */
|
|
2422
|
+
const appAccessKeySchema = z.object({
|
|
2423
|
+
environmentId: z.string().min(1),
|
|
2424
|
+
userId: z.string().min(1),
|
|
2425
|
+
wrappedDek: z.string().min(1).max(8192)
|
|
2426
|
+
});
|
|
2427
|
+
const appAccessGrantsSchema = z.array(appAccessKeySchema).max(5e3).optional();
|
|
2428
|
+
z.object({
|
|
2429
|
+
userId: z.string().min(1),
|
|
2430
|
+
grants: z.array(appAccessKeySchema).max(5e3)
|
|
2431
|
+
});
|
|
2432
|
+
z.object({ role: orgRoleSchema });
|
|
2350
2433
|
z.object({
|
|
2434
|
+
appAccessGrants: appAccessGrantsSchema,
|
|
2351
2435
|
groupId: z.string().min(1),
|
|
2352
2436
|
/** Precedence among an env's groups (higher wins). Appended if omitted. */
|
|
2353
2437
|
position: z.number().int().min(0).optional(),
|
|
@@ -2358,6 +2442,11 @@ z.object({
|
|
|
2358
2442
|
})).optional()
|
|
2359
2443
|
});
|
|
2360
2444
|
z.object({
|
|
2445
|
+
/** Wrap the new DEK to all persistent app recipients. */
|
|
2446
|
+
userGrants: z.array(z.object({
|
|
2447
|
+
userId: z.string().min(1),
|
|
2448
|
+
wrappedDek: z.string().min(1).max(8192)
|
|
2449
|
+
})).max(5e3).optional(),
|
|
2361
2450
|
name: nameSchema,
|
|
2362
2451
|
slug: slugSchema,
|
|
2363
2452
|
/** Environment DEK wrapped to the creator's public key — created client-side. */
|
|
@@ -2419,7 +2508,23 @@ z.object({
|
|
|
2419
2508
|
/** The same DEK wrapped to each of the parent's existing grant-holders. */
|
|
2420
2509
|
grants: z.array(grantEnvironmentKeySchema).max(500).default([])
|
|
2421
2510
|
});
|
|
2511
|
+
/**
|
|
2512
|
+
* Token creation is client-driven so the server never sees the token secret:
|
|
2513
|
+
* the client generates the keypair + token string and submits only the hash
|
|
2514
|
+
* and public key.
|
|
2515
|
+
*/
|
|
2516
|
+
const environmentGrantBatchSchema = z.array(z.object({
|
|
2517
|
+
environmentId: z.string().min(1),
|
|
2518
|
+
wrappedDek: z.string().min(1).max(8192)
|
|
2519
|
+
})).max(5e3);
|
|
2520
|
+
z.object({
|
|
2521
|
+
principalType: principalTypeSchema,
|
|
2522
|
+
principalId: z.string().min(1),
|
|
2523
|
+
grants: environmentGrantBatchSchema
|
|
2524
|
+
});
|
|
2422
2525
|
z.object({
|
|
2526
|
+
/** Commit the token and all default resolve keys together, when supplied. */
|
|
2527
|
+
initialGrants: environmentGrantBatchSchema.optional(),
|
|
2423
2528
|
name: nameSchema,
|
|
2424
2529
|
tokenId: z.string().regex(/^skt_[0-9A-Za-z]+$/),
|
|
2425
2530
|
/** SHA-256 hash (base64url) of the full token string. */
|
|
@@ -2688,8 +2793,7 @@ z.object({
|
|
|
2688
2793
|
*/
|
|
2689
2794
|
status: settableRotationStatusSchema.optional()
|
|
2690
2795
|
}).refine((v) => v.intervalSeconds !== void 0 || v.config !== void 0 || v.status !== void 0, "provide at least one of intervalSeconds, config, or status");
|
|
2691
|
-
|
|
2692
|
-
//#region ../../packages/core/src/sync.ts
|
|
2796
|
+
// ../../packages/core/src/sync.ts
|
|
2693
2797
|
/**
|
|
2694
2798
|
* Third-party sync: seekrit as source of truth, pushing an environment's
|
|
2695
2799
|
* resolved secrets out to a platform that holds its own copy (Vercel project
|
|
@@ -4173,8 +4277,7 @@ z.object({
|
|
|
4173
4277
|
mode: z.enum(["auto", "manual"]).optional(),
|
|
4174
4278
|
enabled: z.boolean().optional()
|
|
4175
4279
|
});
|
|
4176
|
-
|
|
4177
|
-
//#region ../../packages/core/src/vault.ts
|
|
4280
|
+
// ../../packages/core/src/vault.ts
|
|
4178
4281
|
/** One encrypted credential within a connection. */
|
|
4179
4282
|
const VAULT_SLOTS = [
|
|
4180
4283
|
"oauth2",
|
|
@@ -4458,8 +4561,7 @@ z.object({
|
|
|
4458
4561
|
userRef: vaultUserRefSchema,
|
|
4459
4562
|
leaseId: leaseIdSchema
|
|
4460
4563
|
});
|
|
4461
|
-
|
|
4462
|
-
//#region ../../packages/crypto/src/encoding.ts
|
|
4564
|
+
// ../../packages/crypto/src/encoding.ts
|
|
4463
4565
|
const CHUNK = 32768;
|
|
4464
4566
|
/** Base64url (no padding) — portable across browsers, Workers, and Node. */
|
|
4465
4567
|
function toBase64Url(bytes) {
|
|
@@ -4490,8 +4592,7 @@ function utf8Encode(text) {
|
|
|
4490
4592
|
function utf8Decode(bytes) {
|
|
4491
4593
|
return new TextDecoder().decode(bytes);
|
|
4492
4594
|
}
|
|
4493
|
-
|
|
4494
|
-
//#region ../../packages/crypto/src/errors.ts
|
|
4595
|
+
// ../../packages/crypto/src/errors.ts
|
|
4495
4596
|
var SeekritCryptoError = class extends Error {
|
|
4496
4597
|
code;
|
|
4497
4598
|
constructor(code, message) {
|
|
@@ -4511,8 +4612,7 @@ function splitBlob(blob, prefix, segments) {
|
|
|
4511
4612
|
if (parts.length !== segments + 1 || parts.some((p) => p.length === 0)) throw new SeekritCryptoError("MALFORMED_BLOB", `malformed "${prefix}" blob`);
|
|
4512
4613
|
return parts.slice(1);
|
|
4513
4614
|
}
|
|
4514
|
-
|
|
4515
|
-
//#region ../../packages/crypto/src/aes.ts
|
|
4615
|
+
// ../../packages/crypto/src/aes.ts
|
|
4516
4616
|
const SECRET_PREFIX = "sc1";
|
|
4517
4617
|
const IV_LENGTH$1 = 12;
|
|
4518
4618
|
/** Generate a fresh 256-bit data encryption key for an environment. */
|
|
@@ -4557,8 +4657,7 @@ async function decryptSecret(dek, blob, aad) {
|
|
|
4557
4657
|
function secretAad(environmentId, secretName) {
|
|
4558
4658
|
return `${environmentId}/${secretName}`;
|
|
4559
4659
|
}
|
|
4560
|
-
|
|
4561
|
-
//#region ../../packages/crypto/src/keys.ts
|
|
4660
|
+
// ../../packages/crypto/src/keys.ts
|
|
4562
4661
|
async function generateKeyPair() {
|
|
4563
4662
|
const pair = await crypto.subtle.generateKey({
|
|
4564
4663
|
name: "ECDH",
|
|
@@ -4591,8 +4690,7 @@ async function importPrivateKeyPkcs8(pkcs8) {
|
|
|
4591
4690
|
namedCurve: "P-256"
|
|
4592
4691
|
}, true, ["deriveBits"]);
|
|
4593
4692
|
}
|
|
4594
|
-
|
|
4595
|
-
//#region ../../packages/crypto/src/wrap.ts
|
|
4693
|
+
// ../../packages/crypto/src/wrap.ts
|
|
4596
4694
|
/**
|
|
4597
4695
|
* ECIES-style key wrapping: an ephemeral P-256 keypair performs ECDH against
|
|
4598
4696
|
* the recipient's public key; the shared secret is run through HKDF-SHA256 to
|
|
@@ -4660,8 +4758,7 @@ async function unwrapDek(wrapped, privateKey) {
|
|
|
4660
4758
|
throw new SeekritCryptoError("DECRYPT_FAILED", "DEK unwrap failed: wrong private key or tampered grant");
|
|
4661
4759
|
}
|
|
4662
4760
|
}
|
|
4663
|
-
|
|
4664
|
-
//#region ../../packages/crypto/src/aws.ts
|
|
4761
|
+
// ../../packages/crypto/src/aws.ts
|
|
4665
4762
|
/** Generate the ephemeral P-256 keypair a client uses to receive one AWS lease. */
|
|
4666
4763
|
async function generateAwsRecipientKeyPair() {
|
|
4667
4764
|
return generateKeyPair();
|
|
@@ -4688,8 +4785,7 @@ async function unwrapAwsCredential(wrapped, privateKeyJwk) {
|
|
|
4688
4785
|
region: c.region
|
|
4689
4786
|
};
|
|
4690
4787
|
}
|
|
4691
|
-
|
|
4692
|
-
//#region ../../packages/crypto/src/gcp.ts
|
|
4788
|
+
// ../../packages/crypto/src/gcp.ts
|
|
4693
4789
|
/** Generate the ephemeral P-256 keypair a client uses to receive one GCP lease. */
|
|
4694
4790
|
async function generateGcpRecipientKeyPair() {
|
|
4695
4791
|
return generateKeyPair();
|
|
@@ -4715,8 +4811,7 @@ async function unwrapGcpCredential(wrapped, privateKeyJwk) {
|
|
|
4715
4811
|
scopes: c.scopes
|
|
4716
4812
|
};
|
|
4717
4813
|
}
|
|
4718
|
-
|
|
4719
|
-
//#region ../../packages/crypto/src/kms.ts
|
|
4814
|
+
// ../../packages/crypto/src/kms.ts
|
|
4720
4815
|
/**
|
|
4721
4816
|
* Client-side KMS envelope encryption. A managed `encrypt` key is a 256-bit
|
|
4722
4817
|
* AES-GCM key whose material reaches the client only as a `wd1.` grant (wrapped
|
|
@@ -4846,8 +4941,7 @@ async function decryptDataKey(material, wrapped) {
|
|
|
4846
4941
|
throw new SeekritCryptoError("DECRYPT_FAILED", "data key unwrap failed: wrong key or tampered blob");
|
|
4847
4942
|
}
|
|
4848
4943
|
}
|
|
4849
|
-
|
|
4850
|
-
//#region ../../packages/crypto/src/random.ts
|
|
4944
|
+
// ../../packages/crypto/src/random.ts
|
|
4851
4945
|
const ALPHABETS = {
|
|
4852
4946
|
alphanumeric: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
4853
4947
|
hex: "0123456789abcdef",
|
|
@@ -4883,8 +4977,7 @@ function generateSecretValue(length, alphabet = "alphanumeric") {
|
|
|
4883
4977
|
}
|
|
4884
4978
|
return out;
|
|
4885
4979
|
}
|
|
4886
|
-
|
|
4887
|
-
//#region ../../packages/crypto/src/mongodb.ts
|
|
4980
|
+
// ../../packages/crypto/src/mongodb.ts
|
|
4888
4981
|
/** Generate the ephemeral P-256 keypair a client uses to receive one MongoDB lease. */
|
|
4889
4982
|
async function generateMongoRecipientKeyPair() {
|
|
4890
4983
|
return generateKeyPair();
|
|
@@ -4918,8 +5011,7 @@ async function unwrapMongoCredential(wrapped, privateKeyJwk) {
|
|
|
4918
5011
|
function mongoConnectionUri(cred) {
|
|
4919
5012
|
return `mongodb://${encodeURIComponent(cred.username)}:${encodeURIComponent(cred.password)}@${cred.host}:${cred.port}/${cred.database}?authSource=${cred.authSource}`;
|
|
4920
5013
|
}
|
|
4921
|
-
|
|
4922
|
-
//#region ../../packages/crypto/src/mysql.ts
|
|
5014
|
+
// ../../packages/crypto/src/mysql.ts
|
|
4923
5015
|
/**
|
|
4924
5016
|
* Client-side construction of a MySQL/MariaDB `mysql_native_password`
|
|
4925
5017
|
* authentication string, for minting *temporary MySQL login credentials*
|
|
@@ -4975,8 +5067,7 @@ async function generateMysqlCredential(options = {}) {
|
|
|
4975
5067
|
verifier: await mysqlNativePasswordVerifier(password)
|
|
4976
5068
|
};
|
|
4977
5069
|
}
|
|
4978
|
-
|
|
4979
|
-
//#region ../../packages/crypto/src/passphrase.ts
|
|
5070
|
+
// ../../packages/crypto/src/passphrase.ts
|
|
4980
5071
|
/**
|
|
4981
5072
|
* User private keys are stored server-side encrypted under a key derived from
|
|
4982
5073
|
* the user's passphrase, so any browser or CLI session can fetch and unlock
|
|
@@ -5035,8 +5126,7 @@ async function decryptPrivateKey(passphrase, blob) {
|
|
|
5035
5126
|
throw new SeekritCryptoError("DECRYPT_FAILED", "wrong passphrase or corrupted key blob");
|
|
5036
5127
|
}
|
|
5037
5128
|
}
|
|
5038
|
-
|
|
5039
|
-
//#region ../../packages/crypto/src/policy-key.ts
|
|
5129
|
+
// ../../packages/crypto/src/policy-key.ts
|
|
5040
5130
|
/**
|
|
5041
5131
|
* Signing with a principal's **existing** keypair, for agent access policy.
|
|
5042
5132
|
*
|
|
@@ -5105,8 +5195,7 @@ function policySignerJwk(publicKeyJwk) {
|
|
|
5105
5195
|
y: jwk.y
|
|
5106
5196
|
};
|
|
5107
5197
|
}
|
|
5108
|
-
|
|
5109
|
-
//#region ../../packages/crypto/src/shamir.ts
|
|
5198
|
+
// ../../packages/crypto/src/shamir.ts
|
|
5110
5199
|
/**
|
|
5111
5200
|
* Shamir's Secret Sharing over GF(2^8) — the same field AES uses, with the
|
|
5112
5201
|
* reduction polynomial x^8 + x^4 + x^3 + x + 1 (0x11b). Splits a byte string
|
|
@@ -5235,8 +5324,7 @@ function combineSecret(shares) {
|
|
|
5235
5324
|
}
|
|
5236
5325
|
return secret;
|
|
5237
5326
|
}
|
|
5238
|
-
|
|
5239
|
-
//#region ../../packages/crypto/src/recovery.ts
|
|
5327
|
+
// ../../packages/crypto/src/recovery.ts
|
|
5240
5328
|
/** Generate a fresh org recovery keypair (P-256, same as any principal). */
|
|
5241
5329
|
function generateRecoveryKey() {
|
|
5242
5330
|
return generateKeyPair();
|
|
@@ -5290,8 +5378,7 @@ async function combineRecoveryShares(shareBytes) {
|
|
|
5290
5378
|
throw new SeekritCryptoError("DECRYPT_FAILED", "recovery reconstruction failed: wrong, insufficient, or mismatched shares");
|
|
5291
5379
|
}
|
|
5292
5380
|
}
|
|
5293
|
-
|
|
5294
|
-
//#region ../../packages/crypto/src/redis.ts
|
|
5381
|
+
// ../../packages/crypto/src/redis.ts
|
|
5295
5382
|
/**
|
|
5296
5383
|
* Client-side construction of a Redis (6+) ACL password verifier, for minting
|
|
5297
5384
|
* *temporary Redis login credentials* without the password plaintext ever
|
|
@@ -5392,8 +5479,7 @@ async function generatePostgresCredential(options = {}) {
|
|
|
5392
5479
|
iterations
|
|
5393
5480
|
};
|
|
5394
5481
|
}
|
|
5395
|
-
|
|
5396
|
-
//#region ../../packages/crypto/src/sign.ts
|
|
5482
|
+
// ../../packages/crypto/src/sign.ts
|
|
5397
5483
|
/**
|
|
5398
5484
|
* Managed signing keys for the client-side KMS — ECDSA over P-256 (the curve
|
|
5399
5485
|
* already used for principal keypairs; universal in WebCrypto). A `sign` key's
|
|
@@ -5455,8 +5541,7 @@ function signatureKeyRef(signature) {
|
|
|
5455
5541
|
version
|
|
5456
5542
|
};
|
|
5457
5543
|
}
|
|
5458
|
-
|
|
5459
|
-
//#region ../../packages/crypto/src/ssh.ts
|
|
5544
|
+
// ../../packages/crypto/src/ssh.ts
|
|
5460
5545
|
/**
|
|
5461
5546
|
* Client-side SSH certificate authority for minting *temporary SSH access*
|
|
5462
5547
|
* without any private key ever reaching seekrit's control plane.
|
|
@@ -5587,8 +5672,7 @@ function encodeOpensshPrivateKey(seed, pub, comment) {
|
|
|
5587
5672
|
const b64 = toBase64(new SshWriter().bytes(utf8Encode("openssh-key-v1")).byte(0).string("none").string("none").string(/* @__PURE__ */ new Uint8Array(0)).uint32(1).string(ed25519PublicKeyBlob(pub)).string(concat(privSection, pad)).build());
|
|
5588
5673
|
return `-----BEGIN OPENSSH PRIVATE KEY-----\n${b64.match(/.{1,70}/g)?.join("\n") ?? b64}\n-----END OPENSSH PRIVATE KEY-----\n`;
|
|
5589
5674
|
}
|
|
5590
|
-
|
|
5591
|
-
//#region ../../packages/crypto/src/token.ts
|
|
5675
|
+
// ../../packages/crypto/src/token.ts
|
|
5592
5676
|
/**
|
|
5593
5677
|
* Service tokens (CI, docker builds, agent proxies, k8s, …) are self-contained
|
|
5594
5678
|
* principals. One local bundle holds independent authentication and decryption
|
|
@@ -5698,11 +5782,9 @@ async function createAgentTaskToken() {
|
|
|
5698
5782
|
tokenHash: await hashToken(token)
|
|
5699
5783
|
};
|
|
5700
5784
|
}
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
//#endregion
|
|
5705
|
-
//#region ../../packages/api-client/src/index.ts
|
|
5785
|
+
// package.json
|
|
5786
|
+
var version = "2.2.1";
|
|
5787
|
+
// ../../packages/api-client/src/index.ts
|
|
5706
5788
|
/**
|
|
5707
5789
|
* The proposer's note travels as a header, not a body field: an unprotected
|
|
5708
5790
|
* environment ignores it, so the same call works either way and no caller has
|
|
@@ -5837,6 +5919,56 @@ var SeekritClient = class {
|
|
|
5837
5919
|
listMembers(orgId) {
|
|
5838
5920
|
return this.request("GET", `/v1/orgs/${orgId}/members`);
|
|
5839
5921
|
}
|
|
5922
|
+
updateMemberRole(orgId, userId, role) {
|
|
5923
|
+
return this.request("PATCH", `/v1/orgs/${orgId}/members/${userId}`, { role });
|
|
5924
|
+
}
|
|
5925
|
+
getAppAccess(orgId, appId) {
|
|
5926
|
+
return this.request("GET", `/v1/orgs/${orgId}/apps/${appId}/access`);
|
|
5927
|
+
}
|
|
5928
|
+
provisionAppAccess(orgId, appId, userId, grants) {
|
|
5929
|
+
return this.request("POST", `/v1/orgs/${orgId}/apps/${appId}/access`, {
|
|
5930
|
+
userId,
|
|
5931
|
+
grants
|
|
5932
|
+
});
|
|
5933
|
+
}
|
|
5934
|
+
accessRecipients(orgId, target) {
|
|
5935
|
+
const path = "appId" in target ? `apps/${target.appId}` : `groups/${target.groupId}`;
|
|
5936
|
+
return this.request("GET", `/v1/orgs/${orgId}/${path}/access-recipients`);
|
|
5937
|
+
}
|
|
5938
|
+
/** The caller owns the new DEK; only public keys and wrapped blobs cross the API. */
|
|
5939
|
+
async wrapNewEnvironmentAccess(orgId, target, dek) {
|
|
5940
|
+
const { recipients } = await this.accessRecipients(orgId, target);
|
|
5941
|
+
return Promise.all(recipients.map(async (r) => {
|
|
5942
|
+
if (!r.publicKeyJwk) throw new Error(`${r.email} must finish key setup before creating this environment`);
|
|
5943
|
+
return {
|
|
5944
|
+
userId: r.userId,
|
|
5945
|
+
wrappedDek: await wrapDek(dek, r.publicKeyJwk)
|
|
5946
|
+
};
|
|
5947
|
+
}));
|
|
5948
|
+
}
|
|
5949
|
+
async wrapGroupAppAccess(orgId, envId, groupId, getDek) {
|
|
5950
|
+
const { environment } = await this.getEnv(orgId, envId);
|
|
5951
|
+
if (!environment.applicationId) throw new Error("only app environments can compose groups");
|
|
5952
|
+
const { recipients } = await this.accessRecipients(orgId, { appId: environment.applicationId });
|
|
5953
|
+
if (!recipients.length) return [];
|
|
5954
|
+
const { environments } = await this.listGroupEnvs(orgId, groupId);
|
|
5955
|
+
const result = [];
|
|
5956
|
+
for (const env of environments) {
|
|
5957
|
+
const { grants } = await this.listEnvKeys(orgId, env.id);
|
|
5958
|
+
const missing = recipients.filter((r) => !grants.some((g) => g.principalType === "user" && g.principalId === r.userId));
|
|
5959
|
+
if (!missing.length) continue;
|
|
5960
|
+
const dek = await getDek(env.id);
|
|
5961
|
+
for (const recipient of missing) {
|
|
5962
|
+
if (!recipient.publicKeyJwk) throw new Error(`${recipient.email} must finish key setup first`);
|
|
5963
|
+
result.push({
|
|
5964
|
+
environmentId: env.id,
|
|
5965
|
+
userId: recipient.userId,
|
|
5966
|
+
wrappedDek: await wrapDek(dek, recipient.publicKeyJwk)
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
}
|
|
5970
|
+
return result;
|
|
5971
|
+
}
|
|
5840
5972
|
/**
|
|
5841
5973
|
* The org-wide "require a second factor for all members" policy. `configured`
|
|
5842
5974
|
* is false when the identity provider isn't wired up (local dev), in which
|
|
@@ -6021,6 +6153,46 @@ var SeekritClient = class {
|
|
|
6021
6153
|
return this.request("POST", `/v1/orgs/${orgId}/change-requests/${id}/withdraw`);
|
|
6022
6154
|
}
|
|
6023
6155
|
/** The calling principal's wrapped DEK for this environment. */
|
|
6156
|
+
getEnvironmentKeyScope(orgId, envId) {
|
|
6157
|
+
return this.request("GET", `/v1/orgs/${orgId}/envs/${envId}/key-scope`);
|
|
6158
|
+
}
|
|
6159
|
+
async prepareComposedKeyGrants(orgId, envId, principal, getDek) {
|
|
6160
|
+
const scope = await this.getEnvironmentKeyScope(orgId, envId);
|
|
6161
|
+
const grants = [];
|
|
6162
|
+
for (const layer of scope.environments) {
|
|
6163
|
+
if (scope.grants.some((g) => g.environmentId === layer.environmentId && g.principalType === principal.principalType && g.principalId === principal.principalId)) continue;
|
|
6164
|
+
try {
|
|
6165
|
+
const dek = await getDek(layer.environmentId);
|
|
6166
|
+
grants.push({
|
|
6167
|
+
environmentId: layer.environmentId,
|
|
6168
|
+
wrappedDek: await wrapDek(dek, principal.publicKeyJwk)
|
|
6169
|
+
});
|
|
6170
|
+
} catch (error) {
|
|
6171
|
+
if (error instanceof Error && error.message === "unlock cancelled") throw error;
|
|
6172
|
+
throw new Error(`Could not prepare access to ${layer.label}: ${error instanceof Error ? error.message : "key unavailable"}. No keys were granted.`);
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
return grants;
|
|
6176
|
+
}
|
|
6177
|
+
grantComposedKeys(orgId, envId, input) {
|
|
6178
|
+
return this.request("POST", `/v1/orgs/${orgId}/envs/${envId}/keys/composed`, input);
|
|
6179
|
+
}
|
|
6180
|
+
/** Sharing an application environment includes the exact layers resolve reads. */
|
|
6181
|
+
async grantEnvironmentAccess(orgId, envId, principal, getDek) {
|
|
6182
|
+
const { environment } = await this.getEnv(orgId, envId);
|
|
6183
|
+
if (environment.applicationId) {
|
|
6184
|
+
const grants = await this.prepareComposedKeyGrants(orgId, envId, principal, getDek);
|
|
6185
|
+
await this.grantComposedKeys(orgId, envId, {
|
|
6186
|
+
principalType: principal.principalType,
|
|
6187
|
+
principalId: principal.principalId,
|
|
6188
|
+
grants
|
|
6189
|
+
});
|
|
6190
|
+
} else await this.grantEnvKey(orgId, envId, {
|
|
6191
|
+
principalType: principal.principalType,
|
|
6192
|
+
principalId: principal.principalId,
|
|
6193
|
+
wrappedDek: await wrapDek(await getDek(envId), principal.publicKeyJwk)
|
|
6194
|
+
});
|
|
6195
|
+
}
|
|
6024
6196
|
getMyEnvKey(orgId, envId) {
|
|
6025
6197
|
return this.request("GET", `/v1/orgs/${orgId}/envs/${envId}/key`);
|
|
6026
6198
|
}
|
|
@@ -6609,8 +6781,7 @@ function writeProjectConfig(config, dir = process.cwd()) {
|
|
|
6609
6781
|
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
|
|
6610
6782
|
return path;
|
|
6611
6783
|
}
|
|
6612
|
-
|
|
6613
|
-
//#region src/io.ts
|
|
6784
|
+
// src/io.ts
|
|
6614
6785
|
let failThrows = false;
|
|
6615
6786
|
/**
|
|
6616
6787
|
* In `seekrit mcp` the process is a long-lived stdio server, so a `fail()`
|
|
@@ -6735,8 +6906,7 @@ async function readStdin() {
|
|
|
6735
6906
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
6736
6907
|
return Buffer.concat(chunks).toString("utf8");
|
|
6737
6908
|
}
|
|
6738
|
-
|
|
6739
|
-
//#region src/context.ts
|
|
6909
|
+
// src/context.ts
|
|
6740
6910
|
/** Sent as `x-seekrit-client` so the API attributes usage to the CLI (analytics). */
|
|
6741
6911
|
const CLI_CLIENT = `cli/${version}`;
|
|
6742
6912
|
/**
|
|
@@ -6876,8 +7046,7 @@ async function getDek(ctx, orgId, envId) {
|
|
|
6876
7046
|
const [{ wrappedDek }, privateKey] = await Promise.all([ctx.client.getMyEnvKey(orgId, envId), getPrivateKey(ctx)]);
|
|
6877
7047
|
return unwrapDek(wrappedDek, privateKey);
|
|
6878
7048
|
}
|
|
6879
|
-
|
|
6880
|
-
//#region src/output.ts
|
|
7049
|
+
// src/output.ts
|
|
6881
7050
|
/** Shorthand for a column whose header is fixed and value is one field. */
|
|
6882
7051
|
function col(header, value) {
|
|
6883
7052
|
return {
|
|
@@ -6939,8 +7108,7 @@ function section(title) {
|
|
|
6939
7108
|
console.log("");
|
|
6940
7109
|
console.log(title);
|
|
6941
7110
|
}
|
|
6942
|
-
|
|
6943
|
-
//#region src/target.ts
|
|
7111
|
+
// src/target.ts
|
|
6944
7112
|
/** Resolve the target org from a flag, the committed config, or a lone org. */
|
|
6945
7113
|
async function resolveOrg(ctx, orgSlug) {
|
|
6946
7114
|
const wanted = orgSlug ?? findProjectConfig()?.org;
|
|
@@ -7096,8 +7264,7 @@ async function resolveGroup(ctx, opts) {
|
|
|
7096
7264
|
slug: group.slug
|
|
7097
7265
|
};
|
|
7098
7266
|
}
|
|
7099
|
-
|
|
7100
|
-
//#region src/access.ts
|
|
7267
|
+
// src/access.ts
|
|
7101
7268
|
/**
|
|
7102
7269
|
* Human-readable labels for the principals holding a key grant. `grant list`
|
|
7103
7270
|
* would otherwise be a column of opaque ids, which is exactly the question
|
|
@@ -7154,12 +7321,11 @@ function registerAccessCommands(program) {
|
|
|
7154
7321
|
const ctx = buildContext();
|
|
7155
7322
|
const { orgId, envId, label } = await resolveEnvTarget(ctx, options);
|
|
7156
7323
|
const principal = await resolvePrincipal(ctx, orgId, options);
|
|
7157
|
-
|
|
7158
|
-
await ctx.client.grantEnvKey(orgId, envId, {
|
|
7324
|
+
await ctx.client.grantEnvironmentAccess(orgId, envId, {
|
|
7159
7325
|
principalType: principal.type,
|
|
7160
7326
|
principalId: principal.id,
|
|
7161
|
-
|
|
7162
|
-
});
|
|
7327
|
+
publicKeyJwk: principal.publicKeyJwk
|
|
7328
|
+
}, (id) => getDek(ctx, orgId, id));
|
|
7163
7329
|
console.error(`granted ${label} access to ${principal.id}`);
|
|
7164
7330
|
});
|
|
7165
7331
|
withEnvTarget(grant.command("list").alias("ls").description("list who holds a key for an environment").option("--json", "print the raw API response")).action(async (options) => {
|
|
@@ -7191,8 +7357,7 @@ function registerAccessCommands(program) {
|
|
|
7191
7357
|
console.error(`revoked ${options.user ?? options.token}'s access to ${label}`);
|
|
7192
7358
|
});
|
|
7193
7359
|
}
|
|
7194
|
-
|
|
7195
|
-
//#region src/account.ts
|
|
7360
|
+
// src/account.ts
|
|
7196
7361
|
function sessionStatus(session) {
|
|
7197
7362
|
if (session.revokedAt) return "revoked";
|
|
7198
7363
|
if (session.expiresAt && Date.parse(session.expiresAt) < Date.now()) return "expired";
|
|
@@ -7242,8 +7407,7 @@ function registerAccountCommands(program) {
|
|
|
7242
7407
|
console.error(`${type} emails ${state}`);
|
|
7243
7408
|
});
|
|
7244
7409
|
}
|
|
7245
|
-
|
|
7246
|
-
//#region src/agents.ts
|
|
7410
|
+
// src/agents.ts
|
|
7247
7411
|
/**
|
|
7248
7412
|
* Agent access governance from the CLI.
|
|
7249
7413
|
*
|
|
@@ -7375,6 +7539,14 @@ function describeList(values) {
|
|
|
7375
7539
|
function describeSecrets(values) {
|
|
7376
7540
|
return values.length === 0 ? "none" : values.join(" ");
|
|
7377
7541
|
}
|
|
7542
|
+
/**
|
|
7543
|
+
* The third convention, and the one most likely to be misread: **no ports means
|
|
7544
|
+
* HTTPS on 443**, not any port. A blank or `any` here would say the opposite of
|
|
7545
|
+
* what the proxy enforces, so the default is spelled out.
|
|
7546
|
+
*/
|
|
7547
|
+
function describePorts(rule) {
|
|
7548
|
+
return describePolicyPorts(rule) ?? "https:443";
|
|
7549
|
+
}
|
|
7378
7550
|
function printRules(rules) {
|
|
7379
7551
|
printTable(rules.map((rule, i) => ({
|
|
7380
7552
|
n: i + 1,
|
|
@@ -7382,6 +7554,7 @@ function printRules(rules) {
|
|
|
7382
7554
|
})), [
|
|
7383
7555
|
col("#", (r) => r.n),
|
|
7384
7556
|
col("host", (r) => r.rule.host),
|
|
7557
|
+
col("ports", (r) => describePorts(r.rule)),
|
|
7385
7558
|
col("methods", (r) => describeList(r.rule.methods)),
|
|
7386
7559
|
col("paths", (r) => describeList(r.rule.paths)),
|
|
7387
7560
|
col("secrets", (r) => describeSecrets(r.rule.allow)),
|
|
@@ -7390,8 +7563,9 @@ function printRules(rules) {
|
|
|
7390
7563
|
}
|
|
7391
7564
|
/** One rule as a single line, for the publish diff. */
|
|
7392
7565
|
function ruleLine(rule) {
|
|
7566
|
+
const ports = describePolicyPorts(rule);
|
|
7393
7567
|
const parts = [
|
|
7394
|
-
rule.host,
|
|
7568
|
+
ports ? `${rule.host} [${ports}]` : rule.host,
|
|
7395
7569
|
describeList(rule.methods),
|
|
7396
7570
|
describeList(rule.paths),
|
|
7397
7571
|
`secrets=${describeSecrets(rule.allow)}`
|
|
@@ -7718,7 +7892,11 @@ function registerAgentCommands(program) {
|
|
|
7718
7892
|
console.error("widening proposals are never applied: an agent must not earn permissions by retrying");
|
|
7719
7893
|
}
|
|
7720
7894
|
});
|
|
7721
|
-
agents.command("simulate <ref>").description("ask what a policy would decide, without sending a request").requiredOption("--host <host>", "upstream hostname").requiredOption("--path <path>", "request path").option("--method <method>", "HTTP method", "GET").option("--secret <NAME>", "the secret the request would inject").option("--version <n>", "evaluate a specific published version").option("--org <slug>", "organization").option("--json", "machine-readable output").action(async (ref, options) => {
|
|
7895
|
+
agents.command("simulate <ref>").description("ask what a policy would decide, without sending a request").requiredOption("--host <host>", "upstream hostname").requiredOption("--path <path>", "request path").option("--method <method>", "HTTP method", "GET").option("--scheme <scheme>", "https or http, as the agent dials it (forward proxy)", "https").option("--port <n>", "port the agent dials (forward proxy; default 443 for https, 80 for http)").option("--secret <NAME>", "the secret the request would inject").option("--version <n>", "evaluate a specific published version").option("--org <slug>", "organization").option("--json", "machine-readable output").action(async (ref, options) => {
|
|
7896
|
+
const scheme = options.scheme.toLowerCase();
|
|
7897
|
+
if (scheme !== "https" && scheme !== "http") fail("--scheme must be https or http");
|
|
7898
|
+
const port = options.port === void 0 ? scheme === "https" ? 443 : 80 : Number(options.port);
|
|
7899
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) fail("--port must be 1-65535");
|
|
7722
7900
|
const ctx = buildContext();
|
|
7723
7901
|
const org = await resolveOrg(ctx, options.org);
|
|
7724
7902
|
const agent = await resolveAgent(ctx, org.id, ref);
|
|
@@ -7728,6 +7906,10 @@ function registerAgentCommands(program) {
|
|
|
7728
7906
|
if (!published) fail(`${agent.slug} has no published policy — every request is denied`);
|
|
7729
7907
|
const verdict = evaluatePolicy(published.rules, {
|
|
7730
7908
|
host: options.host,
|
|
7909
|
+
transport: {
|
|
7910
|
+
scheme,
|
|
7911
|
+
port
|
|
7912
|
+
},
|
|
7731
7913
|
method: options.method,
|
|
7732
7914
|
path: options.path,
|
|
7733
7915
|
...options.secret === void 0 ? {} : { secret: options.secret }
|
|
@@ -7738,7 +7920,7 @@ function registerAgentCommands(program) {
|
|
|
7738
7920
|
verdict,
|
|
7739
7921
|
reason
|
|
7740
7922
|
}, () => {
|
|
7741
|
-
const target = `${options.method.toUpperCase()} ${options.host}${options.path}`;
|
|
7923
|
+
const target = `${options.method.toUpperCase()} ${scheme}://${options.host}:${port}${options.path}`;
|
|
7742
7924
|
console.log(`${verdict.decision === "allow" ? "allow" : "DENY"} ${target}${options.secret ? ` [${options.secret}]` : ""}`);
|
|
7743
7925
|
console.log(` v${published.version}: ${reason}`);
|
|
7744
7926
|
});
|
|
@@ -7818,7 +8000,7 @@ function registerPolicyCommands(policy) {
|
|
|
7818
8000
|
const { signingKey, jwk } = await getPolicySigner(ctx);
|
|
7819
8001
|
const issuedAt = Math.floor(Date.now() / 1e3);
|
|
7820
8002
|
const bundle = await signAgentPolicy(signingKey, jwk, {
|
|
7821
|
-
v:
|
|
8003
|
+
v: policyBundleVersionFor(rules),
|
|
7822
8004
|
org: org.id,
|
|
7823
8005
|
agent: agent.id,
|
|
7824
8006
|
agent_slug: agent.slug,
|
|
@@ -8231,8 +8413,7 @@ function printExplain(provenance, refs = {}) {
|
|
|
8231
8413
|
}
|
|
8232
8414
|
if (refs.unresolved?.length) process.stderr.write(`\nunresolved reference(s), left as literal text: ${refs.unresolved.join(", ")}\n`);
|
|
8233
8415
|
}
|
|
8234
|
-
|
|
8235
|
-
//#region src/env-diff.ts
|
|
8416
|
+
// src/env-diff.ts
|
|
8236
8417
|
/**
|
|
8237
8418
|
* The comparison itself, kept pure so it can be tested without a network or a
|
|
8238
8419
|
* key. Rows come back sorted by name: a diff people read top-to-bottom should
|
|
@@ -8349,8 +8530,7 @@ function registerEnvDiff(env) {
|
|
|
8349
8530
|
});
|
|
8350
8531
|
});
|
|
8351
8532
|
}
|
|
8352
|
-
|
|
8353
|
-
//#region src/group-sync-grants.ts
|
|
8533
|
+
// src/group-sync-grants.ts
|
|
8354
8534
|
/** Wrap a newly composed group layer for connections already syncing the app env or its branches. */
|
|
8355
8535
|
async function syncGrantsForGroupLink(ctx, orgId, envId, envSlug, groupId) {
|
|
8356
8536
|
const [{ groups }, { bindings }, { branches }] = await Promise.all([
|
|
@@ -8375,8 +8555,7 @@ async function syncGrantsForGroupLink(ctx, orgId, envId, envSlug, groupId) {
|
|
|
8375
8555
|
};
|
|
8376
8556
|
}));
|
|
8377
8557
|
}
|
|
8378
|
-
|
|
8379
|
-
//#region src/kms.ts
|
|
8558
|
+
// src/kms.ts
|
|
8380
8559
|
/** Collect a repeatable option into a list. */
|
|
8381
8560
|
function collect$9(value, acc = []) {
|
|
8382
8561
|
acc.push(value);
|
|
@@ -8665,8 +8844,7 @@ function registerKmsCommands(program) {
|
|
|
8665
8844
|
}
|
|
8666
8845
|
});
|
|
8667
8846
|
}
|
|
8668
|
-
|
|
8669
|
-
//#region src/recovery.ts
|
|
8847
|
+
// src/recovery.ts
|
|
8670
8848
|
/** Collect a repeatable option into a list. */
|
|
8671
8849
|
function collect$8(value, acc = []) {
|
|
8672
8850
|
acc.push(value);
|
|
@@ -8865,8 +9043,7 @@ function registerRecoveryCommands(program) {
|
|
|
8865
9043
|
console.error(`recovery request ${requestId} canceled`);
|
|
8866
9044
|
});
|
|
8867
9045
|
}
|
|
8868
|
-
|
|
8869
|
-
//#region src/apps.ts
|
|
9046
|
+
// src/apps.ts
|
|
8870
9047
|
/**
|
|
8871
9048
|
* Applications and their environments — the structure everything else hangs
|
|
8872
9049
|
* off. Creating an environment is the one command here that does crypto: the
|
|
@@ -9013,6 +9190,7 @@ function registerAppCommands(program) {
|
|
|
9013
9190
|
const wrappedDek = await wrapDek(dek, await principalPublicKeyJwk(ctx));
|
|
9014
9191
|
const recoveryWrappedDek = await recoveryWrapForNewEnv(ctx, orgRef.id, dek);
|
|
9015
9192
|
const created = await ctx.client.createEnv(orgRef.id, appRow.id, {
|
|
9193
|
+
userGrants: await ctx.client.wrapNewEnvironmentAccess(orgRef.id, { appId: appRow.id }, dek),
|
|
9016
9194
|
name: options.name,
|
|
9017
9195
|
slug: options.slug,
|
|
9018
9196
|
wrappedDek,
|
|
@@ -9038,6 +9216,7 @@ function registerAppCommands(program) {
|
|
|
9038
9216
|
});
|
|
9039
9217
|
const syncGrants = await syncGrantsForGroupLink(ctx, target.orgId, target.envId, target.envSlug, group.id);
|
|
9040
9218
|
await ctx.client.linkEnvGroup(target.orgId, target.envId, {
|
|
9219
|
+
appAccessGrants: await ctx.client.wrapGroupAppAccess(target.orgId, target.envId, group.id, (id) => getDek(ctx, target.orgId, id)),
|
|
9041
9220
|
groupId: group.id,
|
|
9042
9221
|
position: options.position === void 0 ? void 0 : Number.parseInt(options.position, 10),
|
|
9043
9222
|
syncGrants
|
|
@@ -9066,8 +9245,7 @@ function registerAppCommands(program) {
|
|
|
9066
9245
|
console.error(`removed ${group.slug} from ${target.appSlug}/${target.envSlug}`);
|
|
9067
9246
|
});
|
|
9068
9247
|
}
|
|
9069
|
-
|
|
9070
|
-
//#region src/decryptor.ts
|
|
9248
|
+
// src/decryptor.ts
|
|
9071
9249
|
/**
|
|
9072
9250
|
* The standalone offline decryptor: one self-contained HTML file, written out by
|
|
9073
9251
|
* `seekrit archive decryptor`, that opens a break-glass archive in any browser
|
|
@@ -9899,8 +10077,7 @@ const OFFLINE_DECRYPTOR_HTML = `<!doctype html>
|
|
|
9899
10077
|
</body>
|
|
9900
10078
|
</html>
|
|
9901
10079
|
`;
|
|
9902
|
-
|
|
9903
|
-
//#region src/format.ts
|
|
10080
|
+
// src/format.ts
|
|
9904
10081
|
function shellQuote(value) {
|
|
9905
10082
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
9906
10083
|
}
|
|
@@ -9912,8 +10089,7 @@ function formatSecrets(values, format) {
|
|
|
9912
10089
|
case "dotenv": return names.map((name) => `${name}=${dotenvQuote(values[name] ?? "")}`).join("\n");
|
|
9913
10090
|
}
|
|
9914
10091
|
}
|
|
9915
|
-
|
|
9916
|
-
//#region src/archive.ts
|
|
10092
|
+
// src/archive.ts
|
|
9917
10093
|
/**
|
|
9918
10094
|
* Break-glass archives (docs/break-glass-export.md).
|
|
9919
10095
|
*
|
|
@@ -10243,8 +10419,7 @@ function registerArchiveCommands(program) {
|
|
|
10243
10419
|
console.error("open it in any browser — it declares a Content-Security-Policy of `default-src 'none'`, so it cannot reach the network.");
|
|
10244
10420
|
});
|
|
10245
10421
|
}
|
|
10246
|
-
|
|
10247
|
-
//#region src/audit.ts
|
|
10422
|
+
// src/audit.ts
|
|
10248
10423
|
/** The API's per-page ceiling (`auditQuerySchema.limit`). */
|
|
10249
10424
|
const MAX_PAGE = 200;
|
|
10250
10425
|
function parseLimit(raw) {
|
|
@@ -10298,8 +10473,7 @@ function registerAuditCommands(program) {
|
|
|
10298
10473
|
});
|
|
10299
10474
|
});
|
|
10300
10475
|
}
|
|
10301
|
-
|
|
10302
|
-
//#region src/aws.ts
|
|
10476
|
+
// src/aws.ts
|
|
10303
10477
|
/**
|
|
10304
10478
|
* `seekrit aws` — temporary AWS credentials via STS AssumeRole (Vault-style
|
|
10305
10479
|
* dynamic secrets, the tier-2 sibling of `seekrit pg`).
|
|
@@ -10441,8 +10615,7 @@ function registerAwsCommands(program) {
|
|
|
10441
10615
|
console.error(`revoked ${leaseId} (issued credentials remain valid until they expire)`);
|
|
10442
10616
|
});
|
|
10443
10617
|
}
|
|
10444
|
-
|
|
10445
|
-
//#region src/billing.ts
|
|
10618
|
+
// src/billing.ts
|
|
10446
10619
|
/** `included: null` means unlimited; an unavailable meter is unknown, not zero. */
|
|
10447
10620
|
function usageLine(usage) {
|
|
10448
10621
|
if (!usage.available) return "unknown";
|
|
@@ -10517,8 +10690,7 @@ function registerBillingCommands(program) {
|
|
|
10517
10690
|
console.error(`${ref.slug} moved to the Free plan`);
|
|
10518
10691
|
});
|
|
10519
10692
|
}
|
|
10520
|
-
|
|
10521
|
-
//#region src/branches.ts
|
|
10693
|
+
// src/branches.ts
|
|
10522
10694
|
/**
|
|
10523
10695
|
* Resolve `--from` to the environment being branched.
|
|
10524
10696
|
*
|
|
@@ -10615,8 +10787,7 @@ function registerBranchCommands(program) {
|
|
|
10615
10787
|
console.error(`deleted branch ${app.slug}#${target.slug}`);
|
|
10616
10788
|
});
|
|
10617
10789
|
}
|
|
10618
|
-
|
|
10619
|
-
//#region src/changes.ts
|
|
10790
|
+
// src/changes.ts
|
|
10620
10791
|
/**
|
|
10621
10792
|
* Change requests: the review queue in front of protected environments, and the
|
|
10622
10793
|
* protection settings that create it.
|
|
@@ -11144,8 +11315,7 @@ async function resolveEnvironment(ctx, scope) {
|
|
|
11144
11315
|
});
|
|
11145
11316
|
return values;
|
|
11146
11317
|
}
|
|
11147
|
-
|
|
11148
|
-
//#region src/doctor.ts
|
|
11318
|
+
// src/doctor.ts
|
|
11149
11319
|
/** Every `SEEKRIT_*` variable the CLI reads, for the set-but-blank check. */
|
|
11150
11320
|
const READ_VARS = [
|
|
11151
11321
|
"SEEKRIT_TOKEN",
|
|
@@ -11565,8 +11735,7 @@ function registerDoctorCommand(program) {
|
|
|
11565
11735
|
if (checks.some((c) => c.status === "fail")) process.exitCode = 1;
|
|
11566
11736
|
});
|
|
11567
11737
|
}
|
|
11568
|
-
|
|
11569
|
-
//#region src/gcp.ts
|
|
11738
|
+
// src/gcp.ts
|
|
11570
11739
|
/**
|
|
11571
11740
|
* `seekrit gcp` — temporary GCP credentials via IAM Credentials
|
|
11572
11741
|
* `generateAccessToken` (Vault-style dynamic secrets, the tier-2 sibling of
|
|
@@ -11708,8 +11877,7 @@ function registerGcpCommands(program) {
|
|
|
11708
11877
|
console.error(`revoked ${leaseId} (issued tokens remain valid until they expire)`);
|
|
11709
11878
|
});
|
|
11710
11879
|
}
|
|
11711
|
-
|
|
11712
|
-
//#region src/groups.ts
|
|
11880
|
+
// src/groups.ts
|
|
11713
11881
|
/**
|
|
11714
11882
|
* Shared groups: reusable secret bags composed into application environments
|
|
11715
11883
|
* by matching slug (`seekrit env groups add`). A group holds one environment
|
|
@@ -11807,6 +11975,7 @@ function registerGroupCommands(program) {
|
|
|
11807
11975
|
const wrappedDek = await wrapDek(dek, user.publicKeyJwk);
|
|
11808
11976
|
const recoveryWrappedDek = await recoveryWrapForNewEnv(ctx, groupRef.orgId, dek);
|
|
11809
11977
|
const created = await ctx.client.createGroupEnv(groupRef.orgId, groupRef.id, {
|
|
11978
|
+
userGrants: await ctx.client.wrapNewEnvironmentAccess(groupRef.orgId, { groupId: groupRef.id }, dek),
|
|
11810
11979
|
name: options.name,
|
|
11811
11980
|
slug: options.slug,
|
|
11812
11981
|
wrappedDek,
|
|
@@ -11815,8 +11984,7 @@ function registerGroupCommands(program) {
|
|
|
11815
11984
|
console.error(`created ${groupRef.slug}@${created.environment.slug} (${created.environment.id})`);
|
|
11816
11985
|
});
|
|
11817
11986
|
}
|
|
11818
|
-
|
|
11819
|
-
//#region src/hermes.ts
|
|
11987
|
+
// src/hermes.ts
|
|
11820
11988
|
/**
|
|
11821
11989
|
* `seekrit hermes` — wire seekrit into [Hermes Agent](https://hermes-agent.nousresearch.com)
|
|
11822
11990
|
* as a **secret source**.
|
|
@@ -12033,8 +12201,7 @@ function registerHermesCommands(program) {
|
|
|
12033
12201
|
emit(options, { reference: ref }, () => console.log(ref));
|
|
12034
12202
|
});
|
|
12035
12203
|
}
|
|
12036
|
-
|
|
12037
|
-
//#region src/honey.ts
|
|
12204
|
+
// src/honey.ts
|
|
12038
12205
|
/**
|
|
12039
12206
|
* Honey tokens — decoy credentials that unlock nothing and alert when used.
|
|
12040
12207
|
*
|
|
@@ -12079,8 +12246,7 @@ function registerHoneyTokenCommands(program) {
|
|
|
12079
12246
|
console.error(`${honeyTokenId} deleted`);
|
|
12080
12247
|
});
|
|
12081
12248
|
}
|
|
12082
|
-
|
|
12083
|
-
//#region src/m2m.ts
|
|
12249
|
+
// src/m2m.ts
|
|
12084
12250
|
/**
|
|
12085
12251
|
* Resolve M2M client credentials from (in order) the process environment, a
|
|
12086
12252
|
* `.env` overlay (for `seekrit run`), then saved config. Both halves must come
|
|
@@ -12270,6 +12436,7 @@ async function runInit(options) {
|
|
|
12270
12436
|
const wrappedDek = await wrapDek(dek, await principalPublicKeyJwk(ctx));
|
|
12271
12437
|
const recoveryWrappedDek = await recoveryWrapForNewEnv(ctx, orgRef.id, dek);
|
|
12272
12438
|
({environment: env} = await ctx.client.createEnv(orgRef.id, app.id, {
|
|
12439
|
+
userGrants: await ctx.client.wrapNewEnvironmentAccess(orgRef.id, { appId: app.id }, dek),
|
|
12273
12440
|
name: titleCase(envSlug),
|
|
12274
12441
|
slug: envSlug,
|
|
12275
12442
|
wrappedDek,
|
|
@@ -12316,8 +12483,7 @@ function registerInitCommand(program) {
|
|
|
12316
12483
|
await runInit(options);
|
|
12317
12484
|
});
|
|
12318
12485
|
}
|
|
12319
|
-
|
|
12320
|
-
//#region src/logsink.ts
|
|
12486
|
+
// src/logsink.ts
|
|
12321
12487
|
/** Collect repeated `--header Name: value` flags into a map. */
|
|
12322
12488
|
function collectHeader(value, acc = {}) {
|
|
12323
12489
|
const sep = value.indexOf(":");
|
|
@@ -12384,8 +12550,7 @@ function registerLogSinkCommands(program) {
|
|
|
12384
12550
|
console.error("log sink removed");
|
|
12385
12551
|
});
|
|
12386
12552
|
}
|
|
12387
|
-
|
|
12388
|
-
//#region src/mongodb.ts
|
|
12553
|
+
// src/mongodb.ts
|
|
12389
12554
|
/**
|
|
12390
12555
|
* `seekrit mongodb` — temporary MongoDB credentials (Vault-style dynamic
|
|
12391
12556
|
* secrets), the tier-2 sibling of `seekrit aws`.
|
|
@@ -12519,8 +12684,7 @@ function registerMongoCommands(program) {
|
|
|
12519
12684
|
console.error(`revoked ${leaseId} (the MongoDB user has been dropped)`);
|
|
12520
12685
|
});
|
|
12521
12686
|
}
|
|
12522
|
-
|
|
12523
|
-
//#region src/provisioner.ts
|
|
12687
|
+
// src/provisioner.ts
|
|
12524
12688
|
/**
|
|
12525
12689
|
* `seekrit provisioner` — helpers for the self-hosted **remote executor**
|
|
12526
12690
|
* (`seekrit-provisioner`), the daemon that runs a target's provisioning SQL
|
|
@@ -12562,8 +12726,7 @@ function resolveLeaseAdminSecret(opts) {
|
|
|
12562
12726
|
if (!adminUrl) fail(`provide the admin connection string via --admin-url or ${opts.adminUrlEnv}`);
|
|
12563
12727
|
return adminUrl;
|
|
12564
12728
|
}
|
|
12565
|
-
|
|
12566
|
-
//#region src/mysql.ts
|
|
12729
|
+
// src/mysql.ts
|
|
12567
12730
|
/**
|
|
12568
12731
|
* `seekrit mysql` — temporary MySQL/MariaDB credentials (Vault-style dynamic
|
|
12569
12732
|
* secrets).
|
|
@@ -12703,8 +12866,7 @@ function collect$5(value, acc) {
|
|
|
12703
12866
|
acc.push(value);
|
|
12704
12867
|
return acc;
|
|
12705
12868
|
}
|
|
12706
|
-
|
|
12707
|
-
//#region src/proxy-binary.ts
|
|
12869
|
+
// src/proxy-binary.ts
|
|
12708
12870
|
/**
|
|
12709
12871
|
* Fetch and run the `seekrit-proxy` binary without a Rust toolchain.
|
|
12710
12872
|
*
|
|
@@ -12739,7 +12901,7 @@ function collect$5(value, acc) {
|
|
|
12739
12901
|
* release-please-config.json), so the pin follows the crate without anyone
|
|
12740
12902
|
* remembering to move it.
|
|
12741
12903
|
*/
|
|
12742
|
-
const PROXY_VERSION = "0.
|
|
12904
|
+
const PROXY_VERSION = "0.14.0";
|
|
12743
12905
|
const BIN = "seekrit-proxy";
|
|
12744
12906
|
/**
|
|
12745
12907
|
* Host → Rust target triple.
|
|
@@ -12775,7 +12937,7 @@ function versionPrefix(version) {
|
|
|
12775
12937
|
return version.startsWith("v") ? version : `v${version}`;
|
|
12776
12938
|
}
|
|
12777
12939
|
function resolveVersion(explicit) {
|
|
12778
|
-
return explicit ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.
|
|
12940
|
+
return explicit ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.14.0";
|
|
12779
12941
|
}
|
|
12780
12942
|
function resolveBaseUrl(explicit) {
|
|
12781
12943
|
return (explicit ?? process.env.SEEKRIT_PROXY_BASE_URL ?? "https://proxy.seekrit.dev").replace(/\/+$/, "");
|
|
@@ -12885,8 +13047,7 @@ function signalNumber(signal) {
|
|
|
12885
13047
|
SIGTERM: 15
|
|
12886
13048
|
}[signal] ?? 0;
|
|
12887
13049
|
}
|
|
12888
|
-
|
|
12889
|
-
//#region src/nomad.ts
|
|
13050
|
+
// src/nomad.ts
|
|
12890
13051
|
/**
|
|
12891
13052
|
* `seekrit nomad` — wire seekrit into [HashiCorp Nomad](https://developer.hashicorp.com/nomad)
|
|
12892
13053
|
* as a **secret provider plugin**.
|
|
@@ -13270,8 +13431,7 @@ function registerNomadCommands(program) {
|
|
|
13270
13431
|
function indent(text) {
|
|
13271
13432
|
return text.split("\n").map((line) => line.length > 0 ? ` ${line}` : line).join("\n");
|
|
13272
13433
|
}
|
|
13273
|
-
|
|
13274
|
-
//#region src/orgs.ts
|
|
13434
|
+
// src/orgs.ts
|
|
13275
13435
|
/**
|
|
13276
13436
|
* Counts for `org show`. Each list is admin-gated to a different degree, so a
|
|
13277
13437
|
* member who can't read one still gets the rest: a failed call reports as
|
|
@@ -13428,8 +13588,7 @@ function registerOrgCommands(program) {
|
|
|
13428
13588
|
});
|
|
13429
13589
|
});
|
|
13430
13590
|
}
|
|
13431
|
-
|
|
13432
|
-
//#region src/proxy-presets.ts
|
|
13591
|
+
// src/proxy-presets.ts
|
|
13433
13592
|
/** `Authorization: Bearer {{seekrit:NAME}}` — the shape most providers take. */
|
|
13434
13593
|
function placeholder(secret) {
|
|
13435
13594
|
return `{{seekrit:${secret}}}`;
|
|
@@ -13613,8 +13772,31 @@ function presetAllow(preset) {
|
|
|
13613
13772
|
if (preset.allow) return preset.allow;
|
|
13614
13773
|
return preset.secret ? [preset.secret] : [];
|
|
13615
13774
|
}
|
|
13616
|
-
|
|
13617
|
-
|
|
13775
|
+
// src/proxy-config.ts
|
|
13776
|
+
/**
|
|
13777
|
+
* Generate a `seekrit-proxy.toml` — the other half of "adoptable without Rust".
|
|
13778
|
+
*
|
|
13779
|
+
* `npx @seekrit/proxy` removes the toolchain; this removes the config file. The
|
|
13780
|
+
* proxy's TOML is not large, but it is the kind of file where every field has to
|
|
13781
|
+
* be right at once before anything works: a bare hostname in one place and a
|
|
13782
|
+
* base URL in another, a path glob matched against the *upstream-facing* path,
|
|
13783
|
+
* a trust anchor that must not come from us, and two modes whose blocks look
|
|
13784
|
+
* similar and mean different things. Generating it turns first-run from an
|
|
13785
|
+
* exercise in reading reference docs into a command.
|
|
13786
|
+
*
|
|
13787
|
+
* Two things this module is careful about:
|
|
13788
|
+
*
|
|
13789
|
+
* - **It writes the config the proxy actually accepts.** In server-policy mode
|
|
13790
|
+
* `allow`/`methods`/`paths` on a route are *rejected*, not ignored, and the
|
|
13791
|
+
* forward proxy's hosts come from the bundle — so the server-mode emitter
|
|
13792
|
+
* leaves all four out rather than emitting a file that fails at startup. The
|
|
13793
|
+
* golden fixtures in `apps/proxy/testdata/generated-configs/` are parsed by a
|
|
13794
|
+
* Rust test, so this cannot drift from the parser.
|
|
13795
|
+
* - **It never pretends a fetched thumbprint is a local trust anchor.** The
|
|
13796
|
+
* `signers` list is the one field that must not come from the API. What is
|
|
13797
|
+
* emitted is annotated with where it came from, and the generated file says
|
|
13798
|
+
* so, in the file, next to the value.
|
|
13799
|
+
*/
|
|
13618
13800
|
/**
|
|
13619
13801
|
* A deliberately small TOML writer: basic strings and arrays of them, which is
|
|
13620
13802
|
* every value in this config. Full TOML is not needed and a general emitter
|
|
@@ -13835,6 +14017,22 @@ function splitHostPort(addr) {
|
|
|
13835
14017
|
if (idx === -1) return [addr, "8080"];
|
|
13836
14018
|
return [addr.slice(0, idx), addr.slice(idx + 1)];
|
|
13837
14019
|
}
|
|
14020
|
+
/**
|
|
14021
|
+
* Where a policy rule's service actually is, as a reverse-proxy upstream.
|
|
14022
|
+
*
|
|
14023
|
+
* A rule names its ports (HTTPS on 443 unless it says otherwise), so a route
|
|
14024
|
+
* generated from it has to dial one of them — `https://host` for a rule that
|
|
14025
|
+
* only covers `http_ports: [5000]` would route to a service the rule never
|
|
14026
|
+
* mentioned. The route is routing, not authorization: the reverse plane never
|
|
14027
|
+
* refuses on ports, because this URL is the operator's to edit.
|
|
14028
|
+
*/
|
|
14029
|
+
function upstreamForRule(rule) {
|
|
14030
|
+
const { https, http } = policyRulePorts(rule);
|
|
14031
|
+
if (https.includes(443)) return `https://${rule.host}`;
|
|
14032
|
+
if (https.length > 0) return `https://${rule.host}:${https[0]}`;
|
|
14033
|
+
const port = http[0];
|
|
14034
|
+
return port === 80 ? `http://${rule.host}` : `http://${rule.host}:${port}`;
|
|
14035
|
+
}
|
|
13838
14036
|
/** Build a file-policy plan from presets and/or ad-hoc `host=SECRET` rules. */
|
|
13839
14037
|
function planFromPresets(presets, options) {
|
|
13840
14038
|
const taken = /* @__PURE__ */ new Set();
|
|
@@ -13910,7 +14108,7 @@ function planFromPolicy(args, options) {
|
|
|
13910
14108
|
const baseUrl = baseUrlFor(options.listen, prefix, preset?.baseUrlSuffix ?? "");
|
|
13911
14109
|
routes.push({
|
|
13912
14110
|
prefix,
|
|
13913
|
-
upstream:
|
|
14111
|
+
upstream: upstreamForRule(rule),
|
|
13914
14112
|
host: rule.host,
|
|
13915
14113
|
allow: [],
|
|
13916
14114
|
methods: [],
|
|
@@ -14053,8 +14251,7 @@ function renderComposeSnippet(plan, options) {
|
|
|
14053
14251
|
out.push(forward ? "# `internal: true` network with the proxy as its only peer." : "# on an `internal: true` network with the proxy as its only peer.");
|
|
14054
14252
|
return `${out.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
|
|
14055
14253
|
}
|
|
14056
|
-
|
|
14057
|
-
//#region src/paperclip.ts
|
|
14254
|
+
// src/paperclip.ts
|
|
14058
14255
|
/**
|
|
14059
14256
|
* `seekrit paperclip` — wire seekrit into a [Paperclip](https://docs.paperclip.ing)
|
|
14060
14257
|
* agent.
|
|
@@ -14242,8 +14439,7 @@ function registerPaperclipCommands(program) {
|
|
|
14242
14439
|
});
|
|
14243
14440
|
});
|
|
14244
14441
|
}
|
|
14245
|
-
|
|
14246
|
-
//#region src/pg.ts
|
|
14442
|
+
// src/pg.ts
|
|
14247
14443
|
/**
|
|
14248
14444
|
* `seekrit pg` — temporary Postgres credentials (Vault-style dynamic secrets).
|
|
14249
14445
|
*
|
|
@@ -14395,8 +14591,7 @@ function collect$3(value, acc) {
|
|
|
14395
14591
|
acc.push(value);
|
|
14396
14592
|
return acc;
|
|
14397
14593
|
}
|
|
14398
|
-
|
|
14399
|
-
//#region src/proxy.ts
|
|
14594
|
+
// src/proxy.ts
|
|
14400
14595
|
/**
|
|
14401
14596
|
* `seekrit proxy` — get `seekrit-proxy` running without a Rust toolchain or a
|
|
14402
14597
|
* hand-written TOML file.
|
|
@@ -14725,10 +14920,10 @@ Next:
|
|
|
14725
14920
|
});
|
|
14726
14921
|
console.log(path);
|
|
14727
14922
|
const { target } = detectTarget();
|
|
14728
|
-
process.stderr.write(`seekrit-proxy ${versionPrefix(options.proxyVersion ?? "0.
|
|
14923
|
+
process.stderr.write(`seekrit-proxy ${versionPrefix(options.proxyVersion ?? "0.14.0")} (${target})\n`);
|
|
14729
14924
|
});
|
|
14730
14925
|
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) => {
|
|
14731
|
-
const version = options.proxyVersion ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.
|
|
14926
|
+
const version = options.proxyVersion ?? process.env.SEEKRIT_PROXY_VERSION ?? "0.14.0";
|
|
14732
14927
|
const override = process.env.SEEKRIT_PROXY_BIN;
|
|
14733
14928
|
const { target, exe } = detectTarget();
|
|
14734
14929
|
const path = override ?? proxyBinaryPath(version, target, exe);
|
|
@@ -14787,13 +14982,12 @@ Next:
|
|
|
14787
14982
|
process.stdout.write(renderComposeSnippet(plan, {
|
|
14788
14983
|
service: options.service,
|
|
14789
14984
|
workload: options.workload,
|
|
14790
|
-
image: options.image ?? `seekritdev/proxy:0.
|
|
14985
|
+
image: options.image ?? `seekritdev/proxy:0.14.0`,
|
|
14791
14986
|
publish: Boolean(options.publish)
|
|
14792
14987
|
}));
|
|
14793
14988
|
});
|
|
14794
14989
|
}
|
|
14795
|
-
|
|
14796
|
-
//#region src/redis.ts
|
|
14990
|
+
// src/redis.ts
|
|
14797
14991
|
/**
|
|
14798
14992
|
* `seekrit redis` — temporary Redis (6+) credentials (Vault-style dynamic
|
|
14799
14993
|
* secrets).
|
|
@@ -14933,8 +15127,7 @@ function collect$2(value, acc) {
|
|
|
14933
15127
|
acc.push(value);
|
|
14934
15128
|
return acc;
|
|
14935
15129
|
}
|
|
14936
|
-
|
|
14937
|
-
//#region src/rotation.ts
|
|
15130
|
+
// src/rotation.ts
|
|
14938
15131
|
/**
|
|
14939
15132
|
* `seekrit rotation` — managed, scheduled rotation of a stored secret's value.
|
|
14940
15133
|
*
|
|
@@ -15101,8 +15294,7 @@ function registerRotationCommands(program) {
|
|
|
15101
15294
|
console.error(`disabled rotation of ${r.secretName}${rotatorRevoked ? " — rotator key access revoked for this environment" : ""}`);
|
|
15102
15295
|
});
|
|
15103
15296
|
}
|
|
15104
|
-
|
|
15105
|
-
//#region src/run-command.ts
|
|
15297
|
+
// src/run-command.ts
|
|
15106
15298
|
/** Keep signal handlers installed from provisioning through asynchronous cleanup. */
|
|
15107
15299
|
async function runCommand(cmd, args, prepare, cleanup) {
|
|
15108
15300
|
let child;
|
|
@@ -15272,8 +15464,7 @@ async function reapStragglers(pids, signal) {
|
|
|
15272
15464
|
process.kill(pid, "SIGKILL");
|
|
15273
15465
|
} catch {}
|
|
15274
15466
|
}
|
|
15275
|
-
|
|
15276
|
-
//#region src/run-leases.ts
|
|
15467
|
+
// src/run-leases.ts
|
|
15277
15468
|
/** Validate all flags before contacting the API or creating any database roles. */
|
|
15278
15469
|
function parseRunLeaseOptions(bindings = [], ttl) {
|
|
15279
15470
|
if (!bindings.length) {
|
|
@@ -15398,8 +15589,7 @@ var RunLeases = class {
|
|
|
15398
15589
|
return ok;
|
|
15399
15590
|
}
|
|
15400
15591
|
};
|
|
15401
|
-
|
|
15402
|
-
//#region src/ssh.ts
|
|
15592
|
+
// src/ssh.ts
|
|
15403
15593
|
/**
|
|
15404
15594
|
* `seekrit ssh` — temporary SSH access via short-lived certificates (Vault-style
|
|
15405
15595
|
* dynamic secrets, the SSH sibling of `seekrit pg`).
|
|
@@ -15541,8 +15731,7 @@ function collect$1(value, acc) {
|
|
|
15541
15731
|
acc.push(value);
|
|
15542
15732
|
return acc;
|
|
15543
15733
|
}
|
|
15544
|
-
|
|
15545
|
-
//#region src/sync.ts
|
|
15734
|
+
// src/sync.ts
|
|
15546
15735
|
/** Split a `--flag a,b` list into trimmed, non-empty members. */
|
|
15547
15736
|
function list(raw, fallback) {
|
|
15548
15737
|
return (raw ?? fallback).split(",").map((item) => item.trim()).filter(Boolean);
|
|
@@ -16288,8 +16477,7 @@ function registerSyncCommands(program) {
|
|
|
16288
16477
|
], "no sync runs yet"));
|
|
16289
16478
|
});
|
|
16290
16479
|
}
|
|
16291
|
-
|
|
16292
|
-
//#region src/web-login.ts
|
|
16480
|
+
// src/web-login.ts
|
|
16293
16481
|
/**
|
|
16294
16482
|
* `seekrit login` — sign in through the browser.
|
|
16295
16483
|
*
|
|
@@ -16464,8 +16652,7 @@ async function runLogout() {
|
|
|
16464
16652
|
});
|
|
16465
16653
|
if (config.clientId) console.error("(machine client credentials are kept — remove them with `seekrit login`)");
|
|
16466
16654
|
}
|
|
16467
|
-
|
|
16468
|
-
//#region src/index.ts
|
|
16655
|
+
// src/index.ts
|
|
16469
16656
|
/** Collect repeated `--with group=env` flags into a map. */
|
|
16470
16657
|
function collectKv(value, acc = {}) {
|
|
16471
16658
|
const eq = value.indexOf("=");
|
|
@@ -16826,42 +17013,43 @@ token.command("create").description("create a service token (runtime, or --admin
|
|
|
16826
17013
|
if (boundToEnv && !(options.app && options.env)) fail("pass both --app and --env to bind a token to an environment");
|
|
16827
17014
|
const target = boundToEnv ? await resolveAppEnv(ctx, options) : { orgId: (await resolveOrg(ctx, options.org)).id };
|
|
16828
17015
|
const created = await createServiceToken();
|
|
17016
|
+
const granted = boundToEnv && options.grant !== false;
|
|
17017
|
+
const initialGrants = granted && "envId" in target ? await ctx.client.prepareComposedKeyGrants(target.orgId, target.envId, {
|
|
17018
|
+
principalType: "service_token",
|
|
17019
|
+
principalId: created.tokenId,
|
|
17020
|
+
publicKeyJwk: created.publicKeyJwk
|
|
17021
|
+
}, (id) => getDek(ctx, target.orgId, id)) : void 0;
|
|
17022
|
+
const extraGrants = [];
|
|
17023
|
+
if (granted) for (const [groupSlug, envSlug] of Object.entries(options.allow ?? {})) {
|
|
17024
|
+
const groupRef = await resolveGroup(ctx, {
|
|
17025
|
+
org: options.org,
|
|
17026
|
+
group: groupSlug
|
|
17027
|
+
});
|
|
17028
|
+
const { environments } = await ctx.client.listGroupEnvs(target.orgId, groupRef.id);
|
|
17029
|
+
const slice = environments.find((e) => e.slug === envSlug);
|
|
17030
|
+
if (!slice) fail(`group "${groupSlug}" has no "${envSlug}" environment`);
|
|
17031
|
+
extraGrants.push({
|
|
17032
|
+
environmentId: slice.id,
|
|
17033
|
+
wrappedDek: await wrapDek(await getDek(ctx, target.orgId, slice.id), created.publicKeyJwk)
|
|
17034
|
+
});
|
|
17035
|
+
}
|
|
16829
17036
|
await ctx.client.createToken(target.orgId, {
|
|
16830
17037
|
name: options.name,
|
|
16831
17038
|
tokenId: created.tokenId,
|
|
16832
17039
|
tokenHash: created.tokenHash,
|
|
16833
17040
|
publicKeyJwk: created.publicKeyJwk,
|
|
16834
17041
|
role,
|
|
16835
|
-
environmentId: "envId" in target ? target.envId : null
|
|
17042
|
+
environmentId: "envId" in target ? target.envId : null,
|
|
17043
|
+
initialGrants
|
|
16836
17044
|
});
|
|
16837
|
-
const
|
|
16838
|
-
|
|
16839
|
-
await ctx.client.grantEnvKey(target.orgId, envId, {
|
|
17045
|
+
for (const extra of extraGrants) try {
|
|
17046
|
+
await ctx.client.grantEnvKey(target.orgId, extra.environmentId, {
|
|
16840
17047
|
principalType: "service_token",
|
|
16841
17048
|
principalId: created.tokenId,
|
|
16842
|
-
wrappedDek:
|
|
17049
|
+
wrappedDek: extra.wrappedDek
|
|
16843
17050
|
});
|
|
16844
|
-
}
|
|
16845
|
-
|
|
16846
|
-
if (granted && "envId" in target) {
|
|
16847
|
-
await grantEnv(target.envId);
|
|
16848
|
-
const { groups } = await ctx.client.listEnvGroups(target.orgId, target.envId);
|
|
16849
|
-
for (const g of groups) {
|
|
16850
|
-
const { environments } = await ctx.client.listGroupEnvs(target.orgId, g.groupId);
|
|
16851
|
-
const slice = environments.find((e) => e.slug === target.envSlug);
|
|
16852
|
-
if (!slice) fail(`group "${g.slug}" has no "${target.envSlug}" environment — create it before granting`);
|
|
16853
|
-
await grantEnv(slice.id);
|
|
16854
|
-
}
|
|
16855
|
-
for (const [groupSlug, envSlug] of Object.entries(options.allow ?? {})) {
|
|
16856
|
-
const groupRef = await resolveGroup(ctx, {
|
|
16857
|
-
org: options.org,
|
|
16858
|
-
group: groupSlug
|
|
16859
|
-
});
|
|
16860
|
-
const { environments } = await ctx.client.listGroupEnvs(target.orgId, groupRef.id);
|
|
16861
|
-
const slice = environments.find((e) => e.slug === envSlug);
|
|
16862
|
-
if (!slice) fail(`group "${groupSlug}" has no "${envSlug}" environment`);
|
|
16863
|
-
await grantEnv(slice.id);
|
|
16864
|
-
}
|
|
17051
|
+
} catch (error) {
|
|
17052
|
+
console.error(`Token created with its default environment keys, but extra grant ${extra.environmentId} failed: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
16865
17053
|
}
|
|
16866
17054
|
const scope = "appSlug" in target ? `${target.appSlug}/${target.envSlug}` : "org-scoped (admin)";
|
|
16867
17055
|
console.error(`${role} token created${granted ? " and granted" : ""} for ${scope} — save it now, it is not stored:`);
|
|
@@ -16915,7 +17103,7 @@ registerKmsCommands(program);
|
|
|
16915
17103
|
registerRotationCommands(program);
|
|
16916
17104
|
registerRecoveryCommands(program);
|
|
16917
17105
|
program.command("mcp").description("run an MCP server over stdio so AI agents can drive seekrit").action(async () => {
|
|
16918
|
-
const { runMcpServer } = await import("./mcp-
|
|
17106
|
+
const { runMcpServer } = await import("./mcp-Clul2n4l.js");
|
|
16919
17107
|
await runMcpServer();
|
|
16920
17108
|
});
|
|
16921
17109
|
registerAuditCommands(program);
|
|
@@ -16929,5 +17117,4 @@ const argv = process.argv.map((arg) => arg === "-v" ? "--version" : arg);
|
|
|
16929
17117
|
program.parseAsync(argv).catch((err) => {
|
|
16930
17118
|
fail(describeError(err));
|
|
16931
17119
|
});
|
|
16932
|
-
//#endregion
|
|
16933
17120
|
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 };
|