@the-ai-company/cbio-node-runtime 1.55.1 → 1.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -29
- package/dist/clients/agent/client.d.ts +2 -2
- package/dist/clients/agent/contracts.d.ts +3 -2
- package/dist/clients/owner/client.d.ts +8 -11
- package/dist/clients/owner/client.js +61 -43
- package/dist/clients/owner/client.js.map +1 -1
- package/dist/clients/owner/contracts.d.ts +23 -10
- package/dist/clients/owner/index.d.ts +1 -1
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/owner-session.d.ts +26 -0
- package/dist/runtime/owner-session.js +89 -0
- package/dist/runtime/owner-session.js.map +1 -0
- package/dist/vault-core/contracts.d.ts +48 -35
- package/dist/vault-core/contracts.js.map +1 -1
- package/dist/vault-core/core.d.ts +16 -21
- package/dist/vault-core/core.js +278 -159
- package/dist/vault-core/core.js.map +1 -1
- package/dist/vault-core/defaults.d.ts +8 -20
- package/dist/vault-core/defaults.js +14 -37
- package/dist/vault-core/defaults.js.map +1 -1
- package/dist/vault-core/index.d.ts +3 -3
- package/dist/vault-core/index.js +1 -1
- package/dist/vault-core/index.js.map +1 -1
- package/dist/vault-core/persistence.d.ts +8 -6
- package/dist/vault-core/persistence.js +17 -9
- package/dist/vault-core/persistence.js.map +1 -1
- package/dist/vault-core/ports.d.ts +8 -20
- package/dist/vault-ingress/defaults.d.ts +2 -2
- package/dist/vault-ingress/index.d.ts +14 -33
- package/dist/vault-ingress/index.js +18 -31
- package/dist/vault-ingress/index.js.map +1 -1
- package/dist/vault-ingress/remote-transport.d.ts +2 -2
- package/dist/vault-ingress/remote-transport.js.map +1 -1
- package/docs/MIGRATION-1.51.md +4 -1
- package/docs/REFERENCE.md +44 -30
- package/docs/api/README.md +7 -2
- package/docs/api/classes/IdentityError.md +1 -1
- package/docs/api/classes/OwnerClientError.md +1 -1
- package/docs/api/classes/VaultCore.md +34 -94
- package/docs/api/classes/VaultCoreError.md +1 -1
- package/docs/api/enumerations/IdentityErrorCode.md +1 -1
- package/docs/api/enumerations/OwnerClientErrorCode.md +1 -1
- package/docs/api/functions/createAgentClient.md +1 -1
- package/docs/api/functions/createIdentity.md +1 -1
- package/docs/api/functions/createOwnerHttpFlowBoundary.md +1 -1
- package/docs/api/functions/createOwnerSession.md +37 -0
- package/docs/api/functions/createPersistentVaultCoreDependencies.md +1 -1
- package/docs/api/functions/createStandardAcquireBoundary.md +1 -1
- package/docs/api/functions/createStandardDispatchBoundary.md +1 -1
- package/docs/api/functions/createVault.md +1 -1
- package/docs/api/functions/createVaultClient.md +1 -1
- package/docs/api/functions/createVaultCore.md +1 -1
- package/docs/api/functions/createVaultCoreDependencies.md +1 -1
- package/docs/api/functions/createVaultService.md +1 -1
- package/docs/api/functions/createWorkspaceStorage.md +1 -1
- package/docs/api/functions/deriveIdentityId.md +1 -1
- package/docs/api/functions/deriveVaultWorkingKeyFromPassword.md +1 -1
- package/docs/api/functions/getDefaultWorkspaceDir.md +1 -1
- package/docs/api/functions/handleVaultAgentControlHttp.md +1 -1
- package/docs/api/functions/handleVaultHttpDispatch.md +1 -1
- package/docs/api/functions/initializeVaultCustody.md +1 -1
- package/docs/api/functions/listVaults.md +1 -1
- package/docs/api/functions/readVaultProfile.md +1 -1
- package/docs/api/functions/recoverVault.md +1 -1
- package/docs/api/functions/recoverVaultWorkingKey.md +1 -1
- package/docs/api/functions/restoreIdentity.md +1 -1
- package/docs/api/functions/updateVaultMetadata.md +1 -1
- package/docs/api/functions/wrapVaultCoreAsVaultService.md +1 -1
- package/docs/api/functions/writeVaultProfile.md +1 -1
- package/docs/api/interfaces/AgentClient.md +5 -5
- package/docs/api/interfaces/AgentDispatchIntent.md +1 -1
- package/docs/api/interfaces/AgentDispatchTransport.md +5 -5
- package/docs/api/interfaces/AgentIdentity.md +1 -1
- package/docs/api/interfaces/AgentSigner.md +1 -1
- package/docs/api/interfaces/AgentSubmitCapabilityRequestInput.md +1 -1
- package/docs/api/interfaces/CbioRuntime.md +39 -1
- package/docs/api/interfaces/CreateAgentClientOptions.md +1 -1
- package/docs/api/interfaces/CreateIdentityOptions.md +1 -1
- package/docs/api/interfaces/CreateOwnerSessionOptions.md +245 -0
- package/docs/api/interfaces/CreatePersistentVaultCoreDependenciesOptions.md +1 -1
- package/docs/api/interfaces/CreateVaultClientOptions.md +1 -1
- package/docs/api/interfaces/CreateVaultOptions.md +1 -1
- package/docs/api/interfaces/CreatedVault.md +1 -1
- package/docs/api/interfaces/DefaultPolicyEngineOptions.md +1 -1
- package/docs/api/interfaces/IStorageProvider.md +1 -1
- package/docs/api/interfaces/InitializeVaultCustodyOptions.md +1 -1
- package/docs/api/interfaces/InitializedVaultCustody.md +1 -1
- package/docs/api/interfaces/OwnerAgentProvisionResult.md +1 -1
- package/docs/api/interfaces/OwnerDefineSecretTargetsInput.md +1 -1
- package/docs/api/interfaces/OwnerSecretTargetBinding.md +1 -1
- package/docs/api/interfaces/OwnerSensitiveActionConfirmation.md +1 -1
- package/docs/api/interfaces/OwnerSensitiveActionContext.md +1 -1
- package/docs/api/interfaces/OwnerSession.md +95 -0
- package/docs/api/interfaces/OwnerStoreSecretInput.md +1 -1
- package/docs/api/interfaces/OwnerWriteSecretInput.md +1 -1
- package/docs/api/interfaces/RecoverVaultOptions.md +5 -1
- package/docs/api/interfaces/RecoveredVault.md +1 -1
- package/docs/api/interfaces/RestoreIdentityOptions.md +1 -1
- package/docs/api/interfaces/Signer.md +1 -1
- package/docs/api/interfaces/VaultApproveCapabilityRequestInput.md +1 -1
- package/docs/api/interfaces/VaultApproveDispatchInput.md +1 -1
- package/docs/api/interfaces/VaultAuditQueryInput.md +1 -1
- package/docs/api/interfaces/VaultClient.md +41 -77
- package/docs/api/interfaces/VaultCoreDependenciesOptions.md +1 -1
- package/docs/api/interfaces/VaultCreateAgentInput.md +1 -1
- package/docs/api/interfaces/VaultDeleteSecretInput.md +1 -1
- package/docs/api/interfaces/VaultExportSecretInput.md +1 -1
- package/docs/api/interfaces/VaultGrantCapabilityInput.md +25 -1
- package/docs/api/interfaces/VaultGrantCapabilityRequest.md +23 -0
- package/docs/api/interfaces/VaultIdentity.md +1 -1
- package/docs/api/interfaces/VaultImportAgentInput.md +1 -1
- package/docs/api/interfaces/VaultIssueSessionTokenInput.md +1 -1
- package/docs/api/interfaces/VaultListAgentsInput.md +1 -1
- package/docs/api/interfaces/VaultListCapabilitiesInput.md +1 -1
- package/docs/api/interfaces/VaultListSecretsInput.md +1 -1
- package/docs/api/interfaces/VaultMetadata.md +1 -1
- package/docs/api/interfaces/VaultObject.md +1 -1
- package/docs/api/interfaces/VaultProfile.md +1 -1
- package/docs/api/interfaces/VaultReadAgentPrivateKeyInput.md +1 -1
- package/docs/api/interfaces/VaultReadSecretPlaintextInput.md +1 -1
- package/docs/api/interfaces/VaultRegisterFlowInput.md +1 -1
- package/docs/api/interfaces/VaultRevokeCapabilityInput.md +1 -1
- package/docs/api/interfaces/VaultRevokeSessionTokenInput.md +1 -1
- package/docs/api/interfaces/VaultSigner.md +1 -1
- package/docs/api/interfaces/VaultSubmitCapabilityRequestInput.md +1 -1
- package/docs/api/interfaces/VaultUpdateAgentInput.md +1 -1
- package/docs/api/type-aliases/AgentCapabilityEnvelope.md +1 -1
- package/docs/api/type-aliases/AgentVisibleSecretRecord.md +1 -1
- package/docs/api/type-aliases/CbioRuntimeModule.md +1 -1
- package/docs/api/type-aliases/OwnerGrantCapabilityInput.md +7 -0
- package/docs/api/variables/DEFAULT_VAULT_KEY_CUSTODY_BLOB_KEY.md +1 -1
- package/docs/es/README.md +6 -9
- package/docs/fr/README.md +6 -10
- package/docs/ja/README.md +6 -9
- package/docs/ko/README.md +6 -9
- package/docs/pt/README.md +6 -9
- package/docs/zh/README.md +101 -42
- package/package.json +1 -1
package/dist/vault-core/core.js
CHANGED
|
@@ -2,6 +2,7 @@ import { AuditAction, AuditOutcome, DispatchStatus, } from "./contracts.js";
|
|
|
2
2
|
import { VaultCoreError } from "./errors.js";
|
|
3
3
|
import { verifySignature } from "../protocol/crypto.js";
|
|
4
4
|
import { getAgentToolbox } from "./tool-metadata.js";
|
|
5
|
+
const VAULT_MASTER_ID = "vault-master";
|
|
5
6
|
function toAuditEntry(deps, actor, action, outcome, detail, options) {
|
|
6
7
|
return {
|
|
7
8
|
entryId: deps.ids.newAuditEntryId(),
|
|
@@ -35,11 +36,37 @@ function buildSecretRecord(deps, command) {
|
|
|
35
36
|
updatedAt: now,
|
|
36
37
|
};
|
|
37
38
|
}
|
|
39
|
+
function normalizeScopeTarget(targetUrl) {
|
|
40
|
+
try {
|
|
41
|
+
const parsed = new URL(targetUrl);
|
|
42
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
parsed.protocol = parsed.protocol.toLowerCase();
|
|
46
|
+
parsed.hostname = parsed.hostname.toLowerCase();
|
|
47
|
+
parsed.hash = "";
|
|
48
|
+
parsed.search = "";
|
|
49
|
+
if ((parsed.protocol === "https:" && parsed.port === "443") || (parsed.protocol === "http:" && parsed.port === "80")) {
|
|
50
|
+
parsed.port = "";
|
|
51
|
+
}
|
|
52
|
+
parsed.pathname = parsed.pathname || "/";
|
|
53
|
+
return parsed.toString();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
38
59
|
function isScopeMatch(scope, targetUrl) {
|
|
60
|
+
const normalizedTarget = normalizeScopeTarget(targetUrl);
|
|
61
|
+
if (!normalizedTarget) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
39
64
|
if (scope.endsWith("*")) {
|
|
40
|
-
|
|
65
|
+
const normalizedPrefix = normalizeScopeTarget(scope.slice(0, -1));
|
|
66
|
+
return normalizedPrefix ? normalizedTarget.startsWith(normalizedPrefix) : false;
|
|
41
67
|
}
|
|
42
|
-
|
|
68
|
+
const normalizedScope = normalizeScopeTarget(scope);
|
|
69
|
+
return normalizedScope === normalizedTarget;
|
|
43
70
|
}
|
|
44
71
|
function createAgentControlBinding(requestId, requestedAt, agentId, action, payload = {}) {
|
|
45
72
|
return JSON.stringify({
|
|
@@ -56,11 +83,137 @@ function createAgentControlBinding(requestId, requestedAt, agentId, action, payl
|
|
|
56
83
|
*/
|
|
57
84
|
export class VaultCore {
|
|
58
85
|
_deps;
|
|
59
|
-
|
|
60
|
-
_pendingCapabilityObservers = new Set();
|
|
86
|
+
_capabilityStateObservers = new Set();
|
|
61
87
|
constructor(_deps) {
|
|
62
88
|
this._deps = _deps;
|
|
63
89
|
}
|
|
90
|
+
_assertOwnerPrincipal(actor, code = "VAULT_AUDIT_DENIED") {
|
|
91
|
+
if (actor.kind !== "owner" || actor.id !== VAULT_MASTER_ID) {
|
|
92
|
+
throw new VaultCoreError("owner access denied", code);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
_stateToGrantedCapability(state) {
|
|
96
|
+
return {
|
|
97
|
+
vaultId: state.vaultId,
|
|
98
|
+
capabilityId: state.capabilityId ?? "",
|
|
99
|
+
agentId: state.agentId,
|
|
100
|
+
secretIds: state.secretIds ? [...state.secretIds] : undefined,
|
|
101
|
+
secretAliases: state.secretAliases ? [...state.secretAliases] : undefined,
|
|
102
|
+
operation: state.operation,
|
|
103
|
+
customFlowId: state.customFlowId,
|
|
104
|
+
scope: state.scope,
|
|
105
|
+
methods: [...state.methods],
|
|
106
|
+
issuedAt: state.issuedAt ?? state.requestedAt,
|
|
107
|
+
expiresAt: state.expiresAt,
|
|
108
|
+
rateLimit: state.rateLimit,
|
|
109
|
+
skipAudit: state.skipAudit,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async _buildAgentCapabilityStates(agentId) {
|
|
113
|
+
return (await this._deps.capabilityStates.list(this._deps.vaultId, agentId)).map((state) => ({
|
|
114
|
+
status: state.status,
|
|
115
|
+
source: state.source,
|
|
116
|
+
agentId: state.agentId,
|
|
117
|
+
requestId: state.requestId,
|
|
118
|
+
capabilityId: state.capabilityId,
|
|
119
|
+
operation: state.operation,
|
|
120
|
+
secretIds: state.secretIds ? [...state.secretIds] : undefined,
|
|
121
|
+
secretAliases: state.secretAliases ? [...state.secretAliases] : undefined,
|
|
122
|
+
customFlowId: state.customFlowId,
|
|
123
|
+
scope: state.scope,
|
|
124
|
+
methods: [...state.methods],
|
|
125
|
+
issuedAt: state.issuedAt,
|
|
126
|
+
requestedAt: state.requestedAt,
|
|
127
|
+
expiresAt: state.expiresAt,
|
|
128
|
+
rateLimit: state.rateLimit,
|
|
129
|
+
skipAudit: state.skipAudit,
|
|
130
|
+
justification: state.justification,
|
|
131
|
+
secretAlias: state.secretAlias,
|
|
132
|
+
targetUrl: state.targetUrl,
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
_isExecutablePendingState(state) {
|
|
136
|
+
return !!(state.requestId && state.targetUrl && state.secretAlias && state.proof);
|
|
137
|
+
}
|
|
138
|
+
async _executePendingCapabilityState(command, mode) {
|
|
139
|
+
if (command.vaultId.value !== this._deps.vaultId.value) {
|
|
140
|
+
throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
|
|
141
|
+
}
|
|
142
|
+
const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
|
|
143
|
+
if (!pending || pending.status !== "PENDING") {
|
|
144
|
+
throw new VaultCoreError("pending capability state not found", "VAULT_REQUEST_NOT_FOUND");
|
|
145
|
+
}
|
|
146
|
+
const issuedAt = this._deps.clock.nowIso();
|
|
147
|
+
const capability = {
|
|
148
|
+
vaultId: this._deps.vaultId,
|
|
149
|
+
agentId: pending.agentId,
|
|
150
|
+
capabilityId: pending.capabilityId ?? this._deps.ids.newCapabilityId(),
|
|
151
|
+
secretIds: pending.secretIds ? [...pending.secretIds] : undefined,
|
|
152
|
+
secretAliases: pending.secretAliases ? [...pending.secretAliases] : (pending.secretAlias ? [pending.secretAlias] : []),
|
|
153
|
+
operation: pending.operation,
|
|
154
|
+
customFlowId: pending.customFlowId,
|
|
155
|
+
scope: pending.targetUrl ?? pending.scope,
|
|
156
|
+
methods: [...pending.methods],
|
|
157
|
+
issuedAt,
|
|
158
|
+
expiresAt: pending.expiresAt,
|
|
159
|
+
rateLimit: pending.rateLimit,
|
|
160
|
+
skipAudit: pending.skipAudit,
|
|
161
|
+
};
|
|
162
|
+
let result;
|
|
163
|
+
if (this._isExecutablePendingState(pending)) {
|
|
164
|
+
result = await this.agentDispatchSecret({
|
|
165
|
+
vaultId: this._deps.vaultId,
|
|
166
|
+
agent: { kind: "agent", id: pending.agentId },
|
|
167
|
+
capability,
|
|
168
|
+
secretAlias: pending.secretAlias === "unknown" ? undefined : pending.secretAlias,
|
|
169
|
+
targetUrl: pending.targetUrl,
|
|
170
|
+
method: pending.methods[0] ?? "POST",
|
|
171
|
+
headers: pending.headers,
|
|
172
|
+
body: pending.body,
|
|
173
|
+
proof: pending.proof,
|
|
174
|
+
requestId: pending.requestId,
|
|
175
|
+
requestedAt: pending.requestedAt,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else if (mode === "grant") {
|
|
179
|
+
result = {
|
|
180
|
+
vaultId: this._deps.vaultId,
|
|
181
|
+
requestId: pending.requestId ?? command.requestId,
|
|
182
|
+
status: DispatchStatus.SUCCEEDED,
|
|
183
|
+
targetUrl: pending.scope,
|
|
184
|
+
method: pending.methods[0] ?? "POST",
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
throw new VaultCoreError("pending capability state is not executable", "VAULT_WRITE_DENIED");
|
|
189
|
+
}
|
|
190
|
+
if (mode === "grant") {
|
|
191
|
+
await this._deps.capabilityStates.upsert({
|
|
192
|
+
...pending,
|
|
193
|
+
capabilityId: capability.capabilityId,
|
|
194
|
+
status: "GRANTED",
|
|
195
|
+
source: "owner_grant",
|
|
196
|
+
issuedAt,
|
|
197
|
+
decidedAt: issuedAt,
|
|
198
|
+
});
|
|
199
|
+
await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `executed and granted capability state ${command.requestId}`, {
|
|
200
|
+
requestId: command.requestId,
|
|
201
|
+
agentId: pending.agentId,
|
|
202
|
+
capabilityId: capability.capabilityId,
|
|
203
|
+
operation: capability.operation,
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
await this._deps.capabilityStates.deleteByRequestId(command.vaultId, command.requestId);
|
|
208
|
+
await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `executed once and deleted capability state ${command.requestId}`, {
|
|
209
|
+
requestId: command.requestId,
|
|
210
|
+
agentId: pending.agentId,
|
|
211
|
+
capabilityId: capability.capabilityId,
|
|
212
|
+
operation: capability.operation,
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
64
217
|
get vaultId() {
|
|
65
218
|
return this._deps.vaultId;
|
|
66
219
|
}
|
|
@@ -110,7 +263,9 @@ export class VaultCore {
|
|
|
110
263
|
}
|
|
111
264
|
}
|
|
112
265
|
async _listVisibleSecretsForAgent(agentId) {
|
|
113
|
-
const capabilities = await this._deps.
|
|
266
|
+
const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, agentId))
|
|
267
|
+
.filter((state) => state.status === "GRANTED")
|
|
268
|
+
.map((state) => this._stateToGrantedCapability(state));
|
|
114
269
|
const capabilityMap = new Map();
|
|
115
270
|
for (const capability of capabilities) {
|
|
116
271
|
for (const alias of capability.secretAliases ?? []) {
|
|
@@ -139,16 +294,10 @@ export class VaultCore {
|
|
|
139
294
|
};
|
|
140
295
|
});
|
|
141
296
|
}
|
|
142
|
-
|
|
143
|
-
this.
|
|
144
|
-
return () => {
|
|
145
|
-
this._pendingObservers.delete(callback);
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
ownerOnPendingCapabilityRequest(callback) {
|
|
149
|
-
this._pendingCapabilityObservers.add(callback);
|
|
297
|
+
ownerOnCapabilityState(callback) {
|
|
298
|
+
this._capabilityStateObservers.add(callback);
|
|
150
299
|
return () => {
|
|
151
|
-
this.
|
|
300
|
+
this._capabilityStateObservers.delete(callback);
|
|
152
301
|
};
|
|
153
302
|
}
|
|
154
303
|
async ownerRegisterAgentIdentity(command) {
|
|
@@ -213,7 +362,13 @@ export class VaultCore {
|
|
|
213
362
|
throw new VaultCoreError("capability id required", "VAULT_IDENTITY_DENIED");
|
|
214
363
|
}
|
|
215
364
|
try {
|
|
216
|
-
await this._deps.
|
|
365
|
+
await this._deps.capabilityStates.upsert({
|
|
366
|
+
...command.capability,
|
|
367
|
+
status: "GRANTED",
|
|
368
|
+
source: "owner_grant",
|
|
369
|
+
requestId: undefined,
|
|
370
|
+
requestedAt: command.capability.issuedAt,
|
|
371
|
+
});
|
|
217
372
|
await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REGISTER_CAPABILITY, AuditOutcome.SUCCEEDED, `capability registered: ${command.capability.capabilityId}`, {
|
|
218
373
|
capabilityId: command.capability.capabilityId,
|
|
219
374
|
operation: command.capability.operation,
|
|
@@ -243,28 +398,27 @@ export class VaultCore {
|
|
|
243
398
|
}
|
|
244
399
|
const pendingRecord = {
|
|
245
400
|
vaultId: this._deps.vaultId,
|
|
401
|
+
status: "PENDING",
|
|
402
|
+
source: "explicit_request",
|
|
246
403
|
requestId: command.requestId,
|
|
247
|
-
requester: command.requester,
|
|
248
404
|
agentId: command.agentId,
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
expiresAt: command.scope.expiresAt,
|
|
257
|
-
},
|
|
405
|
+
operation: command.scope.operation,
|
|
406
|
+
secretAliases: command.scope.secretAliases ? [...command.scope.secretAliases] : [],
|
|
407
|
+
scope: command.scope.scope,
|
|
408
|
+
methods: [...command.scope.methods],
|
|
409
|
+
rateLimit: command.scope.rateLimit,
|
|
410
|
+
skipAudit: command.scope.skipAudit,
|
|
411
|
+
expiresAt: command.scope.expiresAt,
|
|
258
412
|
justification: command.justification,
|
|
259
413
|
requestedAt: command.requestedAt,
|
|
260
414
|
};
|
|
261
|
-
await this._deps.
|
|
262
|
-
for (const observer of this.
|
|
415
|
+
await this._deps.capabilityStates.upsert(pendingRecord);
|
|
416
|
+
for (const observer of this._capabilityStateObservers) {
|
|
263
417
|
try {
|
|
264
418
|
observer(pendingRecord);
|
|
265
419
|
}
|
|
266
420
|
catch (error) {
|
|
267
|
-
console.error("VaultCore: error in
|
|
421
|
+
console.error("VaultCore: error in capability state observer:", error);
|
|
268
422
|
}
|
|
269
423
|
}
|
|
270
424
|
await this._appendAudit(toAuditEntry(this._deps, command.requester, AuditAction.SUBMIT_CAPABILITY_REQUEST, AuditOutcome.PENDING, `capability request submitted for agent: ${command.agentId}`, {
|
|
@@ -278,7 +432,8 @@ export class VaultCore {
|
|
|
278
432
|
if (vaultId.value !== this._deps.vaultId.value) {
|
|
279
433
|
throw new VaultCoreError("capability lookup vault mismatch", "VAULT_IDENTITY_DENIED");
|
|
280
434
|
}
|
|
281
|
-
|
|
435
|
+
const state = await this._deps.capabilityStates.getByCapabilityId(vaultId, agentId, capabilityId);
|
|
436
|
+
return state && state.status === "GRANTED" ? this._stateToGrantedCapability(state) : null;
|
|
282
437
|
}
|
|
283
438
|
async ownerRegisterCustomFlow(command) {
|
|
284
439
|
if (command.vaultId.value !== this._deps.vaultId.value) {
|
|
@@ -481,8 +636,14 @@ export class VaultCore {
|
|
|
481
636
|
if (!agentRecord) {
|
|
482
637
|
return { vaultId: this._deps.vaultId, decision: "deny", reason: "agent not found", secretId: null, executorTarget: null };
|
|
483
638
|
}
|
|
484
|
-
const capabilities = await this._deps.
|
|
485
|
-
|
|
639
|
+
const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, request.agent.id))
|
|
640
|
+
.filter((state) => state.status === "GRANTED")
|
|
641
|
+
.map((state) => this._stateToGrantedCapability(state));
|
|
642
|
+
const requestedCapabilityId = request.capability?.capabilityId;
|
|
643
|
+
const candidateCapabilities = requestedCapabilityId
|
|
644
|
+
? capabilities.filter((cap) => cap.capabilityId === requestedCapabilityId)
|
|
645
|
+
: capabilities;
|
|
646
|
+
const capability = candidateCapabilities.find((cap) => this.isCapabilityMatch(cap, request, record?.secretId.value));
|
|
486
647
|
const executorTarget = record
|
|
487
648
|
? record.targetBindings.find((binding) => binding.targetUrl === request.targetUrl)
|
|
488
649
|
?? record.targetBindings.find((binding) => binding.targetId === request.targetUrl)
|
|
@@ -491,25 +652,31 @@ export class VaultCore {
|
|
|
491
652
|
if (!capability) {
|
|
492
653
|
// It's a discovery case if the agent and secret exist but no capability matches
|
|
493
654
|
const pendingRecord = {
|
|
655
|
+
vaultId: this._deps.vaultId,
|
|
656
|
+
status: "PENDING",
|
|
657
|
+
source: "dispatch_discovery",
|
|
494
658
|
requestId: request.requestId,
|
|
495
659
|
agentId: request.agent.id,
|
|
496
660
|
capabilityId: undefined,
|
|
661
|
+
operation: "dispatch_http",
|
|
662
|
+
secretAliases: request.secretAlias ? [request.secretAlias] : [],
|
|
663
|
+
scope: request.targetUrl,
|
|
664
|
+
methods: [request.method],
|
|
665
|
+
requestedAt: request.requestedAt,
|
|
497
666
|
secretAlias: request.secretAlias ?? "unknown",
|
|
498
667
|
targetUrl: request.targetUrl,
|
|
499
|
-
method: request.method,
|
|
500
668
|
headers: request.headers,
|
|
501
669
|
body: request.body,
|
|
502
|
-
requestedAt: request.requestedAt,
|
|
503
670
|
proof: request.proof,
|
|
504
671
|
};
|
|
505
|
-
await this._deps.
|
|
672
|
+
await this._deps.capabilityStates.upsert(pendingRecord);
|
|
506
673
|
// Notify observers
|
|
507
|
-
for (const observer of this.
|
|
674
|
+
for (const observer of this._capabilityStateObservers) {
|
|
508
675
|
try {
|
|
509
676
|
observer(pendingRecord);
|
|
510
677
|
}
|
|
511
678
|
catch (error) {
|
|
512
|
-
console.error("VaultCore: error in
|
|
679
|
+
console.error("VaultCore: error in capability state observer:", error);
|
|
513
680
|
}
|
|
514
681
|
}
|
|
515
682
|
await this._appendDecisionAudit(request, AuditOutcome.PENDING, "dispatch stalled for manual discovery approval", {
|
|
@@ -524,6 +691,26 @@ export class VaultCore {
|
|
|
524
691
|
executorTarget,
|
|
525
692
|
};
|
|
526
693
|
}
|
|
694
|
+
try {
|
|
695
|
+
await this._deps.policy.authorizeDispatch({
|
|
696
|
+
...request,
|
|
697
|
+
capability,
|
|
698
|
+
}, record);
|
|
699
|
+
}
|
|
700
|
+
catch (error) {
|
|
701
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
702
|
+
await this._appendDecisionAudit(request, AuditOutcome.DENIED, detail, {
|
|
703
|
+
secretAlias: record?.alias.value ?? request.secretAlias,
|
|
704
|
+
secretId: record?.secretId.value,
|
|
705
|
+
});
|
|
706
|
+
return {
|
|
707
|
+
vaultId: this._deps.vaultId,
|
|
708
|
+
decision: "deny",
|
|
709
|
+
reason: detail,
|
|
710
|
+
secretId: record?.secretId ?? null,
|
|
711
|
+
executorTarget,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
527
714
|
// Capability found, proceed
|
|
528
715
|
if (!capability.skipAudit) {
|
|
529
716
|
await this._appendDecisionAudit(request, AuditOutcome.ALLOWED, "dispatch authorized", {
|
|
@@ -585,11 +772,13 @@ export class VaultCore {
|
|
|
585
772
|
};
|
|
586
773
|
}
|
|
587
774
|
async ownerReadAudit(actor, query, request) {
|
|
775
|
+
this._assertOwnerPrincipal(actor, "VAULT_AUDIT_DENIED");
|
|
588
776
|
const entries = await this._deps.audit.query(query);
|
|
589
777
|
await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.READ_AUDIT, AuditOutcome.ALLOWED, "audit queried"));
|
|
590
778
|
return entries;
|
|
591
779
|
}
|
|
592
780
|
async ownerExportSecret(actor, alias, request) {
|
|
781
|
+
this._assertOwnerPrincipal(actor, "VAULT_AUDIT_DENIED");
|
|
593
782
|
try {
|
|
594
783
|
const record = await this._deps.secrets.getByAlias({ value: alias });
|
|
595
784
|
if (!record) {
|
|
@@ -622,10 +811,14 @@ export class VaultCore {
|
|
|
622
811
|
throw error;
|
|
623
812
|
}
|
|
624
813
|
}
|
|
625
|
-
isCapabilityMatch(capability, request) {
|
|
626
|
-
//
|
|
627
|
-
if (request.secretAlias
|
|
628
|
-
|
|
814
|
+
isCapabilityMatch(capability, request, secretId) {
|
|
815
|
+
// Match either alias- or id-based capability grants when a secret is specified.
|
|
816
|
+
if (request.secretAlias) {
|
|
817
|
+
const aliasMatched = capability.secretAliases?.includes(request.secretAlias) ?? false;
|
|
818
|
+
const idMatched = secretId ? (capability.secretIds?.includes(secretId) ?? false) : false;
|
|
819
|
+
if (!aliasMatched && !idMatched) {
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
629
822
|
}
|
|
630
823
|
if (request.method && capability.methods?.length > 0 && !capability.methods.includes(request.method)) {
|
|
631
824
|
return false;
|
|
@@ -643,7 +836,9 @@ export class VaultCore {
|
|
|
643
836
|
return identities;
|
|
644
837
|
}
|
|
645
838
|
async ownerListCapabilities(actor, agentId, request) {
|
|
646
|
-
const capabilities = await this._deps.
|
|
839
|
+
const capabilities = (await this._deps.capabilityStates.list(this._deps.vaultId, agentId))
|
|
840
|
+
.filter((state) => state.status === "GRANTED")
|
|
841
|
+
.map((state) => this._stateToGrantedCapability(state));
|
|
647
842
|
await this._appendAudit(toAuditEntry(this._deps, actor, AuditAction.LIST_CAPABILITIES, AuditOutcome.ALLOWED, "capabilities listed", {
|
|
648
843
|
requestId: request?.requestId,
|
|
649
844
|
agentId,
|
|
@@ -670,7 +865,7 @@ export class VaultCore {
|
|
|
670
865
|
throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
|
|
671
866
|
}
|
|
672
867
|
await this._verifyAgentControlProof(request, "list_capabilities");
|
|
673
|
-
return this.
|
|
868
|
+
return this._buildAgentCapabilityStates(request.agent.id);
|
|
674
869
|
}
|
|
675
870
|
async agentListSecrets(request) {
|
|
676
871
|
if (request.vaultId.value !== this._deps.vaultId.value) {
|
|
@@ -683,13 +878,25 @@ export class VaultCore {
|
|
|
683
878
|
if (command.vaultId.value !== this._deps.vaultId.value) {
|
|
684
879
|
throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
|
|
685
880
|
}
|
|
686
|
-
|
|
881
|
+
await this._verifyAgentControlProof(command, "get_manifest");
|
|
882
|
+
const agentRecord = await this._deps.agentIdentities.get(this._deps.vaultId, command.agent.id);
|
|
883
|
+
if (!agentRecord) {
|
|
884
|
+
throw new VaultCoreError("agent identity not registered", "VAULT_DISPATCH_DENIED");
|
|
885
|
+
}
|
|
886
|
+
const capabilities = await this._buildAgentCapabilityStates(command.agent.id);
|
|
687
887
|
const vaultNickname = "CBIO Vault"; // TODO: Pull from profile if available
|
|
688
888
|
return {
|
|
689
889
|
agentId: command.agent.id,
|
|
690
890
|
vaultId: this._deps.vaultId.value,
|
|
691
891
|
vaultNickname,
|
|
692
892
|
issuedAt: this._deps.clock.nowIso(),
|
|
893
|
+
agent: {
|
|
894
|
+
agentId: agentRecord.agentId,
|
|
895
|
+
identityId: agentRecord.identityId,
|
|
896
|
+
publicKey: agentRecord.publicKey,
|
|
897
|
+
nickname: agentRecord.nickname,
|
|
898
|
+
metadata: agentRecord.metadata,
|
|
899
|
+
},
|
|
693
900
|
capabilities,
|
|
694
901
|
tools: getAgentToolbox(),
|
|
695
902
|
};
|
|
@@ -716,7 +923,15 @@ export class VaultCore {
|
|
|
716
923
|
});
|
|
717
924
|
}
|
|
718
925
|
async ownerRevokeCapability(command) {
|
|
719
|
-
await this._deps.
|
|
926
|
+
const existing = await this._deps.capabilityStates.getByCapabilityId(command.vaultId, command.agentId, command.capabilityId);
|
|
927
|
+
if (!existing) {
|
|
928
|
+
throw new VaultCoreError("capability not found", "VAULT_CAPABILITY_NOT_FOUND");
|
|
929
|
+
}
|
|
930
|
+
await this._deps.capabilityStates.upsert({
|
|
931
|
+
...existing,
|
|
932
|
+
status: "REJECTED",
|
|
933
|
+
decidedAt: this._deps.clock.nowIso(),
|
|
934
|
+
});
|
|
720
935
|
await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REVOKE_CAPABILITY, AuditOutcome.SUCCEEDED, "capability revoked", {
|
|
721
936
|
requestId: command.requestId,
|
|
722
937
|
agentId: command.agentId,
|
|
@@ -762,135 +977,39 @@ export class VaultCore {
|
|
|
762
977
|
await this._deps.sessionTokens.revoke(request.token);
|
|
763
978
|
await this._appendAudit(toAuditEntry(this._deps, request.actor, AuditAction.REVOKE_SESSION_TOKEN, AuditOutcome.SUCCEEDED, "session token revoked"));
|
|
764
979
|
}
|
|
765
|
-
async
|
|
980
|
+
async ownerListCapabilityStates(command) {
|
|
766
981
|
if (command.vaultId.value !== this._deps.vaultId.value) {
|
|
767
982
|
throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
|
|
768
983
|
}
|
|
769
|
-
return this._deps.
|
|
984
|
+
return (await this._deps.capabilityStates.list(command.vaultId, command.agentId))
|
|
985
|
+
.filter((state) => !command.status || state.status === command.status);
|
|
770
986
|
}
|
|
771
|
-
async
|
|
772
|
-
|
|
773
|
-
throw new VaultCoreError("read vault mismatch", "VAULT_READ_DENIED");
|
|
774
|
-
}
|
|
775
|
-
return this._deps.pendingCapabilityRequests.list(command.vaultId);
|
|
987
|
+
async ownerExecuteCapabilityStateOnce(command) {
|
|
988
|
+
return this._executePendingCapabilityState(command, "once");
|
|
776
989
|
}
|
|
777
|
-
async
|
|
778
|
-
|
|
779
|
-
throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
|
|
780
|
-
}
|
|
781
|
-
const pending = await this._deps.pendingCapabilityRequests.get(command.requestId);
|
|
782
|
-
if (!pending) {
|
|
783
|
-
throw new VaultCoreError("pending capability request not found", "VAULT_REQUEST_NOT_FOUND");
|
|
784
|
-
}
|
|
785
|
-
const capability = {
|
|
786
|
-
vaultId: this._deps.vaultId,
|
|
787
|
-
agentId: pending.agentId,
|
|
788
|
-
capabilityId: command.capabilityId ?? this._deps.ids.newCapabilityId(),
|
|
789
|
-
operation: pending.scope.operation,
|
|
790
|
-
secretAliases: pending.scope.secretAliases ? [...pending.scope.secretAliases] : [],
|
|
791
|
-
scope: pending.scope.scope,
|
|
792
|
-
methods: [...pending.scope.methods],
|
|
793
|
-
rateLimit: pending.scope.rateLimit,
|
|
794
|
-
skipAudit: pending.scope.skipAudit,
|
|
795
|
-
expiresAt: pending.scope.expiresAt,
|
|
796
|
-
issuedAt: this._deps.clock.nowIso(),
|
|
797
|
-
};
|
|
798
|
-
await this._deps.capabilities.register(capability);
|
|
799
|
-
await this._deps.pendingCapabilityRequests.delete(command.requestId);
|
|
800
|
-
await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `approved capability request ${command.requestId}`, {
|
|
801
|
-
requestId: command.requestId,
|
|
802
|
-
agentId: pending.agentId,
|
|
803
|
-
capabilityId: capability.capabilityId,
|
|
804
|
-
operation: capability.operation,
|
|
805
|
-
}));
|
|
806
|
-
return capability;
|
|
990
|
+
async ownerExecuteCapabilityStateAndGrant(command) {
|
|
991
|
+
return this._executePendingCapabilityState(command, "grant");
|
|
807
992
|
}
|
|
808
|
-
async
|
|
993
|
+
async ownerRejectCapabilityState(command) {
|
|
809
994
|
if (command.vaultId.value !== this._deps.vaultId.value) {
|
|
810
995
|
throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
|
|
811
996
|
}
|
|
812
|
-
const pending = await this._deps.
|
|
813
|
-
if (!pending) {
|
|
814
|
-
throw new VaultCoreError("pending capability
|
|
997
|
+
const pending = await this._deps.capabilityStates.getByRequestId(command.vaultId, command.requestId);
|
|
998
|
+
if (!pending || pending.status !== "PENDING") {
|
|
999
|
+
throw new VaultCoreError("pending capability state not found", "VAULT_REQUEST_NOT_FOUND");
|
|
815
1000
|
}
|
|
816
|
-
|
|
1001
|
+
const rejectedState = {
|
|
1002
|
+
...pending,
|
|
1003
|
+
status: "REJECTED",
|
|
1004
|
+
decidedAt: this._deps.clock.nowIso(),
|
|
1005
|
+
};
|
|
1006
|
+
await this._deps.capabilityStates.upsert(rejectedState);
|
|
817
1007
|
await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REJECT_CAPABILITY_REQUEST, AuditOutcome.SUCCEEDED, `rejected capability request ${command.requestId}`, {
|
|
818
1008
|
requestId: command.requestId,
|
|
819
1009
|
agentId: pending.agentId,
|
|
820
|
-
operation: pending.
|
|
821
|
-
}));
|
|
822
|
-
}
|
|
823
|
-
async ownerApproveDispatch(command) {
|
|
824
|
-
if (command.vaultId.value !== this._deps.vaultId.value) {
|
|
825
|
-
throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
|
|
826
|
-
}
|
|
827
|
-
const pending = await this._deps.pendingRequests.get(command.requestId);
|
|
828
|
-
if (!pending) {
|
|
829
|
-
throw new VaultCoreError("pending request not found", "VAULT_REQUEST_NOT_FOUND");
|
|
830
|
-
}
|
|
831
|
-
const agentRecord = await this._deps.agentIdentities.get(this._deps.vaultId, pending.agentId);
|
|
832
|
-
if (!agentRecord) {
|
|
833
|
-
throw new VaultCoreError("agent identity not found", "VAULT_AGENT_NOT_FOUND");
|
|
834
|
-
}
|
|
835
|
-
let capability;
|
|
836
|
-
if (pending.capabilityId) {
|
|
837
|
-
const existing = await this._deps.capabilities.get(this._deps.vaultId, pending.agentId, pending.capabilityId);
|
|
838
|
-
if (!existing) {
|
|
839
|
-
throw new VaultCoreError("capability not found", "VAULT_CAPABILITY_NOT_FOUND");
|
|
840
|
-
}
|
|
841
|
-
capability = existing;
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
// Discovery case: derive from request
|
|
845
|
-
const capabilityId = this._deps.ids.newCapabilityId();
|
|
846
|
-
capability = {
|
|
847
|
-
vaultId: this._deps.vaultId,
|
|
848
|
-
agentId: pending.agentId,
|
|
849
|
-
capabilityId,
|
|
850
|
-
secretAliases: [pending.secretAlias],
|
|
851
|
-
methods: [pending.method],
|
|
852
|
-
scope: pending.targetUrl,
|
|
853
|
-
operation: "dispatch_http",
|
|
854
|
-
issuedAt: this._deps.clock.nowIso(),
|
|
855
|
-
skipAudit: command.skipAudit ?? false,
|
|
856
|
-
};
|
|
857
|
-
if (command.permanent) {
|
|
858
|
-
await this._deps.capabilities.register(capability);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
const result = await this.agentDispatchSecret({
|
|
862
|
-
vaultId: this._deps.vaultId,
|
|
863
|
-
agent: { kind: "agent", id: pending.agentId },
|
|
864
|
-
capability: capability,
|
|
865
|
-
secretAlias: pending.secretAlias === "unknown" ? undefined : pending.secretAlias,
|
|
866
|
-
targetUrl: pending.targetUrl,
|
|
867
|
-
method: pending.method,
|
|
868
|
-
headers: pending.headers,
|
|
869
|
-
body: pending.body,
|
|
870
|
-
proof: pending.proof,
|
|
871
|
-
requestId: pending.requestId,
|
|
872
|
-
requestedAt: pending.requestedAt,
|
|
873
|
-
});
|
|
874
|
-
await this._deps.pendingRequests.delete(command.requestId);
|
|
875
|
-
await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.APPROVE_DISPATCH, AuditOutcome.SUCCEEDED, `approved dispatch ${command.requestId}${command.permanent ? " and granted permanent capability" : ""}`, {
|
|
876
|
-
requestId: command.requestId,
|
|
877
|
-
agentId: pending.agentId,
|
|
878
|
-
capabilityId: capability.capabilityId,
|
|
879
|
-
}));
|
|
880
|
-
return result;
|
|
881
|
-
}
|
|
882
|
-
async ownerRejectDispatch(command) {
|
|
883
|
-
if (command.vaultId.value !== this._deps.vaultId.value) {
|
|
884
|
-
throw new VaultCoreError("write vault mismatch", "VAULT_WRITE_DENIED");
|
|
885
|
-
}
|
|
886
|
-
const pending = await this._deps.pendingRequests.get(command.requestId);
|
|
887
|
-
if (!pending) {
|
|
888
|
-
throw new VaultCoreError("pending request not found", "VAULT_REQUEST_NOT_FOUND");
|
|
889
|
-
}
|
|
890
|
-
await this._deps.pendingRequests.delete(command.requestId);
|
|
891
|
-
await this._appendAudit(toAuditEntry(this._deps, command.owner, AuditAction.REJECT_DISPATCH, AuditOutcome.SUCCEEDED, `rejected dispatch ${command.requestId}`, {
|
|
892
|
-
requestId: command.requestId,
|
|
1010
|
+
operation: pending.operation,
|
|
893
1011
|
}));
|
|
1012
|
+
return rejectedState;
|
|
894
1013
|
}
|
|
895
1014
|
}
|
|
896
1015
|
export function createVaultCore(deps) {
|