@treeseed/sdk 0.13.0-rc.86 → 0.13.0-rc.88
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/.treeseed-build-complete.json +1 -1
- package/dist/configuration/secrets-capability.d.ts +2 -2
- package/dist/configuration/secrets-capability.js +2 -2
- package/dist/operator-contracts/canonical-command-tree.js +3 -2
- package/dist/operator-contracts/catalog/hosted-topology-operations.d.ts +0 -3
- package/dist/operator-contracts/catalog/hosted-topology-operations.js +3 -5
- package/dist/operator-contracts/catalog/services/secret-commands.d.ts +2 -0
- package/dist/operator-contracts/catalog/services/secret-commands.js +31 -0
- package/dist/operator-contracts/catalog/services/secret-operations.d.ts +62 -0
- package/dist/operator-contracts/catalog/services/secret-operations.js +51 -0
- package/dist/operator-contracts/control-plane-operations.d.ts +57 -20
- package/dist/operator-contracts/control-plane-operations.js +3 -4
- package/dist/secrets-capability/github-actions-encryption.d.ts +2 -0
- package/dist/secrets-capability/github-actions-encryption.js +11 -0
- package/dist/secrets-capability/provider-operation-contracts.d.ts +1 -1
- package/dist/secrets-capability/secret-contracts.d.ts +33 -0
- package/dist/secrets-capability/secret-contracts.js +49 -0
- package/dist/secrets-capability/service-provider-contracts.d.ts +2 -2
- package/dist/secrets-capability/service-provider-contracts.js +15 -31
- package/package.json +1 -1
- package/dist/operator-contracts/catalog/services/service-vault-operations.d.ts +0 -17
- package/dist/operator-contracts/catalog/services/service-vault-operations.js +0 -158
- package/dist/secrets-capability/service-vault-contracts.d.ts +0 -108
- package/dist/secrets-capability/service-vault-contracts.js +0 -74
- package/dist/secrets-capability/service-vault-crypto.d.ts +0 -20
- package/dist/secrets-capability/service-vault-crypto.js +0 -210
|
@@ -1 +1 @@
|
|
|
1
|
-
{"completedAt":"2026-09-
|
|
1
|
+
{"completedAt":"2026-09-05T04:21:52.801Z"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from '../secrets-capability/service-provider-contracts.js';
|
|
2
2
|
export * from '../secrets-capability/provider-operation-contracts.js';
|
|
3
3
|
export * from '../secrets-capability/workflow-operation-contracts.js';
|
|
4
|
-
export * from '../secrets-capability/
|
|
5
|
-
export * from '../secrets-capability/
|
|
4
|
+
export * from '../secrets-capability/secret-contracts.js';
|
|
5
|
+
export * from '../secrets-capability/github-actions-encryption.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "../secrets-capability/service-provider-contracts.js";
|
|
2
2
|
export * from "../secrets-capability/provider-operation-contracts.js";
|
|
3
3
|
export * from "../secrets-capability/workflow-operation-contracts.js";
|
|
4
|
-
export * from "../secrets-capability/
|
|
5
|
-
export * from "../secrets-capability/
|
|
4
|
+
export * from "../secrets-capability/secret-contracts.js";
|
|
5
|
+
export * from "../secrets-capability/github-actions-encryption.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { managedSecretCommands } from "./catalog/services/secret-commands.js";
|
|
1
2
|
import { hostProviderEnvironmentBranch, PROVIDER_ENVIRONMENT_COMMAND_BINDINGS, providerEnvironmentBranches } from "./catalog/provider-environment-commands.js";
|
|
2
3
|
const unavailable = (reason = "This capability is not enabled until its control-plane operation is accepted.") => ({
|
|
3
4
|
kind: "unavailable",
|
|
@@ -30,7 +31,6 @@ const operationBindings = {
|
|
|
30
31
|
"secrets status": local("local.secrets.status"),
|
|
31
32
|
"secrets unlock": local("local.secrets.unlock"),
|
|
32
33
|
"secrets lock": local("local.secrets.lock"),
|
|
33
|
-
"secrets rotate": local("local.secrets.rotate"),
|
|
34
34
|
"platform verify": local("local.platform.verify"),
|
|
35
35
|
"platform workset": local("local.platform.workset"),
|
|
36
36
|
"platform project create": local("local.platform.project.create"),
|
|
@@ -356,7 +356,8 @@ const commandTree = {
|
|
|
356
356
|
branch("auth", [authLogin(), leaf("logout", "mutation"), leaf("status")]),
|
|
357
357
|
branch("users", [userCreate()]),
|
|
358
358
|
branch("teams", [leaf("list"), { nodeType: "leaf", segment: "current", description: "Show the active team for this authenticated server session.", kind: "read", resultSchemaId: "treeseed.command.teams.current/v1", execution: unavailable() }, { nodeType: "leaf", segment: "use", description: "Select the active team for this authenticated server session.", kind: "mutation", arguments: [{ name: "team", description: "Team UUID or unambiguous slug.", required: true }], options: [planOption], authorization: { capability: "teams.read", confirmation: "never" }, resultSchemaId: "treeseed.command.teams.use/v1", execution: unavailable() }]),
|
|
359
|
-
branch("secrets", [leaf("list"), leaf("status"), leaf("unlock", "mutation", void 0, "credential"), leaf("lock", "mutation")
|
|
359
|
+
branch("secrets", [leaf("list"), leaf("status"), leaf("unlock", "mutation", void 0, "credential"), leaf("lock", "mutation")]),
|
|
360
|
+
managedSecretCommands,
|
|
360
361
|
branch("platform", [
|
|
361
362
|
{ nodeType: "leaf", segment: "verify", description: "Verify a declarative Platform repository without package checkouts or a control-plane API.", kind: "read", arguments: [{ name: "root", description: "Platform root; defaults to the current directory.", required: false }], options: [{ name: "--profile", description: "Composable profile to verify.", type: "string[]" }, { name: "--json", description: "Emit the stable command-result envelope.", type: "boolean" }], resultSchemaId: "treeseed.platform-verification/v1", execution: local("local.platform.verify") },
|
|
362
363
|
{ nodeType: "leaf", segment: "workset", description: "Plan or safely materialize exact primary source checkouts beneath packages/.", kind: "mutation", options: [planOption, { name: "--apply", description: "Apply the frozen workset plan.", type: "boolean" }, { name: "--yes", description: "Confirm the planned checkout mutations.", type: "boolean" }, { name: "--profile", description: "Composable source profile; repeat to union profiles.", type: "string[]" }, { name: "--project", description: "Explicit project slug; repeat to select projects.", type: "string[]" }, { name: "--exclude", description: "Project slug to exclude; repeat as needed.", type: "string[]" }, { name: "--json", description: "Emit the stable command-result envelope.", type: "boolean" }], authorization: { capability: "development.workset", confirmation: "never" }, resultSchemaId: "treeseed.platform-workset-result/v1", execution: local("local.platform.workset") },
|
|
@@ -59,7 +59,6 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
59
59
|
connectionRef: string;
|
|
60
60
|
}>>;
|
|
61
61
|
};
|
|
62
|
-
credentialLeaseIds?: string[] | undefined;
|
|
63
62
|
}, Record<string, unknown>>;
|
|
64
63
|
readonly apply: import("../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
65
64
|
teamId: string;
|
|
@@ -150,7 +149,6 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
150
149
|
platformCommit: string;
|
|
151
150
|
authorization: "authenticated-agent";
|
|
152
151
|
};
|
|
153
|
-
credentialLeaseIds?: string[] | undefined;
|
|
154
152
|
}, Record<string, unknown>>;
|
|
155
153
|
readonly status: import("../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
156
154
|
teamId: string;
|
|
@@ -397,6 +395,5 @@ export declare const HOSTED_TOPOLOGY_OPERATIONS: {
|
|
|
397
395
|
platformCommit: string;
|
|
398
396
|
authorization: "authenticated-agent";
|
|
399
397
|
};
|
|
400
|
-
credentialLeaseIds?: string[] | undefined;
|
|
401
398
|
}, Record<string, unknown>>;
|
|
402
399
|
};
|
|
@@ -5,7 +5,6 @@ const teamPath = z.object({ teamId: z.string().min(1) }).strict();
|
|
|
5
5
|
const empty = z.object({}).strict();
|
|
6
6
|
const none = z.undefined();
|
|
7
7
|
const operationReceipt = z.record(z.unknown());
|
|
8
|
-
const credentialLeaseIds = z.array(z.string().min(1)).min(1).optional();
|
|
9
8
|
const HOSTED_TOPOLOGY_OPERATIONS = {
|
|
10
9
|
plan: defineOperation({
|
|
11
10
|
operationId: "infrastructure.topology.plan",
|
|
@@ -22,7 +21,7 @@ const HOSTED_TOPOLOGY_OPERATIONS = {
|
|
|
22
21
|
cacheScope: "none",
|
|
23
22
|
pagination: "none",
|
|
24
23
|
idempotencyRequired: false
|
|
25
|
-
}, { path: teamPath, query: empty, body: z.object({ declaration: hostedTopologyDeclarationSchema
|
|
24
|
+
}, { path: teamPath, query: empty, body: z.object({ declaration: hostedTopologyDeclarationSchema }).strict(), output: operationReceipt }),
|
|
26
25
|
apply: defineOperation({
|
|
27
26
|
operationId: "infrastructure.topology.apply",
|
|
28
27
|
description: "Apply an exact agent-authorized hosted topology plan through the operations runner.",
|
|
@@ -38,7 +37,7 @@ const HOSTED_TOPOLOGY_OPERATIONS = {
|
|
|
38
37
|
cacheScope: "none",
|
|
39
38
|
pagination: "none",
|
|
40
39
|
concurrencyRequired: true
|
|
41
|
-
}, { path: teamPath, query: empty, body: z.object({ plan: hostedTopologyPlanSchema
|
|
40
|
+
}, { path: teamPath, query: empty, body: z.object({ plan: hostedTopologyPlanSchema }).strict(), output: operationReceipt }),
|
|
42
41
|
status: defineOperation({
|
|
43
42
|
operationId: "infrastructure.topology.status",
|
|
44
43
|
description: "Read the latest authoritative hosted topology receipt and operation state.",
|
|
@@ -72,8 +71,7 @@ const HOSTED_TOPOLOGY_OPERATIONS = {
|
|
|
72
71
|
}, { path: teamPath, query: empty, body: z.object({
|
|
73
72
|
execution: hostedTopologyRollbackExecutionSchema,
|
|
74
73
|
sourcePlan: hostedTopologyPlanSchema,
|
|
75
|
-
targetPlan: hostedTopologyPlanSchema
|
|
76
|
-
credentialLeaseIds
|
|
74
|
+
targetPlan: hostedTopologyPlanSchema
|
|
77
75
|
}).strict(), output: operationReceipt })
|
|
78
76
|
};
|
|
79
77
|
export {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const managedSecretCommands = {
|
|
2
|
+
nodeType: "branch",
|
|
3
|
+
segment: "services",
|
|
4
|
+
description: "Team service credentials.",
|
|
5
|
+
children: [{
|
|
6
|
+
nodeType: "branch",
|
|
7
|
+
segment: "credentials",
|
|
8
|
+
description: "Manage credentials in core OpenBao.",
|
|
9
|
+
children: ["show", "put", "delete", "validate"].map((name) => ({
|
|
10
|
+
nodeType: "leaf",
|
|
11
|
+
segment: name,
|
|
12
|
+
description: `${name} an exact service credential profile.`,
|
|
13
|
+
kind: name === "show" ? "read" : "mutation",
|
|
14
|
+
arguments: [{ name: "connection", description: "Service connection ID.", required: true }, { name: "profile", description: "Credential profile ID.", required: true }],
|
|
15
|
+
options: [
|
|
16
|
+
{ name: "--team", description: "Authorized team ID.", type: "string" },
|
|
17
|
+
...name === "show" ? [] : [
|
|
18
|
+
{ name: "--plan", description: "Inspect the proposed operation without mutation or secret input.", type: "boolean" },
|
|
19
|
+
{ name: "--expected-version", description: "Exact current credential version; zero for first creation.", type: "number", required: true }
|
|
20
|
+
],
|
|
21
|
+
...name === "put" ? [{ name: "--stdin", description: "Read the credential field object as JSON from standard input.", type: "boolean" }] : []
|
|
22
|
+
],
|
|
23
|
+
authorization: { capability: name === "show" ? "secrets.read" : "secrets.write", confirmation: "never" },
|
|
24
|
+
resultSchemaId: `treeseed.services.credentials.${name}/v1`,
|
|
25
|
+
execution: { kind: "local", handlerId: `local.services.credentials.${name}` }
|
|
26
|
+
}))
|
|
27
|
+
}]
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
managedSecretCommands
|
|
31
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export declare const SECRET_OPERATIONS: {
|
|
2
|
+
readonly credentialStatus: import("../../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
3
|
+
teamId: string;
|
|
4
|
+
profileId: string;
|
|
5
|
+
connectionId: string;
|
|
6
|
+
}, {}, {
|
|
7
|
+
expectedVersion: number;
|
|
8
|
+
} | undefined, {
|
|
9
|
+
version: number;
|
|
10
|
+
teamId: string;
|
|
11
|
+
profileId: string;
|
|
12
|
+
connectionId: string;
|
|
13
|
+
configured: boolean;
|
|
14
|
+
custody: "openbao";
|
|
15
|
+
fields: string[];
|
|
16
|
+
}>;
|
|
17
|
+
readonly putCredentials: import("../../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
18
|
+
teamId: string;
|
|
19
|
+
profileId: string;
|
|
20
|
+
connectionId: string;
|
|
21
|
+
}, {}, {
|
|
22
|
+
expectedVersion: number;
|
|
23
|
+
} | undefined, {
|
|
24
|
+
version: number;
|
|
25
|
+
teamId: string;
|
|
26
|
+
profileId: string;
|
|
27
|
+
connectionId: string;
|
|
28
|
+
configured: boolean;
|
|
29
|
+
custody: "openbao";
|
|
30
|
+
fields: string[];
|
|
31
|
+
}>;
|
|
32
|
+
readonly deleteCredentials: import("../../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
33
|
+
teamId: string;
|
|
34
|
+
profileId: string;
|
|
35
|
+
connectionId: string;
|
|
36
|
+
}, {}, {
|
|
37
|
+
expectedVersion: number;
|
|
38
|
+
} | undefined, {
|
|
39
|
+
version: number;
|
|
40
|
+
teamId: string;
|
|
41
|
+
profileId: string;
|
|
42
|
+
connectionId: string;
|
|
43
|
+
configured: boolean;
|
|
44
|
+
custody: "openbao";
|
|
45
|
+
fields: string[];
|
|
46
|
+
}>;
|
|
47
|
+
readonly validateCredentials: import("../../control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
48
|
+
teamId: string;
|
|
49
|
+
profileId: string;
|
|
50
|
+
connectionId: string;
|
|
51
|
+
}, {}, {
|
|
52
|
+
expectedVersion: number;
|
|
53
|
+
} | undefined, {
|
|
54
|
+
version: number;
|
|
55
|
+
teamId: string;
|
|
56
|
+
profileId: string;
|
|
57
|
+
connectionId: string;
|
|
58
|
+
configured: boolean;
|
|
59
|
+
custody: "openbao";
|
|
60
|
+
fields: string[];
|
|
61
|
+
}>;
|
|
62
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineOperation } from "../../operation-builder.js";
|
|
3
|
+
const id = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u);
|
|
4
|
+
const path = z.object({ teamId: id, connectionId: id, profileId: id }).strict();
|
|
5
|
+
const version = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER - 1);
|
|
6
|
+
const descriptor = z.object({
|
|
7
|
+
teamId: id,
|
|
8
|
+
connectionId: id,
|
|
9
|
+
profileId: id,
|
|
10
|
+
custody: z.literal("openbao"),
|
|
11
|
+
version,
|
|
12
|
+
configured: z.boolean(),
|
|
13
|
+
fields: z.array(id)
|
|
14
|
+
}).strict();
|
|
15
|
+
const put = z.object({
|
|
16
|
+
expectedVersion: version,
|
|
17
|
+
values: z.record(z.string().max(1024 * 1024)).refine((v) => Object.keys(v).length > 0 && Object.keys(v).length <= 128)
|
|
18
|
+
}).strict();
|
|
19
|
+
function operation(name, method) {
|
|
20
|
+
const read = method === "GET";
|
|
21
|
+
return defineOperation({
|
|
22
|
+
operationId: `services.credentials.${name}`,
|
|
23
|
+
description: `${name} managed service credentials.`,
|
|
24
|
+
rest: { method, path: `/v1/teams/{teamId}/services/{connectionId}/credentials/{profileId}${name === "validate" ? "/validate" : ""}` },
|
|
25
|
+
parameters: `treeseed.services.credentials.${name}.parameters/v1`,
|
|
26
|
+
capability: read ? "secrets.read" : "secrets.write",
|
|
27
|
+
authentication: "oauth",
|
|
28
|
+
oauthScopes: read ? ["treeseed:read"] : ["treeseed:projects:write"],
|
|
29
|
+
kind: read ? "read" : "mutation",
|
|
30
|
+
riskClass: read ? "ordinary" : "credential",
|
|
31
|
+
confirmation: read ? "never" : "input_required",
|
|
32
|
+
surfaces: ["rest"],
|
|
33
|
+
cacheScope: "none",
|
|
34
|
+
pagination: "none",
|
|
35
|
+
redactedPaths: ["body.values"]
|
|
36
|
+
}, {
|
|
37
|
+
path,
|
|
38
|
+
query: z.object({}).strict(),
|
|
39
|
+
body: read ? z.undefined() : name === "put" ? put : z.object({ expectedVersion: version }).strict(),
|
|
40
|
+
output: name === "validate" ? z.object({ ok: z.boolean() }).strict() : descriptor
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const SECRET_OPERATIONS = {
|
|
44
|
+
credentialStatus: operation("show", "GET"),
|
|
45
|
+
putCredentials: operation("put", "PUT"),
|
|
46
|
+
deleteCredentials: operation("delete", "DELETE"),
|
|
47
|
+
validateCredentials: operation("validate", "POST")
|
|
48
|
+
};
|
|
49
|
+
export {
|
|
50
|
+
SECRET_OPERATIONS
|
|
51
|
+
};
|
|
@@ -62,7 +62,6 @@ export declare const CONTROL_PLANE_OPERATIONS: {
|
|
|
62
62
|
connectionRef: string;
|
|
63
63
|
}>>;
|
|
64
64
|
};
|
|
65
|
-
credentialLeaseIds?: string[] | undefined;
|
|
66
65
|
}, Record<string, unknown>>;
|
|
67
66
|
readonly apply: import("./control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
68
67
|
teamId: string;
|
|
@@ -153,7 +152,6 @@ export declare const CONTROL_PLANE_OPERATIONS: {
|
|
|
153
152
|
platformCommit: string;
|
|
154
153
|
authorization: "authenticated-agent";
|
|
155
154
|
};
|
|
156
|
-
credentialLeaseIds?: string[] | undefined;
|
|
157
155
|
}, Record<string, unknown>>;
|
|
158
156
|
readonly status: import("./control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
159
157
|
teamId: string;
|
|
@@ -400,7 +398,6 @@ export declare const CONTROL_PLANE_OPERATIONS: {
|
|
|
400
398
|
platformCommit: string;
|
|
401
399
|
authorization: "authenticated-agent";
|
|
402
400
|
};
|
|
403
|
-
credentialLeaseIds?: string[] | undefined;
|
|
404
401
|
}, Record<string, unknown>>;
|
|
405
402
|
};
|
|
406
403
|
};
|
|
@@ -1397,26 +1394,66 @@ export declare const CONTROL_PLANE_OPERATIONS: {
|
|
|
1397
1394
|
teamId: string;
|
|
1398
1395
|
connectionId: string;
|
|
1399
1396
|
}, {}, Record<string, unknown> | undefined, Record<string, unknown>>;
|
|
1400
|
-
readonly
|
|
1397
|
+
readonly credentialStatus: import("./control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
1401
1398
|
teamId: string;
|
|
1402
1399
|
profileId: string;
|
|
1403
1400
|
connectionId: string;
|
|
1404
|
-
}, {},
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
readonly
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1401
|
+
}, {}, {
|
|
1402
|
+
expectedVersion: number;
|
|
1403
|
+
} | undefined, {
|
|
1404
|
+
version: number;
|
|
1405
|
+
teamId: string;
|
|
1406
|
+
profileId: string;
|
|
1407
|
+
connectionId: string;
|
|
1408
|
+
configured: boolean;
|
|
1409
|
+
custody: "openbao";
|
|
1410
|
+
fields: string[];
|
|
1411
|
+
}>;
|
|
1412
|
+
readonly putCredentials: import("./control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
1413
|
+
teamId: string;
|
|
1414
|
+
profileId: string;
|
|
1415
|
+
connectionId: string;
|
|
1416
|
+
}, {}, {
|
|
1417
|
+
expectedVersion: number;
|
|
1418
|
+
} | undefined, {
|
|
1419
|
+
version: number;
|
|
1420
|
+
teamId: string;
|
|
1421
|
+
profileId: string;
|
|
1422
|
+
connectionId: string;
|
|
1423
|
+
configured: boolean;
|
|
1424
|
+
custody: "openbao";
|
|
1425
|
+
fields: string[];
|
|
1426
|
+
}>;
|
|
1427
|
+
readonly deleteCredentials: import("./control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
1428
|
+
teamId: string;
|
|
1429
|
+
profileId: string;
|
|
1430
|
+
connectionId: string;
|
|
1431
|
+
}, {}, {
|
|
1432
|
+
expectedVersion: number;
|
|
1433
|
+
} | undefined, {
|
|
1434
|
+
version: number;
|
|
1435
|
+
teamId: string;
|
|
1436
|
+
profileId: string;
|
|
1437
|
+
connectionId: string;
|
|
1438
|
+
configured: boolean;
|
|
1439
|
+
custody: "openbao";
|
|
1440
|
+
fields: string[];
|
|
1441
|
+
}>;
|
|
1442
|
+
readonly validateCredentials: import("./control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
1443
|
+
teamId: string;
|
|
1444
|
+
profileId: string;
|
|
1445
|
+
connectionId: string;
|
|
1446
|
+
}, {}, {
|
|
1447
|
+
expectedVersion: number;
|
|
1448
|
+
} | undefined, {
|
|
1449
|
+
version: number;
|
|
1450
|
+
teamId: string;
|
|
1451
|
+
profileId: string;
|
|
1452
|
+
connectionId: string;
|
|
1453
|
+
configured: boolean;
|
|
1454
|
+
custody: "openbao";
|
|
1455
|
+
fields: string[];
|
|
1456
|
+
}>;
|
|
1420
1457
|
};
|
|
1421
1458
|
readonly agents: {
|
|
1422
1459
|
readonly list: import("./control-plane-operation.js").ControlPlaneOperationBinding<{
|
|
@@ -8,7 +8,7 @@ import { capabilityOntologyOperations } from "./catalog/capability-ontology-oper
|
|
|
8
8
|
import { knowledgeShareOperations } from "./catalog/knowledge-share-operations.js";
|
|
9
9
|
import { PROVIDER_ENVIRONMENT_OPERATIONS } from "./catalog/provider-environment-operations.js";
|
|
10
10
|
import { HOSTED_TOPOLOGY_OPERATIONS } from "./catalog/hosted-topology-operations.js";
|
|
11
|
-
import {
|
|
11
|
+
import { SECRET_OPERATIONS } from "./catalog/services/secret-operations.js";
|
|
12
12
|
import { buildControlPlaneCatalog, flattenControlPlaneOperations } from "./catalog/control-plane-catalog.js";
|
|
13
13
|
const empty = z.object({}).strict(), none = z.undefined(), record = z.record(z.unknown()), payload = record;
|
|
14
14
|
function read(operationId, path, capability, surfaces = ["rest"]) {
|
|
@@ -278,15 +278,14 @@ const CONTROL_PLANE_OPERATIONS = {
|
|
|
278
278
|
deleteWorkflowVariable: resource("repositories.workflow.variables.delete", "DELETE", "/v1/projects/{projectId}/workflow-configuration/variables/{name}", { projectId: z.string().min(1), name: z.string().min(1) }, { capability: "workflows.write", risk: "destructive", concurrency: true })
|
|
279
279
|
},
|
|
280
280
|
services: {
|
|
281
|
-
...
|
|
281
|
+
...SECRET_OPERATIONS,
|
|
282
282
|
providers: resource("services.providers.list", "GET", "/v1/service-providers", {}, { capability: "services.read", surfaces: ["rest", "cli", "mcp_tool"] }),
|
|
283
283
|
connections: resource("services.connections.list", "GET", "/v1/teams/{teamId}/services", { teamId: z.string().min(1) }, { capability: "services.read", surfaces: ["rest", "cli", "mcp_tool"], pagination: "cursor" }),
|
|
284
284
|
connection: resource("services.connections.show", "GET", "/v1/teams/{teamId}/services/{connectionId}", { teamId: z.string().min(1), connectionId: z.string().min(1) }, { capability: "services.read", surfaces: ["rest", "cli", "mcp_resource"] }),
|
|
285
285
|
createConnection: resource("services.connections.create", "POST", "/v1/teams/{teamId}/services", { teamId: z.string().min(1) }, { capability: "services.write", surfaces: ["rest", "cli", "mcp_tool"], risk: "credential" }),
|
|
286
286
|
updateConnection: resource("services.connections.update", "PUT", "/v1/teams/{teamId}/services/{connectionId}", { teamId: z.string().min(1), connectionId: z.string().min(1) }, { capability: "services.write", surfaces: ["rest", "cli", "mcp_tool"], concurrency: true }),
|
|
287
287
|
disconnect: resource("services.connections.disconnect", "DELETE", "/v1/teams/{teamId}/services/{connectionId}", { teamId: z.string().min(1), connectionId: z.string().min(1) }, { capability: "services.write", surfaces: ["rest", "cli", "mcp_tool"], risk: "destructive", concurrency: true }),
|
|
288
|
-
authorities: resource("services.credential.authorities.list", "GET", "/v1/teams/{teamId}/services/{connectionId}/credential-authorities", { teamId: z.string().min(1), connectionId: z.string().min(1) }, { capability: "secrets.read", surfaces: ["rest", "cli"], pagination: "cursor" })
|
|
289
|
-
putAuthority: resource("services.credential.authorities.put", "PUT", "/v1/teams/{teamId}/services/{connectionId}/credential-authorities/{profileId}", { teamId: z.string().min(1), connectionId: z.string().min(1), profileId: z.string().min(1) }, { capability: "secrets.write", surfaces: ["rest", "cli"], risk: "credential", concurrency: true, redactedPaths: ["body"] })
|
|
288
|
+
authorities: resource("services.credential.authorities.list", "GET", "/v1/teams/{teamId}/services/{connectionId}/credential-authorities", { teamId: z.string().min(1), connectionId: z.string().min(1) }, { capability: "secrets.read", surfaces: ["rest", "cli"], pagination: "cursor" })
|
|
290
289
|
},
|
|
291
290
|
agents: {
|
|
292
291
|
list: resource("agents.list", "GET", "/v1/projects/{projectId}/agents", { projectId: z.string().min(1) }, { capability: "agents.read", surfaces: ["rest", "cli", "mcp_tool"], pagination: "cursor" }),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import sodium from "libsodium-wrappers-sumo";
|
|
2
|
+
async function encryptGitHubActionsSecret(value, providerPublicKey) {
|
|
3
|
+
await sodium.ready;
|
|
4
|
+
if (!value) throw new Error("GitHub Actions secret value is required.");
|
|
5
|
+
const key = sodium.from_base64(providerPublicKey, sodium.base64_variants.ORIGINAL);
|
|
6
|
+
if (key.length !== sodium.crypto_box_PUBLICKEYBYTES) throw new Error("Invalid GitHub public key.");
|
|
7
|
+
return sodium.to_base64(sodium.crypto_box_seal(value, key), sodium.base64_variants.ORIGINAL);
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
encryptGitHubActionsSecret
|
|
11
|
+
};
|
|
@@ -9,7 +9,7 @@ export type ProviderCredentialAuthority = {
|
|
|
9
9
|
scheme: CredentialAuthorityScheme;
|
|
10
10
|
reference: string;
|
|
11
11
|
capabilities: ServiceCapabilityType[];
|
|
12
|
-
status: 'ready' | '
|
|
12
|
+
status: 'ready' | 'reauthorization-required' | 'revoked';
|
|
13
13
|
version: number;
|
|
14
14
|
};
|
|
15
15
|
export type ProjectRemoteRepositoryBinding = {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const secretScopeSchema: z.ZodObject<{
|
|
3
|
+
team: z.ZodString;
|
|
4
|
+
project: z.ZodString;
|
|
5
|
+
environment: z.ZodString;
|
|
6
|
+
purpose: z.ZodString;
|
|
7
|
+
name: z.ZodString;
|
|
8
|
+
}, "strict", z.ZodTypeAny, {
|
|
9
|
+
project: string;
|
|
10
|
+
name: string;
|
|
11
|
+
team: string;
|
|
12
|
+
purpose: string;
|
|
13
|
+
environment: string;
|
|
14
|
+
}, {
|
|
15
|
+
project: string;
|
|
16
|
+
name: string;
|
|
17
|
+
team: string;
|
|
18
|
+
purpose: string;
|
|
19
|
+
environment: string;
|
|
20
|
+
}>;
|
|
21
|
+
export type SecretScope = z.infer<typeof secretScopeSchema>;
|
|
22
|
+
export declare const SECRET_CUSTODY_BACKENDS: readonly ["openbao", "os"];
|
|
23
|
+
export declare function canonicalSecretPath(input: SecretScope): string;
|
|
24
|
+
export type HostedSecretOperationBinding = {
|
|
25
|
+
subjectType: 'declaration' | 'plan' | 'rollback';
|
|
26
|
+
subjectDigest: string;
|
|
27
|
+
deploymentId: string;
|
|
28
|
+
stackId: string;
|
|
29
|
+
environment: 'staging' | 'production';
|
|
30
|
+
};
|
|
31
|
+
export declare function containsForbiddenPlaintextSecretMaterial(value: unknown, path?: string): string[];
|
|
32
|
+
export declare function canonicalHostedSecretOperationBinding(input: HostedSecretOperationBinding): string;
|
|
33
|
+
export declare function validateHostedSecretOperationBinding(value: unknown): value is HostedSecretOperationBinding;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const segment = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u);
|
|
3
|
+
const secretScopeSchema = z.object({
|
|
4
|
+
team: segment,
|
|
5
|
+
project: segment,
|
|
6
|
+
environment: segment,
|
|
7
|
+
purpose: segment,
|
|
8
|
+
name: segment
|
|
9
|
+
}).strict();
|
|
10
|
+
const SECRET_CUSTODY_BACKENDS = ["openbao", "os"];
|
|
11
|
+
function canonicalSecretPath(input) {
|
|
12
|
+
const s = secretScopeSchema.parse(input);
|
|
13
|
+
return `teams/${s.team}/projects/${s.project}/environments/${s.environment}/purposes/${s.purpose}/secrets/${s.name}`;
|
|
14
|
+
}
|
|
15
|
+
const FORBIDDEN_SECRET_KEYS = /(?:passphrase|password|plaintext|derivedKey|privateKey|apiToken|accessToken|secretValue|credentialValue)$/iu;
|
|
16
|
+
function containsForbiddenPlaintextSecretMaterial(value, path = "") {
|
|
17
|
+
if (!value || typeof value !== "object") return [];
|
|
18
|
+
const failures = [];
|
|
19
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
20
|
+
const nextPath = path ? `${path}.${key}` : key;
|
|
21
|
+
if (FORBIDDEN_SECRET_KEYS.test(key) && typeof entry === "string" && entry.trim()) failures.push(nextPath);
|
|
22
|
+
if (entry && typeof entry === "object") failures.push(...containsForbiddenPlaintextSecretMaterial(entry, nextPath));
|
|
23
|
+
}
|
|
24
|
+
return failures;
|
|
25
|
+
}
|
|
26
|
+
const digest = /^sha256:[a-f0-9]{64}$/u;
|
|
27
|
+
const identifier = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
28
|
+
function canonicalHostedSecretOperationBinding(input) {
|
|
29
|
+
return JSON.stringify({
|
|
30
|
+
subjectType: input.subjectType,
|
|
31
|
+
subjectDigest: input.subjectDigest,
|
|
32
|
+
deploymentId: input.deploymentId,
|
|
33
|
+
stackId: input.stackId,
|
|
34
|
+
environment: input.environment
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function validateHostedSecretOperationBinding(value) {
|
|
38
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
39
|
+
const binding = value;
|
|
40
|
+
return ["declaration", "plan", "rollback"].includes(String(binding.subjectType)) && digest.test(String(binding.subjectDigest)) && identifier.test(String(binding.deploymentId)) && identifier.test(String(binding.stackId)) && ["staging", "production"].includes(String(binding.environment));
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
SECRET_CUSTODY_BACKENDS,
|
|
44
|
+
canonicalHostedSecretOperationBinding,
|
|
45
|
+
canonicalSecretPath,
|
|
46
|
+
containsForbiddenPlaintextSecretMaterial,
|
|
47
|
+
secretScopeSchema,
|
|
48
|
+
validateHostedSecretOperationBinding
|
|
49
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SERVICE_PROVIDER_IDS: readonly ["github", "cloudflare", "railway"
|
|
1
|
+
export declare const SERVICE_PROVIDER_IDS: readonly ["github", "cloudflare", "railway"];
|
|
2
2
|
export declare const SERVICE_CAPABILITY_TYPES: readonly ["repository-hosting", "workflow-execution", "workflow-configuration", "secret-enclave", "frontend-hosting", "backend-hosting", "dns-management", "object-storage", "state-encryption", "database-hosting", "capacity-runtime-hosting", "private-knowledge-index-hosting", "artifact-hosting"];
|
|
3
3
|
export declare const SERVICE_CONNECTION_STATUSES: readonly ["draft", "active", "degraded", "validation-failed", "reentry-required", "reauthorization-required", "disconnected"];
|
|
4
4
|
export declare const SERVICE_CAPABILITY_STATUSES: readonly ["configured", "disabled", "blocked", "planned"];
|
|
@@ -27,7 +27,7 @@ export type CredentialProfileDefinition = {
|
|
|
27
27
|
authoritySchemes?: CredentialAuthorityScheme[];
|
|
28
28
|
knowledgePageIds: string[];
|
|
29
29
|
};
|
|
30
|
-
export declare const CREDENTIAL_AUTHORITY_SCHEMES: readonly ["app-installation", "
|
|
30
|
+
export declare const CREDENTIAL_AUTHORITY_SCHEMES: readonly ["app-installation", "openbao"];
|
|
31
31
|
export type CredentialAuthorityScheme = (typeof CREDENTIAL_AUTHORITY_SCHEMES)[number];
|
|
32
32
|
export type ServiceCapabilityDefinition = {
|
|
33
33
|
type: ServiceCapabilityType;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const SERVICE_PROVIDER_IDS = ["github", "cloudflare", "railway"
|
|
1
|
+
const SERVICE_PROVIDER_IDS = ["github", "cloudflare", "railway"];
|
|
2
2
|
const SERVICE_CAPABILITY_TYPES = [
|
|
3
3
|
"repository-hosting",
|
|
4
4
|
"workflow-execution",
|
|
@@ -26,12 +26,7 @@ const SERVICE_CONNECTION_STATUSES = [
|
|
|
26
26
|
const SERVICE_CAPABILITY_STATUSES = ["configured", "disabled", "blocked", "planned"];
|
|
27
27
|
const CREDENTIAL_AUTHORITY_SCHEMES = [
|
|
28
28
|
"app-installation",
|
|
29
|
-
"
|
|
30
|
-
"oauth-token",
|
|
31
|
-
"environment-reference",
|
|
32
|
-
"client-encrypted",
|
|
33
|
-
"external-vault",
|
|
34
|
-
"workload-identity"
|
|
29
|
+
"openbao"
|
|
35
30
|
];
|
|
36
31
|
const field = (key, label, description, required = true, sensitive = false) => ({
|
|
37
32
|
key,
|
|
@@ -76,11 +71,11 @@ const SERVICE_PROVIDER_CATALOG = [
|
|
|
76
71
|
label: "Repository token authority",
|
|
77
72
|
description: "Fine-grained token authority restricted to selected repositories and Contents access.",
|
|
78
73
|
capabilities: ["repository-hosting"],
|
|
79
|
-
fields: [field("accessToken", "Fine-grained token", "
|
|
74
|
+
fields: [field("accessToken", "Fine-grained token", "Stored in core OpenBao; used only by authorized operations.", true, true)],
|
|
80
75
|
permissions: ["Metadata: read", "Contents: read and write"],
|
|
81
76
|
sharing: "capability-scoped",
|
|
82
|
-
unattendedCompatible:
|
|
83
|
-
authoritySchemes: ["
|
|
77
|
+
unattendedCompatible: true,
|
|
78
|
+
authoritySchemes: ["openbao"],
|
|
84
79
|
knowledgePageIds: ["provider.github", "services.credentials", "vault.rotation"]
|
|
85
80
|
},
|
|
86
81
|
{
|
|
@@ -100,11 +95,11 @@ const SERVICE_PROVIDER_CATALOG = [
|
|
|
100
95
|
label: "Workflow token authority",
|
|
101
96
|
description: "Fine-grained token authority for Actions and explicitly enabled secret or variable scopes.",
|
|
102
97
|
capabilities: ["workflow-execution", "workflow-configuration", "secret-enclave"],
|
|
103
|
-
fields: [field("accessToken", "Fine-grained token", "
|
|
98
|
+
fields: [field("accessToken", "Fine-grained token", "Stored in core OpenBao; used only by authorized operations.", true, true)],
|
|
104
99
|
permissions: ["Metadata: read", "Contents: read", "Actions: read and write", "Secrets: read and write", "Variables: read and write"],
|
|
105
100
|
sharing: "capability-scoped",
|
|
106
|
-
unattendedCompatible:
|
|
107
|
-
authoritySchemes: ["
|
|
101
|
+
unattendedCompatible: true,
|
|
102
|
+
authoritySchemes: ["openbao"],
|
|
108
103
|
knowledgePageIds: ["provider.github", "services.credentials", "vault.rotation"]
|
|
109
104
|
}
|
|
110
105
|
]
|
|
@@ -132,11 +127,11 @@ const SERVICE_PROVIDER_CATALOG = [
|
|
|
132
127
|
{ type: "state-encryption", label: "State encryption", description: "Independent encryption authority for OpenTofu state and plan files.", credentialProfileIds: ["opentofu-state-encryption"], status: "available" }
|
|
133
128
|
],
|
|
134
129
|
credentialProfiles: [
|
|
135
|
-
{ id: "cloudflare-runtime", label: "Pages and Workers token", description: "A token limited to application deployment resources.", capabilities: ["frontend-hosting"], fields: [field("apiToken", "API token", "
|
|
136
|
-
{ id: "cloudflare-dns", label: "DNS token", description: "A token restricted to selected zones.", capabilities: ["dns-management"], fields: [field("apiToken", "DNS API token", "Encrypted separately from deployment authority.", true, true)], permissions: ["Zone: DNS Edit for only the managed zones"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["
|
|
137
|
-
{ id: "cloudflare-storage", label: "Storage management authority", description: "Vault-custodied token used only to reconcile authorized R2 resources.", capabilities: ["object-storage"], fields: [field("apiToken", "Storage API token", "Encrypted separately and used only to reconcile authorized R2 resources.", true, true)], permissions: ["Account: Workers R2 Storage Edit"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["
|
|
138
|
-
{ id: "s3-state-session", label: "OpenTofu state session", description: "S3-compatible credentials limited to the selected team state prefix.", capabilities: ["object-storage"], fields: [field("accessKeyId", "R2 access key ID", "Vault-custodied S3-compatible access key identifier for encrypted OpenTofu state.", true, true), field("secretAccessKey", "R2 secret access key", "Vault-custodied S3-compatible secret key for encrypted OpenTofu state.", true, true), field("sessionToken", "R2 session token", "Optional short-lived S3-compatible session token.", false, true)], permissions: ["Object read and write for only the managed team state prefix"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["
|
|
139
|
-
{ id: "opentofu-state-encryption", label: "OpenTofu state encryption", description: "Independent encryption material that is never stored with the R2 state object.", capabilities: ["state-encryption"], fields: [field("stateEncryptionKey", "OpenTofu state encryption key", "
|
|
130
|
+
{ id: "cloudflare-runtime", label: "Pages and Workers token", description: "A token limited to application deployment resources.", capabilities: ["frontend-hosting"], fields: [field("apiToken", "API token", "Stored in core OpenBao and used only by authorized operations.", true, true)], permissions: ["Account: Workers Scripts Edit", "Account: Pages Edit"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["openbao"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] },
|
|
131
|
+
{ id: "cloudflare-dns", label: "DNS token", description: "A token restricted to selected zones.", capabilities: ["dns-management"], fields: [field("apiToken", "DNS API token", "Encrypted separately from deployment authority.", true, true)], permissions: ["Zone: DNS Edit for only the managed zones"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["openbao"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] },
|
|
132
|
+
{ id: "cloudflare-storage", label: "Storage management authority", description: "Vault-custodied token used only to reconcile authorized R2 resources.", capabilities: ["object-storage"], fields: [field("apiToken", "Storage API token", "Encrypted separately and used only to reconcile authorized R2 resources.", true, true)], permissions: ["Account: Workers R2 Storage Edit"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["openbao"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] },
|
|
133
|
+
{ id: "s3-state-session", label: "OpenTofu state session", description: "S3-compatible credentials limited to the selected team state prefix.", capabilities: ["object-storage"], fields: [field("accessKeyId", "R2 access key ID", "Vault-custodied S3-compatible access key identifier for encrypted OpenTofu state.", true, true), field("secretAccessKey", "R2 secret access key", "Vault-custodied S3-compatible secret key for encrypted OpenTofu state.", true, true), field("sessionToken", "R2 session token", "Optional short-lived S3-compatible session token.", false, true)], permissions: ["Object read and write for only the managed team state prefix"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["openbao"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] },
|
|
134
|
+
{ id: "opentofu-state-encryption", label: "OpenTofu state encryption", description: "Independent encryption material that is never stored with the R2 state object.", capabilities: ["state-encryption"], fields: [field("stateEncryptionKey", "OpenTofu state encryption key", "A 32-byte hexadecimal key stored separately in core OpenBao.", true, true)], permissions: ["Encrypt and decrypt only the selected team OpenTofu state"], sharing: "capability-scoped", unattendedCompatible: true, authoritySchemes: ["openbao"], knowledgePageIds: ["provider.cloudflare", "services.credentials", "vault.rotation"] }
|
|
140
135
|
]
|
|
141
136
|
},
|
|
142
137
|
{
|
|
@@ -163,24 +158,13 @@ const SERVICE_PROVIDER_CATALOG = [
|
|
|
163
158
|
label: "Railway workspace token",
|
|
164
159
|
description: "Railway currently exposes broad workspace authority. Sharing it increases the blast radius across enabled capabilities.",
|
|
165
160
|
capabilities: ["backend-hosting", "database-hosting", "capacity-runtime-hosting", "private-knowledge-index-hosting"],
|
|
166
|
-
fields: [field("apiToken", "Workspace token", "
|
|
161
|
+
fields: [field("apiToken", "Workspace token", "Stored in core OpenBao and used only by authorized operations.", true, true)],
|
|
167
162
|
permissions: ["Workspace access required by the selected operations"],
|
|
168
163
|
sharing: "provider-shared",
|
|
169
164
|
unattendedCompatible: true,
|
|
170
|
-
authoritySchemes: ["
|
|
165
|
+
authoritySchemes: ["openbao"],
|
|
171
166
|
knowledgePageIds: ["provider.railway", "services.credentials", "vault.rotation"]
|
|
172
167
|
}]
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
id: "openbao",
|
|
176
|
-
label: "OpenBao / HashiCorp Vault",
|
|
177
|
-
logoKey: "vault",
|
|
178
|
-
documentationUrl: "https://openbao.org/docs/auth/jwt/",
|
|
179
|
-
description: "Reference an external vault through workload identity; no long-lived vault token is stored.",
|
|
180
|
-
knowledgePageIds: ["provider.openbao", "services.external-vault"],
|
|
181
|
-
connectionFields: [field("address", "Vault address", "The HTTPS endpoint for the vault."), field("mount", "Secrets mount", "The mount containing TreeSeed-managed references."), field("role", "Workload identity role", "The OIDC/JWT role used by the operations runner.")],
|
|
182
|
-
capabilities: [{ type: "secret-enclave", label: "External secret vault", description: "Resolve approved secret references through workload identity.", credentialProfileIds: [], status: "available" }],
|
|
183
|
-
credentialProfiles: []
|
|
184
168
|
}
|
|
185
169
|
];
|
|
186
170
|
function getServiceProviderDefinition(providerId) {
|
package/package.json
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export declare const SERVICE_VAULT_OPERATIONS: {
|
|
2
|
-
readonly userVaultKey: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
3
|
-
readonly putUserVaultKey: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
4
|
-
readonly teamVault: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
5
|
-
readonly initializeTeamVault: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
6
|
-
readonly resetTeamVault: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
7
|
-
readonly rotateTeamVault: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
8
|
-
readonly vaultGrantCandidates: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
9
|
-
readonly createVaultGrant: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
10
|
-
readonly deleteVaultGrant: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
11
|
-
readonly vaultCredentialEnvelopes: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
12
|
-
readonly credentialEnvelopes: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
13
|
-
readonly putCredentialEnvelope: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
14
|
-
readonly createOperationLease: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
15
|
-
readonly operationLease: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
16
|
-
readonly putOperationLeasePayload: import("../../control-plane-operation.js").ControlPlaneOperationBinding<any, {}, any, any>;
|
|
17
|
-
};
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { defineOperation } from "../../operation-builder.js";
|
|
3
|
-
const empty = z.object({}).strict();
|
|
4
|
-
const none = z.undefined();
|
|
5
|
-
const record = z.record(z.unknown());
|
|
6
|
-
const teamPath = z.object({ teamId: z.string().min(1) }).strict();
|
|
7
|
-
function vaultOperation(operationId, method, path, pathSchema, options = {}) {
|
|
8
|
-
const read = method === "GET", risk = options.risk ?? "ordinary";
|
|
9
|
-
return defineOperation({
|
|
10
|
-
operationId,
|
|
11
|
-
description: `${read ? "Read" : "Apply"} ${operationId}.`,
|
|
12
|
-
rest: { method, path },
|
|
13
|
-
capability: read ? "secrets.read" : "secrets.write",
|
|
14
|
-
...path.includes("{") ? { parameters: `treeseed.${operationId}.parameters/v1` } : {},
|
|
15
|
-
authentication: "oauth",
|
|
16
|
-
oauthScopes: read ? ["treeseed:read"] : ["treeseed:projects:write"],
|
|
17
|
-
kind: read ? "read" : "mutation",
|
|
18
|
-
riskClass: risk,
|
|
19
|
-
confirmation: risk === "ordinary" ? "never" : "input_required",
|
|
20
|
-
surfaces: ["rest"],
|
|
21
|
-
cacheScope: read ? "principal" : "none",
|
|
22
|
-
pagination: "none",
|
|
23
|
-
redactedPaths: options.redactedPaths
|
|
24
|
-
}, { path: pathSchema, query: read ? record : empty, body: read ? none : options.body ?? record, output: options.output ?? record });
|
|
25
|
-
}
|
|
26
|
-
const nullableRecord = record.nullable(), records = z.array(record);
|
|
27
|
-
const encryptedPrivateKeyEnvelope = z.object({
|
|
28
|
-
version: z.literal("service-vault-v1"),
|
|
29
|
-
algorithm: z.literal("xchacha20-poly1305-ietf"),
|
|
30
|
-
kdf: z.object({ algorithm: z.literal("argon2id"), opsLimit: z.number().int().positive(), memLimit: z.number().int().positive(), salt: z.string().min(1) }).strict(),
|
|
31
|
-
nonce: z.string().min(1),
|
|
32
|
-
ciphertext: z.string().min(1),
|
|
33
|
-
publicKey: z.string().min(1)
|
|
34
|
-
}).strict();
|
|
35
|
-
const encryptedCredentialEnvelope = z.object({
|
|
36
|
-
version: z.literal("service-vault-v1"),
|
|
37
|
-
algorithm: z.literal("xchacha20-poly1305-ietf"),
|
|
38
|
-
ciphertext: z.string().min(1),
|
|
39
|
-
nonce: z.string().min(1),
|
|
40
|
-
wrappedKey: z.string().min(1),
|
|
41
|
-
wrappedKeyNonce: z.string().min(1),
|
|
42
|
-
associatedData: z.string().min(1),
|
|
43
|
-
associatedDataDigest: z.string().min(1),
|
|
44
|
-
fingerprint: z.string().min(1)
|
|
45
|
-
}).strict();
|
|
46
|
-
const SERVICE_VAULT_OPERATIONS = {
|
|
47
|
-
userVaultKey: vaultOperation("services.vault.user.key.show", "GET", "/v1/users/me/vault-key", empty, { output: nullableRecord }),
|
|
48
|
-
putUserVaultKey: vaultOperation(
|
|
49
|
-
"services.vault.user.key.put",
|
|
50
|
-
"PUT",
|
|
51
|
-
"/v1/users/me/vault-key",
|
|
52
|
-
empty,
|
|
53
|
-
{ risk: "credential", redactedPaths: ["body.encryptedPrivateKeyEnvelope"], body: z.object({ publicKey: z.string().min(1), encryptedPrivateKeyEnvelope }).strict() }
|
|
54
|
-
),
|
|
55
|
-
teamVault: vaultOperation("services.vault.team.show", "GET", "/v1/teams/{teamId}/vault", teamPath, { output: nullableRecord }),
|
|
56
|
-
initializeTeamVault: vaultOperation(
|
|
57
|
-
"services.vault.team.initialize",
|
|
58
|
-
"POST",
|
|
59
|
-
"/v1/teams/{teamId}/vault",
|
|
60
|
-
teamPath,
|
|
61
|
-
{ risk: "credential", redactedPaths: ["body.wrappedTeamVaultKey"], body: z.object({ userVaultKeyId: z.string().min(1), wrappedTeamVaultKey: z.string().min(1), encryptionVersion: z.literal("service-vault-v1") }).strict() }
|
|
62
|
-
),
|
|
63
|
-
resetTeamVault: vaultOperation(
|
|
64
|
-
"services.vault.team.reset",
|
|
65
|
-
"POST",
|
|
66
|
-
"/v1/teams/{teamId}/vault/reset",
|
|
67
|
-
teamPath,
|
|
68
|
-
{ risk: "destructive", redactedPaths: ["body.wrappedTeamVaultKey", "body.currentPassword"], body: z.object({ userVaultKeyId: z.string().min(1), wrappedTeamVaultKey: z.string().min(1), encryptionVersion: z.literal("service-vault-v1"), confirmation: z.string().min(1), currentPassword: z.string().min(1) }).strict() }
|
|
69
|
-
),
|
|
70
|
-
rotateTeamVault: vaultOperation(
|
|
71
|
-
"services.vault.team.rotate",
|
|
72
|
-
"POST",
|
|
73
|
-
"/v1/teams/{teamId}/vault/rotate",
|
|
74
|
-
teamPath,
|
|
75
|
-
{ risk: "credential", redactedPaths: ["body.envelopes", "body.grants"], body: z.object({
|
|
76
|
-
expectedKeyVersion: z.number().int().positive(),
|
|
77
|
-
envelopes: z.array(z.object({ id: z.string().min(1), envelope: encryptedCredentialEnvelope }).strict()),
|
|
78
|
-
grants: z.array(z.object({ userId: z.string().min(1), userVaultKeyId: z.string().min(1), wrappedTeamVaultKey: z.string().min(1) }).strict()).min(1)
|
|
79
|
-
}).strict() }
|
|
80
|
-
),
|
|
81
|
-
vaultGrantCandidates: vaultOperation("services.vault.grant.candidates.list", "GET", "/v1/teams/{teamId}/vault/grant-candidates", teamPath, { output: records }),
|
|
82
|
-
createVaultGrant: vaultOperation(
|
|
83
|
-
"services.vault.grants.create",
|
|
84
|
-
"POST",
|
|
85
|
-
"/v1/teams/{teamId}/vault/grants",
|
|
86
|
-
teamPath,
|
|
87
|
-
{ risk: "credential", redactedPaths: ["body.wrappedTeamVaultKey"], body: z.object({ userId: z.string().min(1), userVaultKeyId: z.string().min(1), wrappedTeamVaultKey: z.string().min(1) }).strict() }
|
|
88
|
-
),
|
|
89
|
-
deleteVaultGrant: vaultOperation(
|
|
90
|
-
"services.vault.grants.delete",
|
|
91
|
-
"DELETE",
|
|
92
|
-
"/v1/teams/{teamId}/vault/grants/{grantId}",
|
|
93
|
-
z.object({ teamId: z.string().min(1), grantId: z.string().min(1) }).strict(),
|
|
94
|
-
{ risk: "destructive" }
|
|
95
|
-
),
|
|
96
|
-
vaultCredentialEnvelopes: vaultOperation(
|
|
97
|
-
"services.vault.credential.envelopes.list",
|
|
98
|
-
"GET",
|
|
99
|
-
"/v1/teams/{teamId}/vault/credential-envelopes",
|
|
100
|
-
teamPath,
|
|
101
|
-
{ output: records }
|
|
102
|
-
),
|
|
103
|
-
credentialEnvelopes: vaultOperation(
|
|
104
|
-
"services.credential.envelopes.list",
|
|
105
|
-
"GET",
|
|
106
|
-
"/v1/teams/{teamId}/services/{connectionId}/credential-envelopes",
|
|
107
|
-
z.object({ teamId: z.string().min(1), connectionId: z.string().min(1) }).strict(),
|
|
108
|
-
{ output: records }
|
|
109
|
-
),
|
|
110
|
-
putCredentialEnvelope: vaultOperation(
|
|
111
|
-
"services.credential.envelopes.put",
|
|
112
|
-
"POST",
|
|
113
|
-
"/v1/teams/{teamId}/services/{connectionId}/credential-envelopes",
|
|
114
|
-
z.object({ teamId: z.string().min(1), connectionId: z.string().min(1) }).strict(),
|
|
115
|
-
{ risk: "credential", redactedPaths: ["body.envelope"], body: z.object({ definitionId: z.string().min(1), fieldKey: z.string().min(1), keyVersion: z.number().int().positive(), envelope: encryptedCredentialEnvelope }).strict() }
|
|
116
|
-
),
|
|
117
|
-
createOperationLease: vaultOperation(
|
|
118
|
-
"services.operation.leases.create",
|
|
119
|
-
"POST",
|
|
120
|
-
"/v1/teams/{teamId}/service-operation-leases",
|
|
121
|
-
teamPath,
|
|
122
|
-
{ risk: "authority", body: z.object({
|
|
123
|
-
connectionId: z.string().min(1),
|
|
124
|
-
capabilityType: z.string().min(1),
|
|
125
|
-
credentialProfileId: z.string().min(1),
|
|
126
|
-
purpose: z.enum([
|
|
127
|
-
"provider-connection-validation",
|
|
128
|
-
"remote-git-publication",
|
|
129
|
-
"workflow-dispatch",
|
|
130
|
-
"workflow-configuration",
|
|
131
|
-
"hosted-topology-plan",
|
|
132
|
-
"hosted-topology-apply",
|
|
133
|
-
"hosted-topology-readback",
|
|
134
|
-
"hosted-topology-rollback"
|
|
135
|
-
]),
|
|
136
|
-
idempotencyKey: z.string().min(1).optional(),
|
|
137
|
-
resourceScope: record.optional(),
|
|
138
|
-
hostedBinding: record.optional(),
|
|
139
|
-
authorityRequests: z.array(record).optional()
|
|
140
|
-
}).strict() }
|
|
141
|
-
),
|
|
142
|
-
operationLease: vaultOperation(
|
|
143
|
-
"services.operation.leases.show",
|
|
144
|
-
"GET",
|
|
145
|
-
"/v1/teams/{teamId}/service-operation-leases/{leaseId}",
|
|
146
|
-
z.object({ teamId: z.string().min(1), leaseId: z.string().min(1) }).strict()
|
|
147
|
-
),
|
|
148
|
-
putOperationLeasePayload: vaultOperation(
|
|
149
|
-
"services.operation.leases.payload.put",
|
|
150
|
-
"PUT",
|
|
151
|
-
"/v1/teams/{teamId}/service-operation-leases/{leaseId}/payload",
|
|
152
|
-
z.object({ teamId: z.string().min(1), leaseId: z.string().min(1) }).strict(),
|
|
153
|
-
{ risk: "credential", redactedPaths: ["body.sealedPayload"], body: z.object({ sealedPayload: z.string().min(1) }).strict() }
|
|
154
|
-
)
|
|
155
|
-
};
|
|
156
|
-
export {
|
|
157
|
-
SERVICE_VAULT_OPERATIONS
|
|
158
|
-
};
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
export declare const SERVICE_VAULT_ENCRYPTION_VERSION: "service-vault-v1";
|
|
2
|
-
export declare const SERVICE_VAULT_AEAD_ALGORITHM: "xchacha20-poly1305-ietf";
|
|
3
|
-
export declare const SERVICE_VAULT_KEY_AGREEMENT: "x25519-sealed-box";
|
|
4
|
-
export declare const SERVICE_VAULT_KDF: "argon2id";
|
|
5
|
-
export declare const SECRET_OPERATION_PURPOSES: readonly ["provider-connection-validation", "remote-git-publication", "workflow-dispatch", "workflow-configuration", "hosted-topology-plan", "hosted-topology-apply", "hosted-topology-readback", "hosted-topology-rollback"];
|
|
6
|
-
export type SecretOperationPurpose = (typeof SECRET_OPERATION_PURPOSES)[number];
|
|
7
|
-
export type HostedSecretOperationPurpose = Extract<SecretOperationPurpose, `hosted-topology-${string}`>;
|
|
8
|
-
export type EncryptedCredentialEnvelope = {
|
|
9
|
-
version: typeof SERVICE_VAULT_ENCRYPTION_VERSION;
|
|
10
|
-
algorithm: typeof SERVICE_VAULT_AEAD_ALGORITHM;
|
|
11
|
-
ciphertext: string;
|
|
12
|
-
nonce: string;
|
|
13
|
-
wrappedKey: string;
|
|
14
|
-
wrappedKeyNonce: string;
|
|
15
|
-
associatedData: string;
|
|
16
|
-
associatedDataDigest: string;
|
|
17
|
-
fingerprint: string;
|
|
18
|
-
};
|
|
19
|
-
export type EncryptedPrivateKeyEnvelope = {
|
|
20
|
-
version: typeof SERVICE_VAULT_ENCRYPTION_VERSION;
|
|
21
|
-
algorithm: typeof SERVICE_VAULT_AEAD_ALGORITHM;
|
|
22
|
-
kdf: {
|
|
23
|
-
algorithm: typeof SERVICE_VAULT_KDF;
|
|
24
|
-
opsLimit: number;
|
|
25
|
-
memLimit: number;
|
|
26
|
-
salt: string;
|
|
27
|
-
};
|
|
28
|
-
nonce: string;
|
|
29
|
-
ciphertext: string;
|
|
30
|
-
publicKey: string;
|
|
31
|
-
};
|
|
32
|
-
export type TeamVaultGrantEnvelope = {
|
|
33
|
-
version: typeof SERVICE_VAULT_ENCRYPTION_VERSION;
|
|
34
|
-
algorithm: typeof SERVICE_VAULT_KEY_AGREEMENT;
|
|
35
|
-
recipientPublicKey: string;
|
|
36
|
-
wrappedTeamVaultKey: string;
|
|
37
|
-
};
|
|
38
|
-
export type SecretOperationLease = {
|
|
39
|
-
id: string;
|
|
40
|
-
teamId: string;
|
|
41
|
-
connectionId: string;
|
|
42
|
-
capabilityType: string;
|
|
43
|
-
purpose: SecretOperationPurpose;
|
|
44
|
-
resourceScope: Record<string, string>;
|
|
45
|
-
credentialProfileId: string;
|
|
46
|
-
actorUserId: string;
|
|
47
|
-
requiredFields: string[];
|
|
48
|
-
publicKey: string;
|
|
49
|
-
status: 'awaiting-runner' | 'pending' | 'ready' | 'consumed' | 'expired' | 'cancelled' | 'failed';
|
|
50
|
-
expiresAt: string;
|
|
51
|
-
consumedAt?: string | null;
|
|
52
|
-
operationCorrelationId: string;
|
|
53
|
-
hostedBinding?: HostedSecretOperationBinding;
|
|
54
|
-
authorityRequests?: SecretOperationAuthorityRequest[];
|
|
55
|
-
};
|
|
56
|
-
export type HostedSecretOperationBinding = {
|
|
57
|
-
subjectType: 'declaration' | 'plan' | 'rollback';
|
|
58
|
-
subjectDigest: string;
|
|
59
|
-
deploymentId: string;
|
|
60
|
-
stackId: string;
|
|
61
|
-
environment: 'staging' | 'production';
|
|
62
|
-
};
|
|
63
|
-
export type SecretOperationAuthorityRequest = {
|
|
64
|
-
requestId: string;
|
|
65
|
-
connectionId: string;
|
|
66
|
-
credentialProfileId: string;
|
|
67
|
-
provider: 'cloudflare' | 'railway' | 'treeseed';
|
|
68
|
-
purpose: 'provider' | 'state-backend' | 'state-encryption';
|
|
69
|
-
capabilities: string[];
|
|
70
|
-
requiredFields: string[];
|
|
71
|
-
secretRef?: string;
|
|
72
|
-
};
|
|
73
|
-
export type SealedSecretOperationPayload = {
|
|
74
|
-
schemaVersion: 'treeseed.sealed-secret-operation-payload/v1';
|
|
75
|
-
leaseId: string;
|
|
76
|
-
teamId: string;
|
|
77
|
-
operationCorrelationId: string;
|
|
78
|
-
hostedBinding: HostedSecretOperationBinding;
|
|
79
|
-
algorithm: typeof SERVICE_VAULT_KEY_AGREEMENT;
|
|
80
|
-
ciphertext: string;
|
|
81
|
-
};
|
|
82
|
-
export type SecretOperationCredentialBundle = {
|
|
83
|
-
schemaVersion: 'treeseed.secret-operation-credential-bundle/v1';
|
|
84
|
-
leaseId: string;
|
|
85
|
-
teamId: string;
|
|
86
|
-
operationCorrelationId: string;
|
|
87
|
-
hostedBinding: HostedSecretOperationBinding;
|
|
88
|
-
materials: Array<{
|
|
89
|
-
requestId: string;
|
|
90
|
-
connectionId: string;
|
|
91
|
-
credentialProfileId: string;
|
|
92
|
-
values: Record<string, string>;
|
|
93
|
-
}>;
|
|
94
|
-
};
|
|
95
|
-
export type ServiceVaultAssociatedData = {
|
|
96
|
-
teamId: string;
|
|
97
|
-
connectionId: string;
|
|
98
|
-
credentialProfileId: string;
|
|
99
|
-
field: string;
|
|
100
|
-
purpose: string;
|
|
101
|
-
version: number;
|
|
102
|
-
};
|
|
103
|
-
export declare function containsForbiddenPlaintextSecretMaterial(value: unknown, path?: string): string[];
|
|
104
|
-
export declare function canonicalServiceVaultAssociatedData(input: ServiceVaultAssociatedData): string;
|
|
105
|
-
export declare function canonicalHostedSecretOperationBinding(input: HostedSecretOperationBinding): string;
|
|
106
|
-
export declare function validateHostedSecretOperationBinding(value: unknown): value is HostedSecretOperationBinding;
|
|
107
|
-
export declare function validateSealedSecretOperationPayload(value: unknown): value is SealedSecretOperationPayload;
|
|
108
|
-
export declare function validateEncryptedCredentialEnvelope(value: unknown): value is EncryptedCredentialEnvelope;
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
const SERVICE_VAULT_ENCRYPTION_VERSION = "service-vault-v1";
|
|
2
|
-
const SERVICE_VAULT_AEAD_ALGORITHM = "xchacha20-poly1305-ietf";
|
|
3
|
-
const SERVICE_VAULT_KEY_AGREEMENT = "x25519-sealed-box";
|
|
4
|
-
const SERVICE_VAULT_KDF = "argon2id";
|
|
5
|
-
const SECRET_OPERATION_PURPOSES = [
|
|
6
|
-
"provider-connection-validation",
|
|
7
|
-
"remote-git-publication",
|
|
8
|
-
"workflow-dispatch",
|
|
9
|
-
"workflow-configuration",
|
|
10
|
-
"hosted-topology-plan",
|
|
11
|
-
"hosted-topology-apply",
|
|
12
|
-
"hosted-topology-readback",
|
|
13
|
-
"hosted-topology-rollback"
|
|
14
|
-
];
|
|
15
|
-
const FORBIDDEN_SECRET_KEYS = /(?:passphrase|password|plaintext|derivedKey|privateKey|apiToken|accessToken|secretValue|credentialValue)$/iu;
|
|
16
|
-
function containsForbiddenPlaintextSecretMaterial(value, path = "") {
|
|
17
|
-
if (!value || typeof value !== "object") return [];
|
|
18
|
-
const failures = [];
|
|
19
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
20
|
-
const nextPath = path ? `${path}.${key}` : key;
|
|
21
|
-
if (FORBIDDEN_SECRET_KEYS.test(key) && typeof entry === "string" && entry.trim()) failures.push(nextPath);
|
|
22
|
-
if (entry && typeof entry === "object") failures.push(...containsForbiddenPlaintextSecretMaterial(entry, nextPath));
|
|
23
|
-
}
|
|
24
|
-
return failures;
|
|
25
|
-
}
|
|
26
|
-
function canonicalServiceVaultAssociatedData(input) {
|
|
27
|
-
return JSON.stringify({
|
|
28
|
-
teamId: input.teamId,
|
|
29
|
-
connectionId: input.connectionId,
|
|
30
|
-
credentialProfileId: input.credentialProfileId,
|
|
31
|
-
field: input.field,
|
|
32
|
-
purpose: input.purpose,
|
|
33
|
-
version: input.version
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
const digest = /^sha256:[a-f0-9]{64}$/u;
|
|
37
|
-
const identifier = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
|
|
38
|
-
function canonicalHostedSecretOperationBinding(input) {
|
|
39
|
-
return JSON.stringify({
|
|
40
|
-
subjectType: input.subjectType,
|
|
41
|
-
subjectDigest: input.subjectDigest,
|
|
42
|
-
deploymentId: input.deploymentId,
|
|
43
|
-
stackId: input.stackId,
|
|
44
|
-
environment: input.environment
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
function validateHostedSecretOperationBinding(value) {
|
|
48
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
49
|
-
const binding = value;
|
|
50
|
-
return ["declaration", "plan", "rollback"].includes(String(binding.subjectType)) && digest.test(String(binding.subjectDigest)) && identifier.test(String(binding.deploymentId)) && identifier.test(String(binding.stackId)) && ["staging", "production"].includes(String(binding.environment));
|
|
51
|
-
}
|
|
52
|
-
function validateSealedSecretOperationPayload(value) {
|
|
53
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
54
|
-
const payload = value;
|
|
55
|
-
return payload.schemaVersion === "treeseed.sealed-secret-operation-payload/v1" && payload.algorithm === SERVICE_VAULT_KEY_AGREEMENT && [payload.leaseId, payload.teamId, payload.operationCorrelationId, payload.ciphertext].every((field) => typeof field === "string" && field.length > 0) && validateHostedSecretOperationBinding(payload.hostedBinding);
|
|
56
|
-
}
|
|
57
|
-
function validateEncryptedCredentialEnvelope(value) {
|
|
58
|
-
if (!value || typeof value !== "object") return false;
|
|
59
|
-
const envelope = value;
|
|
60
|
-
return envelope.version === SERVICE_VAULT_ENCRYPTION_VERSION && envelope.algorithm === SERVICE_VAULT_AEAD_ALGORITHM && [envelope.ciphertext, envelope.nonce, envelope.wrappedKey, envelope.wrappedKeyNonce, envelope.associatedData, envelope.associatedDataDigest, envelope.fingerprint].every((field) => typeof field === "string" && field.length > 0);
|
|
61
|
-
}
|
|
62
|
-
export {
|
|
63
|
-
SECRET_OPERATION_PURPOSES,
|
|
64
|
-
SERVICE_VAULT_AEAD_ALGORITHM,
|
|
65
|
-
SERVICE_VAULT_ENCRYPTION_VERSION,
|
|
66
|
-
SERVICE_VAULT_KDF,
|
|
67
|
-
SERVICE_VAULT_KEY_AGREEMENT,
|
|
68
|
-
canonicalHostedSecretOperationBinding,
|
|
69
|
-
canonicalServiceVaultAssociatedData,
|
|
70
|
-
containsForbiddenPlaintextSecretMaterial,
|
|
71
|
-
validateEncryptedCredentialEnvelope,
|
|
72
|
-
validateHostedSecretOperationBinding,
|
|
73
|
-
validateSealedSecretOperationPayload
|
|
74
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type EncryptedCredentialEnvelope, type EncryptedPrivateKeyEnvelope, type TeamVaultGrantEnvelope } from '@treeseed/sdk/secrets-capability';
|
|
2
|
-
export declare function createServiceVaultKey(): Promise<Uint8Array>;
|
|
3
|
-
export declare function createServiceVaultUserKeyPair(): Promise<{
|
|
4
|
-
publicKey: string;
|
|
5
|
-
privateKey: Uint8Array;
|
|
6
|
-
}>;
|
|
7
|
-
export declare function encryptServiceVaultPrivateKey(privateKey: Uint8Array, publicKey: string, passphrase: string, options?: {
|
|
8
|
-
opsLimit?: number;
|
|
9
|
-
memLimit?: number;
|
|
10
|
-
}): Promise<EncryptedPrivateKeyEnvelope>;
|
|
11
|
-
export declare function decryptServiceVaultPrivateKey(envelope: EncryptedPrivateKeyEnvelope, passphrase: string): Promise<Uint8Array>;
|
|
12
|
-
export declare function createTeamVaultGrant(teamVaultKey: Uint8Array, recipientPublicKey: string): Promise<TeamVaultGrantEnvelope>;
|
|
13
|
-
export declare function openTeamVaultGrant(grant: TeamVaultGrantEnvelope, recipientPrivateKey: Uint8Array): Promise<Uint8Array>;
|
|
14
|
-
export declare function encryptServiceCredential(plaintext: string, teamVaultKey: Uint8Array, associatedData: string): Promise<EncryptedCredentialEnvelope>;
|
|
15
|
-
export declare function decryptServiceCredential(envelope: EncryptedCredentialEnvelope, teamVaultKey: Uint8Array, expectedAssociatedData: string): Promise<string>;
|
|
16
|
-
export declare function rewrapServiceCredential(envelope: EncryptedCredentialEnvelope, currentTeamVaultKey: Uint8Array, replacementTeamVaultKey: Uint8Array): Promise<EncryptedCredentialEnvelope>;
|
|
17
|
-
export declare function sealSecretOperationPayload(values: Record<string, string>, operationPublicKey: string): Promise<string>;
|
|
18
|
-
export declare function encryptGitHubActionsSecret(value: string, providerPublicKey: string): Promise<string>;
|
|
19
|
-
export declare function openSecretOperationPayload(sealedPayload: string, operationPublicKey: string, operationPrivateKey: Uint8Array): Promise<Record<string, string>>;
|
|
20
|
-
export declare function clearServiceVaultKey(value: Uint8Array | undefined): void;
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import sodium from "libsodium-wrappers-sumo";
|
|
2
|
-
import {
|
|
3
|
-
SERVICE_VAULT_AEAD_ALGORITHM,
|
|
4
|
-
SERVICE_VAULT_ENCRYPTION_VERSION,
|
|
5
|
-
SERVICE_VAULT_KDF,
|
|
6
|
-
SERVICE_VAULT_KEY_AGREEMENT
|
|
7
|
-
} from "@treeseed/sdk/secrets-capability";
|
|
8
|
-
const b64 = () => sodium.base64_variants.ORIGINAL;
|
|
9
|
-
const encode = (value) => sodium.to_base64(value, b64());
|
|
10
|
-
const decode = (value) => sodium.from_base64(value, b64());
|
|
11
|
-
const hash = (value, key = null) => encode(sodium.crypto_generichash(32, value, key));
|
|
12
|
-
async function ready() {
|
|
13
|
-
await sodium.ready;
|
|
14
|
-
return sodium;
|
|
15
|
-
}
|
|
16
|
-
function validKey(value, length, label) {
|
|
17
|
-
if (!(value instanceof Uint8Array) || value.length !== length) throw new Error(`${label} is invalid.`);
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
async function createServiceVaultKey() {
|
|
21
|
-
const crypto = await ready();
|
|
22
|
-
return crypto.randombytes_buf(crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
|
|
23
|
-
}
|
|
24
|
-
async function createServiceVaultUserKeyPair() {
|
|
25
|
-
const crypto = await ready(), pair = crypto.crypto_box_keypair();
|
|
26
|
-
return { publicKey: encode(pair.publicKey), privateKey: pair.privateKey };
|
|
27
|
-
}
|
|
28
|
-
async function encryptServiceVaultPrivateKey(privateKey, publicKey, passphrase, options = {}) {
|
|
29
|
-
const crypto = await ready();
|
|
30
|
-
validKey(privateKey, crypto.crypto_box_SECRETKEYBYTES, "Service-vault private key");
|
|
31
|
-
validKey(decode(publicKey), crypto.crypto_box_PUBLICKEYBYTES, "Service-vault public key");
|
|
32
|
-
if (passphrase.length < 12) throw new Error("Service-vault passphrase must contain at least 12 characters.");
|
|
33
|
-
const salt = crypto.randombytes_buf(crypto.crypto_pwhash_SALTBYTES);
|
|
34
|
-
const opsLimit = options.opsLimit ?? crypto.crypto_pwhash_OPSLIMIT_MODERATE;
|
|
35
|
-
const memLimit = options.memLimit ?? crypto.crypto_pwhash_MEMLIMIT_MODERATE;
|
|
36
|
-
const key = crypto.crypto_pwhash(
|
|
37
|
-
crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
|
|
38
|
-
passphrase,
|
|
39
|
-
salt,
|
|
40
|
-
opsLimit,
|
|
41
|
-
memLimit,
|
|
42
|
-
crypto.crypto_pwhash_ALG_ARGON2ID13
|
|
43
|
-
);
|
|
44
|
-
const nonce = crypto.randombytes_buf(crypto.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
|
45
|
-
try {
|
|
46
|
-
return {
|
|
47
|
-
version: SERVICE_VAULT_ENCRYPTION_VERSION,
|
|
48
|
-
algorithm: SERVICE_VAULT_AEAD_ALGORITHM,
|
|
49
|
-
kdf: { algorithm: SERVICE_VAULT_KDF, opsLimit, memLimit, salt: encode(salt) },
|
|
50
|
-
nonce: encode(nonce),
|
|
51
|
-
ciphertext: encode(crypto.crypto_aead_xchacha20poly1305_ietf_encrypt(privateKey, publicKey, null, nonce, key)),
|
|
52
|
-
publicKey
|
|
53
|
-
};
|
|
54
|
-
} finally {
|
|
55
|
-
crypto.memzero(key);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
async function decryptServiceVaultPrivateKey(envelope, passphrase) {
|
|
59
|
-
const crypto = await ready();
|
|
60
|
-
if (envelope.version !== SERVICE_VAULT_ENCRYPTION_VERSION || envelope.algorithm !== SERVICE_VAULT_AEAD_ALGORITHM || envelope.kdf.algorithm !== SERVICE_VAULT_KDF) throw new Error("Service-vault private-key envelope is unsupported.");
|
|
61
|
-
if (!Number.isSafeInteger(envelope.kdf.opsLimit) || !Number.isSafeInteger(envelope.kdf.memLimit) || envelope.kdf.opsLimit < crypto.crypto_pwhash_OPSLIMIT_INTERACTIVE || envelope.kdf.opsLimit > crypto.crypto_pwhash_OPSLIMIT_SENSITIVE || envelope.kdf.memLimit < crypto.crypto_pwhash_MEMLIMIT_INTERACTIVE || envelope.kdf.memLimit > crypto.crypto_pwhash_MEMLIMIT_SENSITIVE)
|
|
62
|
-
throw new Error("Service-vault private-key KDF parameters are invalid.");
|
|
63
|
-
const key = crypto.crypto_pwhash(
|
|
64
|
-
crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
|
|
65
|
-
passphrase,
|
|
66
|
-
decode(envelope.kdf.salt),
|
|
67
|
-
envelope.kdf.opsLimit,
|
|
68
|
-
envelope.kdf.memLimit,
|
|
69
|
-
crypto.crypto_pwhash_ALG_ARGON2ID13
|
|
70
|
-
);
|
|
71
|
-
try {
|
|
72
|
-
return validKey(crypto.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
|
73
|
-
null,
|
|
74
|
-
decode(envelope.ciphertext),
|
|
75
|
-
envelope.publicKey,
|
|
76
|
-
decode(envelope.nonce),
|
|
77
|
-
key
|
|
78
|
-
), crypto.crypto_box_SECRETKEYBYTES, "Decrypted service-vault private key");
|
|
79
|
-
} catch {
|
|
80
|
-
throw new Error("Service-vault passphrase or private-key envelope is invalid.");
|
|
81
|
-
} finally {
|
|
82
|
-
crypto.memzero(key);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
async function createTeamVaultGrant(teamVaultKey, recipientPublicKey) {
|
|
86
|
-
const crypto = await ready();
|
|
87
|
-
validKey(teamVaultKey, crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES, "Team vault key");
|
|
88
|
-
const publicKey = validKey(decode(recipientPublicKey), crypto.crypto_box_PUBLICKEYBYTES, "Recipient public key");
|
|
89
|
-
return {
|
|
90
|
-
version: SERVICE_VAULT_ENCRYPTION_VERSION,
|
|
91
|
-
algorithm: SERVICE_VAULT_KEY_AGREEMENT,
|
|
92
|
-
recipientPublicKey,
|
|
93
|
-
wrappedTeamVaultKey: encode(crypto.crypto_box_seal(teamVaultKey, publicKey))
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
async function openTeamVaultGrant(grant, recipientPrivateKey) {
|
|
97
|
-
const crypto = await ready();
|
|
98
|
-
if (grant.version !== SERVICE_VAULT_ENCRYPTION_VERSION || grant.algorithm !== SERVICE_VAULT_KEY_AGREEMENT)
|
|
99
|
-
throw new Error("Team vault grant is unsupported.");
|
|
100
|
-
const publicKey = validKey(decode(grant.recipientPublicKey), crypto.crypto_box_PUBLICKEYBYTES, "Recipient public key");
|
|
101
|
-
validKey(recipientPrivateKey, crypto.crypto_box_SECRETKEYBYTES, "Recipient private key");
|
|
102
|
-
try {
|
|
103
|
-
return validKey(
|
|
104
|
-
crypto.crypto_box_seal_open(decode(grant.wrappedTeamVaultKey), publicKey, recipientPrivateKey),
|
|
105
|
-
crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
|
|
106
|
-
"Unwrapped team vault key"
|
|
107
|
-
);
|
|
108
|
-
} catch {
|
|
109
|
-
throw new Error("Team vault grant cannot be opened by this user key.");
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
async function encryptServiceCredential(plaintext, teamVaultKey, associatedData) {
|
|
113
|
-
const crypto = await ready();
|
|
114
|
-
validKey(teamVaultKey, crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES, "Team vault key");
|
|
115
|
-
if (!plaintext || !associatedData) throw new Error("Credential value and associated data are required.");
|
|
116
|
-
const dataKey = crypto.crypto_aead_xchacha20poly1305_ietf_keygen();
|
|
117
|
-
const nonce = crypto.randombytes_buf(crypto.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
|
118
|
-
const wrappedKeyNonce = crypto.randombytes_buf(crypto.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
|
119
|
-
try {
|
|
120
|
-
return {
|
|
121
|
-
version: SERVICE_VAULT_ENCRYPTION_VERSION,
|
|
122
|
-
algorithm: SERVICE_VAULT_AEAD_ALGORITHM,
|
|
123
|
-
ciphertext: encode(crypto.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, associatedData, null, nonce, dataKey)),
|
|
124
|
-
nonce: encode(nonce),
|
|
125
|
-
wrappedKey: encode(crypto.crypto_aead_xchacha20poly1305_ietf_encrypt(dataKey, associatedData, null, wrappedKeyNonce, teamVaultKey)),
|
|
126
|
-
wrappedKeyNonce: encode(wrappedKeyNonce),
|
|
127
|
-
associatedData,
|
|
128
|
-
associatedDataDigest: hash(associatedData),
|
|
129
|
-
fingerprint: hash(plaintext, teamVaultKey)
|
|
130
|
-
};
|
|
131
|
-
} finally {
|
|
132
|
-
crypto.memzero(dataKey);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
async function decryptServiceCredential(envelope, teamVaultKey, expectedAssociatedData) {
|
|
136
|
-
const crypto = await ready();
|
|
137
|
-
validKey(teamVaultKey, crypto.crypto_aead_xchacha20poly1305_ietf_KEYBYTES, "Team vault key");
|
|
138
|
-
if (envelope.version !== SERVICE_VAULT_ENCRYPTION_VERSION || envelope.algorithm !== SERVICE_VAULT_AEAD_ALGORITHM || envelope.associatedData !== expectedAssociatedData || envelope.associatedDataDigest !== hash(expectedAssociatedData))
|
|
139
|
-
throw new Error("Credential envelope binding is invalid.");
|
|
140
|
-
let dataKey;
|
|
141
|
-
try {
|
|
142
|
-
dataKey = crypto.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
|
143
|
-
null,
|
|
144
|
-
decode(envelope.wrappedKey),
|
|
145
|
-
expectedAssociatedData,
|
|
146
|
-
decode(envelope.wrappedKeyNonce),
|
|
147
|
-
teamVaultKey
|
|
148
|
-
);
|
|
149
|
-
return crypto.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
|
150
|
-
null,
|
|
151
|
-
decode(envelope.ciphertext),
|
|
152
|
-
expectedAssociatedData,
|
|
153
|
-
decode(envelope.nonce),
|
|
154
|
-
dataKey,
|
|
155
|
-
"text"
|
|
156
|
-
);
|
|
157
|
-
} catch {
|
|
158
|
-
throw new Error("Credential envelope cannot be decrypted.");
|
|
159
|
-
} finally {
|
|
160
|
-
if (dataKey) crypto.memzero(dataKey);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
async function rewrapServiceCredential(envelope, currentTeamVaultKey, replacementTeamVaultKey) {
|
|
164
|
-
const plaintext = await decryptServiceCredential(envelope, currentTeamVaultKey, envelope.associatedData);
|
|
165
|
-
return encryptServiceCredential(plaintext, replacementTeamVaultKey, envelope.associatedData);
|
|
166
|
-
}
|
|
167
|
-
async function sealSecretOperationPayload(values, operationPublicKey) {
|
|
168
|
-
const crypto = await ready();
|
|
169
|
-
if (!Object.keys(values).length || Object.values(values).some((value) => typeof value !== "string" || !value))
|
|
170
|
-
throw new Error("Secret operation payload must contain only non-empty string values.");
|
|
171
|
-
const payload = JSON.stringify(Object.fromEntries(Object.entries(values).sort(([left], [right]) => left.localeCompare(right))));
|
|
172
|
-
return encode(crypto.crypto_box_seal(payload, validKey(decode(operationPublicKey), crypto.crypto_box_PUBLICKEYBYTES, "Operation public key")));
|
|
173
|
-
}
|
|
174
|
-
async function encryptGitHubActionsSecret(value, providerPublicKey) {
|
|
175
|
-
const crypto = await ready();
|
|
176
|
-
if (!value) throw new Error("GitHub Actions secret value is required.");
|
|
177
|
-
return encode(crypto.crypto_box_seal(
|
|
178
|
-
value,
|
|
179
|
-
validKey(decode(providerPublicKey), crypto.crypto_box_PUBLICKEYBYTES, "GitHub public key")
|
|
180
|
-
));
|
|
181
|
-
}
|
|
182
|
-
async function openSecretOperationPayload(sealedPayload, operationPublicKey, operationPrivateKey) {
|
|
183
|
-
const crypto = await ready();
|
|
184
|
-
try {
|
|
185
|
-
const plaintext = crypto.crypto_box_seal_open(decode(sealedPayload), decode(operationPublicKey), operationPrivateKey, "text");
|
|
186
|
-
const parsed = JSON.parse(plaintext);
|
|
187
|
-
if (!parsed || Array.isArray(parsed) || Object.values(parsed).some((value) => typeof value !== "string" || !value)) throw new Error();
|
|
188
|
-
return parsed;
|
|
189
|
-
} catch {
|
|
190
|
-
throw new Error("Secret operation payload cannot be opened.");
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function clearServiceVaultKey(value) {
|
|
194
|
-
value?.fill(0);
|
|
195
|
-
}
|
|
196
|
-
export {
|
|
197
|
-
clearServiceVaultKey,
|
|
198
|
-
createServiceVaultKey,
|
|
199
|
-
createServiceVaultUserKeyPair,
|
|
200
|
-
createTeamVaultGrant,
|
|
201
|
-
decryptServiceCredential,
|
|
202
|
-
decryptServiceVaultPrivateKey,
|
|
203
|
-
encryptGitHubActionsSecret,
|
|
204
|
-
encryptServiceCredential,
|
|
205
|
-
encryptServiceVaultPrivateKey,
|
|
206
|
-
openSecretOperationPayload,
|
|
207
|
-
openTeamVaultGrant,
|
|
208
|
-
rewrapServiceCredential,
|
|
209
|
-
sealSecretOperationPayload
|
|
210
|
-
};
|