@vess-id/vess 0.2.0-alpha.3 → 0.2.0-alpha.30
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/adapter/mcp/http-transport.d.ts +9 -3
- package/dist/adapter/mcp/http-transport.d.ts.map +1 -1
- package/dist/adapter/mcp/http-transport.js +85 -15
- package/dist/adapter/mcp/http-transport.js.map +1 -1
- package/dist/adapter/mcp/mcp-adapter.d.ts +2 -0
- package/dist/adapter/mcp/mcp-adapter.d.ts.map +1 -1
- package/dist/adapter/mcp/mcp-adapter.js +9 -0
- package/dist/adapter/mcp/mcp-adapter.js.map +1 -1
- package/dist/adapter/mcp/mcp-server.factory.d.ts.map +1 -1
- package/dist/adapter/mcp/mcp-server.factory.js +59 -20
- package/dist/adapter/mcp/mcp-server.factory.js.map +1 -1
- package/dist/audit/audit-logger.d.ts +1 -1
- package/dist/audit/audit-logger.d.ts.map +1 -1
- package/dist/cli/index.js +9 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +10 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/config/constants.d.ts +8 -0
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/constants.js +9 -1
- package/dist/config/constants.js.map +1 -1
- package/dist/config/version.d.ts +3 -0
- package/dist/config/version.d.ts.map +1 -0
- package/dist/config/version.js +80 -0
- package/dist/config/version.js.map +1 -0
- package/dist/core/execution-engine.d.ts +15 -1
- package/dist/core/execution-engine.d.ts.map +1 -1
- package/dist/core/execution-engine.js +207 -36
- package/dist/core/execution-engine.js.map +1 -1
- package/dist/core/runtime.d.ts.map +1 -1
- package/dist/core/runtime.js +4 -0
- package/dist/core/runtime.js.map +1 -1
- package/dist/core/types.d.ts +8 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/gateway/gateway-client.d.ts +31 -9
- package/dist/gateway/gateway-client.d.ts.map +1 -1
- package/dist/gateway/gateway-client.js +74 -30
- package/dist/gateway/gateway-client.js.map +1 -1
- package/dist/utils/credential-errors.d.ts +41 -0
- package/dist/utils/credential-errors.d.ts.map +1 -1
- package/dist/utils/credential-errors.js +39 -0
- package/dist/utils/credential-errors.js.map +1 -1
- package/dist/wallet/wallet.d.ts +6 -0
- package/dist/wallet/wallet.d.ts.map +1 -1
- package/dist/wallet/wallet.js +9 -0
- package/dist/wallet/wallet.js.map +1 -1
- package/package.json +14 -14
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/config/version.ts"],"names":[],"mappings":"AAyCA,eAAO,MAAM,WAAW,QAAmB,CAAA;AAC3C,eAAO,MAAM,WAAW,QAAmB,CAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SDK_VERSION = exports.CLI_VERSION = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const node_module_1 = require("node:module");
|
|
40
|
+
/**
|
|
41
|
+
* Read the agentd CLI version from package.json at runtime.
|
|
42
|
+
* Works in both dev (src/config) and production (dist/config).
|
|
43
|
+
*/
|
|
44
|
+
function readCliVersion() {
|
|
45
|
+
try {
|
|
46
|
+
const pkgPath = path.resolve(__dirname, '..', '..', 'package.json');
|
|
47
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
48
|
+
return pkg.version ?? '0.0.0-unknown';
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return '0.0.0-unknown';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read the SDK version from @vess-id/ai-identity package.json.
|
|
56
|
+
* Tries multiple resolution strategies for compatibility with
|
|
57
|
+
* jest (moduleNameMapper), workspace links, and production installs.
|
|
58
|
+
*/
|
|
59
|
+
function readSdkVersion() {
|
|
60
|
+
// Strategy 1: createRequire (works in production npm installs)
|
|
61
|
+
try {
|
|
62
|
+
const req = (0, node_module_1.createRequire)(__filename);
|
|
63
|
+
const pkg = req('@vess-id/ai-identity/package.json');
|
|
64
|
+
if (pkg.version)
|
|
65
|
+
return pkg.version;
|
|
66
|
+
}
|
|
67
|
+
catch { /* fall through */ }
|
|
68
|
+
// Strategy 2: relative path from monorepo (works in dev and jest)
|
|
69
|
+
try {
|
|
70
|
+
const pkgPath = path.resolve(__dirname, '..', '..', '..', 'sdk', 'package.json');
|
|
71
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
72
|
+
if (pkg.version)
|
|
73
|
+
return pkg.version;
|
|
74
|
+
}
|
|
75
|
+
catch { /* fall through */ }
|
|
76
|
+
return '0.0.0-unknown';
|
|
77
|
+
}
|
|
78
|
+
exports.CLI_VERSION = readCliVersion();
|
|
79
|
+
exports.SDK_VERSION = readSdkVersion();
|
|
80
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/config/version.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAiC;AACjC,4CAA6B;AAC7B,6CAA2C;AAE3C;;;GAGG;AACH,SAAS,cAAc;IACrB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAA;QACnE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;QACzD,OAAO,GAAG,CAAC,OAAO,IAAI,eAAe,CAAA;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,eAAe,CAAA;IACxB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc;IACrB,+DAA+D;IAC/D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,2BAAa,EAAC,UAAU,CAAC,CAAA;QACrC,MAAM,GAAG,GAAG,GAAG,CAAC,mCAAmC,CAAwB,CAAA;QAC3E,IAAI,GAAG,CAAC,OAAO;YAAE,OAAO,GAAG,CAAC,OAAO,CAAA;IACrC,CAAC;IAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAE9B,kEAAkE;IAClE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,CAAC,CAAA;QAChF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;QACzD,IAAI,GAAG,CAAC,OAAO;YAAE,OAAO,GAAG,CAAC,OAAO,CAAA;IACrC,CAAC;IAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAE9B,OAAO,eAAe,CAAA;AACxB,CAAC;AAEY,QAAA,WAAW,GAAG,cAAc,EAAE,CAAA;AAC9B,QAAA,WAAW,GAAG,cAAc,EAAE,CAAA"}
|
|
@@ -77,10 +77,24 @@ export declare class ExecutionEngine {
|
|
|
77
77
|
*/
|
|
78
78
|
private handlePendingApproval;
|
|
79
79
|
/**
|
|
80
|
-
* Create an OOB approval request
|
|
80
|
+
* Create an OOB (out-of-band) approval request.
|
|
81
81
|
* Returns a URL for the user to approve in their browser.
|
|
82
|
+
*
|
|
83
|
+
* Called in two contexts:
|
|
84
|
+
* - ensureVC Step 4: for high/critical risk actions that require browser approval.
|
|
85
|
+
* - tryOOBFallbackAfterRetryExhaustion: for ANY risk level when a VC expires
|
|
86
|
+
* mid-execution and retry is exhausted. In this case OOB is used as a fallback
|
|
87
|
+
* because the normal in-band flow cannot recover an expired credential — the
|
|
88
|
+
* user must re-authorize via browser regardless of risk level.
|
|
82
89
|
*/
|
|
83
90
|
private createOOBApprovalRequest;
|
|
91
|
+
/**
|
|
92
|
+
* Revoke the stale credential and attempt to create an OOB approval request
|
|
93
|
+
* as a fallback after retry exhaustion. Returns an ExecuteResult with
|
|
94
|
+
* waitingForApproval on success, or null if OOB creation fails (caller falls
|
|
95
|
+
* through to the original error).
|
|
96
|
+
*/
|
|
97
|
+
private tryOOBFallbackAfterRetryExhaustion;
|
|
84
98
|
/**
|
|
85
99
|
* Create an in-band approval response for low/medium risk actions.
|
|
86
100
|
* Generates an approval token for the MCP client to present back.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execution-engine.d.ts","sourceRoot":"","sources":["../../src/core/execution-engine.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAuC,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,mBAAmB,EAAuB,MAAM,2BAA2B,CAAA;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAKjE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAA0E,MAAM,SAAS,CAAA;AAElK,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"execution-engine.d.ts","sourceRoot":"","sources":["../../src/core/execution-engine.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAuC,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,mBAAmB,EAAuB,MAAM,2BAA2B,CAAA;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAKjE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAA0E,MAAM,SAAS,CAAA;AAElK,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAoCrD,qBAAa,eAAe;IASxB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAfjC,OAAO,CAAC,kBAAkB,CAAsB;IAChD,OAAO,CAAC,UAAU,CAAC,CAAmC;IACtD,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,iBAAiB,CAA4B;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAS;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAO;gBAG5B,eAAe,EAAE,eAAe,EAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,WAAW,GAAG,IAAI,EAC/B,aAAa,EAAE,mBAAmB,EAClC,OAAO,EAAE,gBAAgB,EACzB,YAAY,EAAE,oBAAoB,EAClC,aAAa,CAAC,EAAE,aAAa,YAAA;IAGhD;;OAEG;IACH,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIzC;;;OAGG;IACH,aAAa,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAI5D;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA6B7B;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAkF9D;;;;;;;;OAQG;YACW,QAAQ;YA2HR,YAAY;IAuC1B;;;OAGG;YACW,kBAAkB;IAwJhC;;;;;;;OAOG;YACW,mBAAmB;YA2GnB,iBAAiB;YAiDjB,uBAAuB;IAqIrC;;OAEG;YACW,qBAAqB;IAmLnC;;;;;;;;;;OAUG;YACW,wBAAwB;IA+CtC;;;;;OAKG;YACW,kCAAkC;IAqChD;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAgDpC;;;;OAIG;IACG,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAmH7D,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC;IAWpD;;;OAGG;YACW,iBAAiB;IA8B/B,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,eAAe;YAUT,sBAAsB;IA0JpC,OAAO,CAAC,eAAe;IASvB;;;;;OAKG;IACH,OAAO,CAAC,0BAA0B;IA0BlC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,eAAe;IAavB,iHAAiH;YACnG,cAAc;YA4Bd,sBAAsB;IAsCpC,OAAO,CAAC,QAAQ;CAqCjB"}
|
|
@@ -63,8 +63,9 @@ const executor_registry_1 = require("../executor/executor-registry");
|
|
|
63
63
|
const env_resolver_1 = require("../env/env-resolver");
|
|
64
64
|
/** Policy cache staleness TTL (spec §7.6: 30 minutes) */
|
|
65
65
|
const POLICY_SYNC_TTL_MS = 30 * 60 * 1000;
|
|
66
|
-
/** OOB approval
|
|
67
|
-
|
|
66
|
+
/** Fallback OOB approval expiration (seconds) when the API doesn't return a value.
|
|
67
|
+
* Kept short (5 min) so the agent doesn't wait on a possibly-expired request. */
|
|
68
|
+
const OOB_APPROVAL_DEFAULT_EXPIRES_IN_SECONDS = 300;
|
|
68
69
|
/** Default TTL for OOB-approved credentials (10 minutes) */
|
|
69
70
|
const OOB_CREDENTIAL_DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
70
71
|
class ExecutionEngine {
|
|
@@ -243,23 +244,39 @@ class ExecutionEngine {
|
|
|
243
244
|
return { credential: verified, decisionSource: 'cached_vc' };
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
|
-
// Step 2: Check Gateway for approved requests
|
|
247
|
+
// Step 2: Check Gateway for approved requests, then claim VC
|
|
247
248
|
try {
|
|
248
249
|
approvedRequests = await this.gatewayClient.findApprovedRequests(this.context.agentDid);
|
|
249
|
-
const matching = approvedRequests.find(r => r.actions?.includes(action)
|
|
250
|
-
if (matching
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
250
|
+
const matching = approvedRequests.find(r => r.actions?.includes(action));
|
|
251
|
+
if (matching) {
|
|
252
|
+
try {
|
|
253
|
+
const claimed = await this.gatewayClient.claimVC(matching.id, this.context.agentDid);
|
|
254
|
+
const stored = this.wallet.storeCredential({
|
|
255
|
+
holderDid: this.context.agentDid,
|
|
256
|
+
projectId: this.context.projectId,
|
|
257
|
+
credentialJwt: claimed.credential.jwt,
|
|
258
|
+
actions: claimed.actions,
|
|
259
|
+
provider,
|
|
260
|
+
expiresAt: claimed.credential.expiresAt,
|
|
261
|
+
normalizedResourceKey: resourceInfo?.normalizedResource,
|
|
262
|
+
resourceFingerprint: resourceInfo?.fingerprint,
|
|
263
|
+
resources: (0, wallet_1.validateResources)(claimed.resources),
|
|
264
|
+
});
|
|
265
|
+
return { credential: stored, decisionSource: 'claimed_from_approval' };
|
|
266
|
+
}
|
|
267
|
+
catch (claimErr) {
|
|
268
|
+
const msg = claimErr instanceof Error ? claimErr.message : String(claimErr);
|
|
269
|
+
if (msg === 'CLAIM_CONSUMED') {
|
|
270
|
+
process.stderr.write('[vess] claimVC: request already consumed, falling through to step 3\n');
|
|
271
|
+
}
|
|
272
|
+
else if (msg === 'CLAIM_EXPIRED') {
|
|
273
|
+
process.stderr.write('[vess] claimVC: request expired, falling through to step 3\n');
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
process.stderr.write(`[vess] claimVC failed: ${msg}, falling through to step 3\n`);
|
|
277
|
+
}
|
|
278
|
+
// Fall through to Step 3
|
|
279
|
+
}
|
|
263
280
|
}
|
|
264
281
|
}
|
|
265
282
|
catch {
|
|
@@ -384,6 +401,16 @@ class ExecutionEngine {
|
|
|
384
401
|
return { success: false, error: `VC reissuance denied: ${newVCResult.reason}` };
|
|
385
402
|
}
|
|
386
403
|
}
|
|
404
|
+
// After retry exhaustion, if still credential-invalid, fall back to OOB approval
|
|
405
|
+
// so the user gets an actionable re-authorization URL instead of a raw error.
|
|
406
|
+
if ((0, credential_errors_1.isCredentialInvalidError)(authResult.reason) && retryCount >= 1) {
|
|
407
|
+
const resourceInfoForOOB = normalizedResource && resourceFingerprint
|
|
408
|
+
? { normalizedResource, fingerprint: resourceFingerprint }
|
|
409
|
+
: undefined;
|
|
410
|
+
const oobFallback = await this.tryOOBFallbackAfterRetryExhaustion(action, provider, vcResult.credential.id, params, resourceInfoForOOB, 'local');
|
|
411
|
+
if (oobFallback)
|
|
412
|
+
return oobFallback;
|
|
413
|
+
}
|
|
387
414
|
this.logAudit({
|
|
388
415
|
action, provider,
|
|
389
416
|
decision: 'deny',
|
|
@@ -615,12 +642,45 @@ class ExecutionEngine {
|
|
|
615
642
|
vpChallenge: nonce,
|
|
616
643
|
vpDomain: this.context.gatewayUrl,
|
|
617
644
|
});
|
|
645
|
+
// Phase 2c-B — the api signaled a revoked/expired OAuth token. Do not
|
|
646
|
+
// retry: the VC is still valid locally but the upstream SaaS token is
|
|
647
|
+
// gone and won't return until the user clicks the authUrl. Surface the
|
|
648
|
+
// reauth payload so the CLI can render it.
|
|
649
|
+
//
|
|
650
|
+
// Scope note: reauth only originates here (the gateway path) because
|
|
651
|
+
// local actions (`os.*`) don't touch SaaS OAuth tokens. If a future
|
|
652
|
+
// local executor ever starts proxying SaaS calls, mirror this branch
|
|
653
|
+
// in `executeLocalWithVC` with `enforcementType: 'local'`.
|
|
654
|
+
if (!result.success && result.errorCode === ai_identity_1.GATEWAY_ERROR_CODE.REAUTH_REQUIRED && result.reauthRequired) {
|
|
655
|
+
this.logAudit({
|
|
656
|
+
action, provider,
|
|
657
|
+
decision: 'deny',
|
|
658
|
+
enforcementType: 'gateway',
|
|
659
|
+
decisionSource: 'gateway_execution',
|
|
660
|
+
decisionReason: 'oauth_token_revoked',
|
|
661
|
+
executionType: 'gateway',
|
|
662
|
+
credentialId: vcResult.credential.id,
|
|
663
|
+
grantId: vcResult.grantId,
|
|
664
|
+
approvalMode: vcResult.approvalMode,
|
|
665
|
+
});
|
|
666
|
+
return {
|
|
667
|
+
success: false,
|
|
668
|
+
error: result.error,
|
|
669
|
+
reauthRequired: {
|
|
670
|
+
provider: result.reauthRequired.provider,
|
|
671
|
+
reason: result.reauthRequired.reason,
|
|
672
|
+
message: result.reauthRequired.message,
|
|
673
|
+
authUrl: result.reauthRequired.authUrl,
|
|
674
|
+
projectId: result.reauthRequired.projectId,
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
}
|
|
618
678
|
// Detect resource mismatch from structured error code
|
|
619
|
-
if (!result.success && result.errorCode ===
|
|
679
|
+
if (!result.success && result.errorCode === ai_identity_1.GATEWAY_ERROR_CODE.RESOURCE_MISMATCH) {
|
|
620
680
|
return this.handleResourceMismatch(action, provider, params, vcResult);
|
|
621
681
|
}
|
|
622
682
|
// Detect credential-invalid errors and attempt VC reissuance (max 1 retry)
|
|
623
|
-
if (!result.success && result.errorCode ===
|
|
683
|
+
if (!result.success && result.errorCode === ai_identity_1.GATEWAY_ERROR_CODE.CREDENTIAL_INVALID && retryCount < 1) {
|
|
624
684
|
this.wallet.revokeCredential(vcResult.credential.id);
|
|
625
685
|
const newVCResult = await this.ensureVC(action, provider, undefined, params, canonicalResourceId);
|
|
626
686
|
if ('credential' in newVCResult) {
|
|
@@ -640,6 +700,15 @@ class ExecutionEngine {
|
|
|
640
700
|
// Could not re-acquire VC — return the original error
|
|
641
701
|
return { success: false, error: result.error };
|
|
642
702
|
}
|
|
703
|
+
// After retry exhaustion, if still CREDENTIAL_INVALID, fall back to OOB approval
|
|
704
|
+
// so the user gets an actionable re-authorization URL instead of a raw error.
|
|
705
|
+
if (!result.success && result.errorCode === ai_identity_1.GATEWAY_ERROR_CODE.CREDENTIAL_INVALID && retryCount >= 1) {
|
|
706
|
+
// Gateway path does not have normalizedResource/resourceFingerprint in scope
|
|
707
|
+
// (resource resolution happens server-side), so resourceInfo is omitted.
|
|
708
|
+
const oobFallback = await this.tryOOBFallbackAfterRetryExhaustion(action, provider, vcResult.credential.id, params);
|
|
709
|
+
if (oobFallback)
|
|
710
|
+
return oobFallback;
|
|
711
|
+
}
|
|
643
712
|
this.logAudit({
|
|
644
713
|
action, provider,
|
|
645
714
|
decision: result.success ? 'allow' : 'deny',
|
|
@@ -728,7 +797,14 @@ class ExecutionEngine {
|
|
|
728
797
|
if (statusResult.status === 'expired') {
|
|
729
798
|
return { success: false, error: 'Approval request has expired. Please try again.' };
|
|
730
799
|
}
|
|
800
|
+
if (statusResult.status === 'consumed') {
|
|
801
|
+
return { success: false, error: 'Approval was already claimed by another session. Please request a new approval.' };
|
|
802
|
+
}
|
|
731
803
|
if (statusResult.status === 'pending') {
|
|
804
|
+
const rawExpiry = statusResult.requestExpiresAt
|
|
805
|
+
? Math.floor((new Date(statusResult.requestExpiresAt).getTime() - Date.now()) / 1000)
|
|
806
|
+
: NaN;
|
|
807
|
+
const expiresIn = Number.isNaN(rawExpiry) ? OOB_APPROVAL_DEFAULT_EXPIRES_IN_SECONDS : Math.max(0, rawExpiry);
|
|
732
808
|
return {
|
|
733
809
|
success: false,
|
|
734
810
|
waitingForApproval: {
|
|
@@ -737,45 +813,84 @@ class ExecutionEngine {
|
|
|
737
813
|
requestId: pendingRequestId,
|
|
738
814
|
action,
|
|
739
815
|
riskLevel: this.getActionRiskLevel(action),
|
|
740
|
-
expiresIn
|
|
816
|
+
expiresIn,
|
|
741
817
|
message: 'Still waiting for approval. Please approve in your browser.',
|
|
742
818
|
},
|
|
743
819
|
};
|
|
744
820
|
}
|
|
745
|
-
// approved — store
|
|
746
|
-
if (statusResult.status === 'approved'
|
|
821
|
+
// approved — claim VC, store, and execute directly
|
|
822
|
+
if (statusResult.status === 'approved') {
|
|
747
823
|
const provider = action.split('.')[0];
|
|
824
|
+
let claimed;
|
|
825
|
+
try {
|
|
826
|
+
claimed = await this.gatewayClient.claimVC(pendingRequestId, this.context.agentDid);
|
|
827
|
+
}
|
|
828
|
+
catch (claimErr) {
|
|
829
|
+
const msg = claimErr instanceof Error ? claimErr.message : String(claimErr);
|
|
830
|
+
if (msg === 'CLAIM_CONSUMED') {
|
|
831
|
+
return { success: false, error: 'Approval was already claimed by another session' };
|
|
832
|
+
}
|
|
833
|
+
if (msg === 'CLAIM_EXPIRED') {
|
|
834
|
+
return { success: false, error: 'Approval request has expired. Please try again.' };
|
|
835
|
+
}
|
|
836
|
+
return { success: false, error: `Failed to claim VC: ${msg}` };
|
|
837
|
+
}
|
|
838
|
+
// Restore original action parameters from approval request if not provided in retry call
|
|
839
|
+
const fallbackParams = claimed.actionParams;
|
|
840
|
+
const effectiveInput = Object.keys(request.input).length > 0
|
|
841
|
+
? request.input
|
|
842
|
+
: fallbackParams ?? null;
|
|
843
|
+
if (!effectiveInput) {
|
|
844
|
+
return { success: false, error: 'Action parameters could not be restored. Please retry with explicit parameters.' };
|
|
845
|
+
}
|
|
846
|
+
// Security: re-validate resource constraint using effectiveInput (defense-in-depth)
|
|
847
|
+
// The pre-claim check (line 838) may have been skipped when request.input was empty.
|
|
848
|
+
if (statusResult.resources?.length) {
|
|
849
|
+
const extractedId = this.extractResourceIdFromInput(action, effectiveInput);
|
|
850
|
+
if (extractedId) {
|
|
851
|
+
const normalizedId = (0, executor_registry_1.isLocalAction)(action) && effectiveInput.file_path
|
|
852
|
+
? (0, resource_canonicalizer_1.canonicalizePath)(effectiveInput.file_path)
|
|
853
|
+
: extractedId;
|
|
854
|
+
const approvedResources = statusResult.resources;
|
|
855
|
+
const resourceMatch = approvedResources.some((r) => r.id === extractedId || r.id === normalizedId ||
|
|
856
|
+
r.pattern === extractedId || r.pattern === normalizedId);
|
|
857
|
+
if (!resourceMatch) {
|
|
858
|
+
return {
|
|
859
|
+
success: false,
|
|
860
|
+
error: `Resource mismatch: approval was for [${approvedResources.map((r) => r.id || r.pattern).join(', ')}], but effective input resolves to "${extractedId}"`,
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
748
865
|
const stored = this.wallet.storeCredential({
|
|
749
866
|
holderDid: this.context.agentDid,
|
|
750
867
|
projectId: this.context.projectId,
|
|
751
|
-
credentialJwt:
|
|
752
|
-
actions: [action],
|
|
868
|
+
credentialJwt: claimed.credential.jwt,
|
|
869
|
+
actions: claimed.actions.length ? claimed.actions : [action],
|
|
753
870
|
provider,
|
|
754
|
-
expiresAt:
|
|
755
|
-
|
|
756
|
-
: Date.now() + OOB_CREDENTIAL_DEFAULT_TTL_MS,
|
|
757
|
-
resources: (0, wallet_1.validateResources)(statusResult.resources),
|
|
871
|
+
expiresAt: claimed.credential.expiresAt,
|
|
872
|
+
resources: (0, wallet_1.validateResources)(claimed.resources),
|
|
758
873
|
});
|
|
759
874
|
// Build VCAcquired and execute directly (skip re-enter via this.execute)
|
|
760
875
|
const vcAcquired = {
|
|
761
876
|
credential: stored,
|
|
762
877
|
approvalMode: 'one_time',
|
|
763
|
-
decisionSource: '
|
|
878
|
+
decisionSource: 'claimed_from_approval',
|
|
764
879
|
};
|
|
765
880
|
const routing = (0, executor_registry_1.determineRouting)(provider, action);
|
|
766
881
|
let executeResult;
|
|
767
|
-
const normalizedResource = (0, executor_registry_1.isLocalAction)(action) &&
|
|
768
|
-
? (0, resource_canonicalizer_1.canonicalizePath)(
|
|
882
|
+
const normalizedResource = (0, executor_registry_1.isLocalAction)(action) && effectiveInput.file_path
|
|
883
|
+
? (0, resource_canonicalizer_1.canonicalizePath)(effectiveInput.file_path)
|
|
769
884
|
: undefined;
|
|
770
885
|
const resourceFingerprint = normalizedResource
|
|
771
886
|
? (0, resource_canonicalizer_1.computeFingerprint)(normalizedResource, this.context.rootDid)
|
|
772
887
|
: undefined;
|
|
773
888
|
try {
|
|
774
889
|
if (routing.executor === 'local') {
|
|
775
|
-
executeResult = await this.executeLocalWithVC(action,
|
|
890
|
+
executeResult = await this.executeLocalWithVC(action, effectiveInput, provider, vcAcquired, normalizedResource, resourceFingerprint, effectiveInput.file_path);
|
|
776
891
|
}
|
|
777
892
|
else {
|
|
778
|
-
executeResult = await this.executeViaGatewayWithVC(provider, action,
|
|
893
|
+
executeResult = await this.executeViaGatewayWithVC(provider, action, effectiveInput, vcAcquired);
|
|
779
894
|
}
|
|
780
895
|
}
|
|
781
896
|
finally {
|
|
@@ -789,8 +904,15 @@ class ExecutionEngine {
|
|
|
789
904
|
return { success: false, error: `Unexpected approval status: ${statusResult.status}` };
|
|
790
905
|
}
|
|
791
906
|
/**
|
|
792
|
-
* Create an OOB approval request
|
|
907
|
+
* Create an OOB (out-of-band) approval request.
|
|
793
908
|
* Returns a URL for the user to approve in their browser.
|
|
909
|
+
*
|
|
910
|
+
* Called in two contexts:
|
|
911
|
+
* - ensureVC Step 4: for high/critical risk actions that require browser approval.
|
|
912
|
+
* - tryOOBFallbackAfterRetryExhaustion: for ANY risk level when a VC expires
|
|
913
|
+
* mid-execution and retry is exhausted. In this case OOB is used as a fallback
|
|
914
|
+
* because the normal in-band flow cannot recover an expired credential — the
|
|
915
|
+
* user must re-authorize via browser regardless of risk level.
|
|
794
916
|
*/
|
|
795
917
|
async createOOBApprovalRequest(action, provider, riskLevel, resourceInfo, input) {
|
|
796
918
|
const resourceType = this.getResourceType(action);
|
|
@@ -806,7 +928,12 @@ class ExecutionEngine {
|
|
|
806
928
|
projectId: this.context.projectId,
|
|
807
929
|
actions: [action],
|
|
808
930
|
resources,
|
|
931
|
+
actionParams: input,
|
|
809
932
|
});
|
|
933
|
+
const rawExpiry = result.expiresAt
|
|
934
|
+
? Math.floor((new Date(result.expiresAt).getTime() - Date.now()) / 1000)
|
|
935
|
+
: NaN;
|
|
936
|
+
const expiresIn = Number.isNaN(rawExpiry) ? OOB_APPROVAL_DEFAULT_EXPIRES_IN_SECONDS : Math.max(0, rawExpiry);
|
|
810
937
|
return {
|
|
811
938
|
waitingForApproval: {
|
|
812
939
|
status: 'waiting_for_approval',
|
|
@@ -815,7 +942,7 @@ class ExecutionEngine {
|
|
|
815
942
|
action,
|
|
816
943
|
resource: resourceInfo?.normalizedResource ?? extractedId,
|
|
817
944
|
riskLevel,
|
|
818
|
-
expiresIn
|
|
945
|
+
expiresIn,
|
|
819
946
|
message: `This ${riskLevel}-risk action requires browser approval (login required). Open the URL to approve.`,
|
|
820
947
|
},
|
|
821
948
|
};
|
|
@@ -824,6 +951,42 @@ class ExecutionEngine {
|
|
|
824
951
|
throw new Error(`Failed to create OOB approval request: ${err instanceof Error ? err.message : String(err)}`);
|
|
825
952
|
}
|
|
826
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* Revoke the stale credential and attempt to create an OOB approval request
|
|
956
|
+
* as a fallback after retry exhaustion. Returns an ExecuteResult with
|
|
957
|
+
* waitingForApproval on success, or null if OOB creation fails (caller falls
|
|
958
|
+
* through to the original error).
|
|
959
|
+
*/
|
|
960
|
+
async tryOOBFallbackAfterRetryExhaustion(action, provider, credentialId, params, resourceInfo, executionType = 'gateway') {
|
|
961
|
+
const enforcementType = executionType === 'local' ? 'gateway_verified_local' : 'gateway';
|
|
962
|
+
this.wallet.revokeCredential(credentialId);
|
|
963
|
+
try {
|
|
964
|
+
const riskLevel = this.getActionRiskLevel(action);
|
|
965
|
+
const oobResult = await this.createOOBApprovalRequest(action, provider, riskLevel, resourceInfo, params);
|
|
966
|
+
this.logAudit({
|
|
967
|
+
action, provider,
|
|
968
|
+
decision: 'deny',
|
|
969
|
+
enforcementType,
|
|
970
|
+
decisionSource: 'gateway_execution',
|
|
971
|
+
decisionReason: `Credential expired after retry — OOB approval requested (requestId=${oobResult.waitingForApproval.requestId})`,
|
|
972
|
+
executionType,
|
|
973
|
+
credentialId,
|
|
974
|
+
});
|
|
975
|
+
return { success: false, waitingForApproval: oobResult.waitingForApproval };
|
|
976
|
+
}
|
|
977
|
+
catch (err) {
|
|
978
|
+
this.logAudit({
|
|
979
|
+
action, provider,
|
|
980
|
+
decision: 'deny',
|
|
981
|
+
enforcementType,
|
|
982
|
+
decisionSource: 'gateway_execution',
|
|
983
|
+
decisionReason: `OOB approval fallback failed after retry exhaustion: ${err instanceof Error ? err.message : String(err)}`,
|
|
984
|
+
executionType,
|
|
985
|
+
credentialId,
|
|
986
|
+
});
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
827
990
|
/**
|
|
828
991
|
* Create an in-band approval response for low/medium risk actions.
|
|
829
992
|
* Generates an approval token for the MCP client to present back.
|
|
@@ -1180,13 +1343,21 @@ class ExecutionEngine {
|
|
|
1180
1343
|
const binding = actionDef.target_bindings.resource_id;
|
|
1181
1344
|
if (binding.source !== 'param')
|
|
1182
1345
|
return undefined;
|
|
1183
|
-
|
|
1346
|
+
let value = input[binding.param];
|
|
1347
|
+
// Fallback: AI agents may use alternative param names (e.g., "issueKey" instead of "issueIdOrKey")
|
|
1348
|
+
if (!value && binding.fallback_param) {
|
|
1349
|
+
value = input[binding.fallback_param];
|
|
1350
|
+
}
|
|
1184
1351
|
if (!value || typeof value !== 'string')
|
|
1185
1352
|
return undefined;
|
|
1186
1353
|
// Defense-in-depth: reject control characters and excessively long values
|
|
1187
1354
|
const MAX_RESOURCE_ID_LENGTH = 500;
|
|
1188
1355
|
if (value.length > MAX_RESOURCE_ID_LENGTH || /[\x00-\x1f]/.test(value))
|
|
1189
1356
|
return undefined;
|
|
1357
|
+
// Apply derive transformation (e.g., "AIDENTITY-34" → "AIDENTITY" for project-level scoping)
|
|
1358
|
+
if (binding.derive === 'project_key') {
|
|
1359
|
+
value = (0, ai_identity_1.extractProjectKey)(value) ?? value;
|
|
1360
|
+
}
|
|
1190
1361
|
return value;
|
|
1191
1362
|
}
|
|
1192
1363
|
isRecentOOBRequest(action, resourceId) {
|