@agenthippo-ai/agenthippo 0.1.1 → 1.110.9
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/out/agentpack/index.js +2 -1
- package/out/agentpack/paths.js +23 -0
- package/out/analytics/dashboard/fleetDisplayUtils.js +3 -0
- package/out/analytics/dashboard/provider.js +13 -2
- package/out/analytics/dashboard/webview/panel.js +293 -82
- package/out/analytics/identity.js +0 -0
- package/out/analytics/service.js +23 -25
- package/out/analytics/sessionEvents.js +109 -22
- package/out/auth/authBaseUrl.js +59 -0
- package/out/auth/bootstrapApply.js +9 -10
- package/out/auth/brokerClient.js +49 -15
- package/out/auth/onboardingPrompt.js +112 -0
- package/out/auth/provider.js +9 -22
- package/out/auth/sessionStore.js +3 -40
- package/out/auth/signInFlow.js +2 -1
- package/out/auth-broker/config.js +54 -4
- package/out/auth-broker/server.js +152 -20
- package/out/auth-broker/store.js +25 -24
- package/out/authority/warrant.js +206 -0
- package/out/branding.js +27 -1
- package/out/cli/commands/ask.js +3 -0
- package/out/cli/commands/chat.js +3 -0
- package/out/cli/commands/conformance.js +249 -0
- package/out/cli/commands/data-plane.js +67 -0
- package/out/cli/commands/deploy.js +50 -10
- package/out/cli/commands/gateway.js +58 -0
- package/out/cli/commands/idp.js +85 -0
- package/out/cli/commands/pack.js +182 -3
- package/out/cli/commands/sandbox.js +332 -0
- package/out/cli/commands/serve.js +1165 -82
- package/out/cli/commands/store.js +12 -4
- package/out/cli/credential-context.js +42 -80
- package/out/cli/data-plane/broker.js +78 -0
- package/out/cli/data-plane/connector-host.js +121 -0
- package/out/cli/data-plane/egress.js +189 -0
- package/out/cli/data-plane/gateway.js +218 -0
- package/out/cli/data-plane/index.js +152 -0
- package/out/cli/data-plane/registry.js +89 -0
- package/out/cli/data-plane/remote.js +106 -0
- package/out/cli/data-plane/sidecar.js +161 -0
- package/out/cli/deploy/blueprints/registry.js +84 -0
- package/out/cli/deploy/layers/identity.js +172 -0
- package/out/cli/deploy-init.js +1182 -347
- package/out/cli/deps.js +106 -2
- package/out/cli/idp-init.js +338 -0
- package/out/cli/index.js +192 -23
- package/out/cli/pack-install-ref.js +15 -0
- package/out/cli/pack-loader.js +45 -3
- package/out/cli/pack-runtime.js +61 -0
- package/out/cli/runtime.js +44 -3
- package/out/cli/serve-hardening.js +139 -3
- package/out/cli/state-sync.js +16 -15
- package/out/cli/utils.js +12 -1
- package/out/engines/base.js +32 -3
- package/out/engines/claude.js +47 -8
- package/out/engines/codex.js +125 -22
- package/out/engines/custom.js +54 -12
- package/out/evidence/sessionShip.js +610 -0
- package/out/extension.js +13 -2
- package/out/gateway/bridge.js +40 -2
- package/out/gateway/client.js +34 -1
- package/out/gateway/installSource.js +68 -0
- package/out/gateway/lifecycle.js +185 -51
- package/out/gateway/outboundPaths.js +4 -1
- package/out/gateway/outboundSend.js +6 -6
- package/out/gateway/secretsPreflight.js +90 -0
- package/out/gateway/service.js +7 -2
- package/out/headless/index.js +40 -9
- package/out/live-e2e/mcp-demo/server.mjs +32 -0
- package/out/live-probe/connector.mjs +8 -0
- package/out/live-probe/env-report.mjs +8 -0
- package/out/mcp.js +115 -67
- package/out/pack/caps.js +271 -0
- package/out/pack/governor.js +309 -0
- package/out/pack/lease-schema.js +270 -0
- package/out/pack/lease.js +640 -0
- package/out/pack/reconcile.js +217 -0
- package/out/pack/signature.js +839 -0
- package/out/remote/handleRemoteChat.js +44 -3
- package/out/remote/remoteTrust.js +277 -0
- package/out/remote/serveClient.js +52 -5
- package/out/runner/host.js +131 -0
- package/out/runner/protocol.js +108 -0
- package/out/runner/remoteEngine.js +130 -0
- package/out/runtime/interfaces/edit-tracker.js +2 -0
- package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
- package/out/sandbox/containerDriver.js +570 -0
- package/out/sandbox/processDriver.js +315 -0
- package/out/sandbox/sandboxRunner.js +191 -0
- package/out/sandbox/support.js +301 -0
- package/out/sandbox/types.js +55 -0
- package/out/scripts/agentide-litellm.sh +67 -5
- package/out/scripts/agentide_litellm_hooks_v1.py +182 -21
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +94 -118
- package/out/scripts/analytics/setup-analytics.ps1 +37 -13
- package/out/scripts/analytics/setup-analytics.sh +93 -13
- package/out/scripts/analytics/start-analytics.sh +86 -14
- package/out/scripts/gateway/setup-gateway.sh +72 -32
- package/out/shared/models.js +12 -111
- package/out/shared/providers.js +2 -2
- package/out/spotlight/install.js +7 -1
- package/out/store/index.js +41 -10
- package/out/sync.js +3 -1
- package/out/test/analytics.test.js +198 -34
- package/out/test/auth-baseurl.test.js +132 -0
- package/out/test/auth-bootstrapApply.test.js +27 -6
- package/out/test/auth-broker-hardening.test.js +227 -0
- package/out/test/auth-broker.test.js +112 -9
- package/out/test/blueprint-registry.test.js +105 -0
- package/out/test/branding.test.js +13 -0
- package/out/test/caps-governor.test.js +455 -0
- package/out/test/codex-new-file-survival.test.js +119 -0
- package/out/test/configuration-scopes.test.js +131 -0
- package/out/test/credential-context.test.js +23 -28
- package/out/test/custom-env-layering.test.js +151 -0
- package/out/test/data-plane-engine-wiring.test.js +250 -0
- package/out/test/data-plane.test.js +311 -0
- package/out/test/deploy-init.test.js +295 -4
- package/out/test/egress-broker.test.js +172 -0
- package/out/test/fleetDisplayUtils.test.js +4 -0
- package/out/test/gateway-client.test.js +28 -0
- package/out/test/gateway-install-source.test.js +111 -0
- package/out/test/gateway-lifecycle.test.js +1 -1
- package/out/test/gateway-outbound.test.js +7 -0
- package/out/test/gateway-secrets-preflight.test.js +127 -0
- package/out/test/gateway-session-routing.test.js +18 -0
- package/out/test/governor-hardening.test.js +217 -0
- package/out/test/headless-sync.test.js +22 -0
- package/out/test/idp-init.test.js +151 -0
- package/out/test/lease-signature.test.js +357 -0
- package/out/test/lease-trust-root.test.js +273 -0
- package/out/test/lease-validation.test.js +169 -0
- package/out/test/lease.test.js +114 -0
- package/out/test/mcp-obo.test.js +52 -27
- package/out/test/mcp.test.js +69 -3
- package/out/test/model-routing.test.js +133 -0
- package/out/test/pack-admission.test.js +216 -0
- package/out/test/pack-digest-parity.test.js +140 -0
- package/out/test/pack-reconcile.test.js +183 -0
- package/out/test/pack-signature-artifact.test.js +103 -0
- package/out/test/pack-signature.test.js +176 -0
- package/out/test/pack-trust-root.test.js +202 -0
- package/out/test/remote-trust.test.js +242 -0
- package/out/test/runner-protocol.test.js +249 -0
- package/out/test/sandbox-driver.test.js +553 -0
- package/out/test/sandbox-hardening.test.js +395 -0
- package/out/test/sandbox-runner.test.js +343 -0
- package/out/test/serve-gateway-turn.test.js +267 -0
- package/out/test/serve-hardening.test.js +125 -6
- package/out/test/serveClient.test.js +38 -5
- package/out/test/sessionShip.test.js +560 -0
- package/out/test/spotlightPublish.test.js +28 -0
- package/out/test/state-sync-peer.test.js +18 -5
- package/out/test/store-install-safe.test.js +69 -0
- package/out/test/tls-bypass.test.js +90 -0
- package/out/test/warrant.test.js +138 -0
- package/out/utils.js +73 -20
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
require("mocha");
|
|
40
|
+
const assert = __importStar(require("assert"));
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const os = __importStar(require("node:os"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const reconcile_1 = require("../pack/reconcile");
|
|
45
|
+
suite('store install path containment', () => {
|
|
46
|
+
test('safeJoin refuses traversal paths', () => {
|
|
47
|
+
const root = path.join(os.tmpdir(), 'pack-install-root');
|
|
48
|
+
assert.strictEqual((0, reconcile_1.safeJoin)(root, '../../etc/passwd'), undefined);
|
|
49
|
+
assert.strictEqual((0, reconcile_1.safeJoin)(root, '/etc/passwd'), undefined);
|
|
50
|
+
const ok = (0, reconcile_1.safeJoin)(root, 'agent.yaml');
|
|
51
|
+
assert.ok(ok?.endsWith(`${path.sep}agent.yaml`));
|
|
52
|
+
});
|
|
53
|
+
test('install extract pattern refuses escape before write', async () => {
|
|
54
|
+
const installDir = fs.mkdtempSync(path.join(os.tmpdir(), 'store-install-'));
|
|
55
|
+
const files = [{ path: '../escaped.txt', content: Buffer.from('nope') }];
|
|
56
|
+
let refused = false;
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const filePath = (0, reconcile_1.safeJoin)(installDir, file.path);
|
|
59
|
+
if (!filePath) {
|
|
60
|
+
refused = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
64
|
+
await fs.promises.writeFile(filePath, file.content);
|
|
65
|
+
}
|
|
66
|
+
assert.strictEqual(refused, true);
|
|
67
|
+
assert.equal(fs.existsSync(path.join(path.dirname(installDir), 'escaped.txt')), false);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* `applyLocalTlsBypass` sets NODE_TLS_REJECT_UNAUTHORIZED=0 in an engine child's environment.
|
|
6
|
+
* It used to decide with `url.includes('localhost')`, so `https://localhost.attacker.tld/v1`
|
|
7
|
+
* disabled certificate verification for that child.
|
|
8
|
+
*--------------------------------------------------------------------------------------------*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
require("mocha");
|
|
44
|
+
const assert = __importStar(require("assert"));
|
|
45
|
+
const utils_1 = require("../utils");
|
|
46
|
+
suite('local HTTPS detection', () => {
|
|
47
|
+
test('recognises the genuinely local endpoints', () => {
|
|
48
|
+
assert.ok((0, utils_1.isLocalHttps)('https://localhost:4000/v1'));
|
|
49
|
+
assert.ok((0, utils_1.isLocalHttps)('https://127.0.0.1:4000/v1'));
|
|
50
|
+
assert.ok((0, utils_1.isLocalHttps)('https://0.0.0.0:4000/v1'));
|
|
51
|
+
assert.ok((0, utils_1.isLocalHttps)('https://[::1]:4000/v1'));
|
|
52
|
+
assert.ok((0, utils_1.isLocalHttps)('https://LOCALHOST:4000/v1'), 'hostname comparison is case-insensitive');
|
|
53
|
+
});
|
|
54
|
+
test('a registrable domain that merely CONTAINS a loopback name is not local', () => {
|
|
55
|
+
// The bug: an attacker registers localhost.attacker.tld, points a routing setting at it,
|
|
56
|
+
// and the engine child stops verifying certificates for every TLS connection it makes.
|
|
57
|
+
assert.ok(!(0, utils_1.isLocalHttps)('https://localhost.attacker.tld/v1'));
|
|
58
|
+
assert.ok(!(0, utils_1.isLocalHttps)('https://127.0.0.1.attacker.tld/v1'));
|
|
59
|
+
assert.ok(!(0, utils_1.isLocalHttps)('https://not-localhost.example/v1'));
|
|
60
|
+
assert.ok(!(0, utils_1.isLocalHttps)('https://evil.example/?redirect=localhost'));
|
|
61
|
+
assert.ok(!(0, utils_1.isLocalHttps)('https://evil.example/localhost'));
|
|
62
|
+
assert.ok(!(0, utils_1.isLocalHttps)('https://evil.example/#127.0.0.1'));
|
|
63
|
+
assert.ok(!(0, utils_1.isLocalHttps)('https://user@localhost.attacker.tld/v1'));
|
|
64
|
+
});
|
|
65
|
+
test('http, empty and malformed values are not local HTTPS', () => {
|
|
66
|
+
assert.ok(!(0, utils_1.isLocalHttps)('http://localhost:4000/v1'), 'plain http needs no TLS bypass');
|
|
67
|
+
assert.ok(!(0, utils_1.isLocalHttps)(undefined));
|
|
68
|
+
assert.ok(!(0, utils_1.isLocalHttps)(''));
|
|
69
|
+
assert.ok(!(0, utils_1.isLocalHttps)('https://'));
|
|
70
|
+
assert.ok(!(0, utils_1.isLocalHttps)('localhost:4000'));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
suite('local TLS bypass application', () => {
|
|
74
|
+
test('sets the bypass only for a genuinely local HTTPS endpoint', () => {
|
|
75
|
+
const env = {};
|
|
76
|
+
assert.strictEqual((0, utils_1.applyLocalTlsBypass)(env, 'https://127.0.0.1:4000/v1'), true);
|
|
77
|
+
assert.strictEqual(env.NODE_TLS_REJECT_UNAUTHORIZED, '0');
|
|
78
|
+
});
|
|
79
|
+
test('leaves the child env untouched for a lookalike host', () => {
|
|
80
|
+
const env = {};
|
|
81
|
+
assert.strictEqual((0, utils_1.applyLocalTlsBypass)(env, 'https://localhost.attacker.tld/v1'), false);
|
|
82
|
+
assert.strictEqual(env.NODE_TLS_REJECT_UNAUTHORIZED, undefined);
|
|
83
|
+
assert.deepStrictEqual(env, {}, 'nothing at all is added');
|
|
84
|
+
});
|
|
85
|
+
test('leaves the child env untouched for a remote provider', () => {
|
|
86
|
+
const env = {};
|
|
87
|
+
assert.strictEqual((0, utils_1.applyLocalTlsBypass)(env, 'https://api.anthropic.com/v1'), false);
|
|
88
|
+
assert.strictEqual(env.NODE_TLS_REJECT_UNAUTHORIZED, undefined);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Signed warrants (PLAN/serve-v4-clean.md §6).
|
|
4
|
+
*
|
|
5
|
+
* The properties under test are the ones that delete the P1 bug class:
|
|
6
|
+
* - a gateway can verify but NEVER mint (no shared secret anywhere);
|
|
7
|
+
* - a warrant is bound to its sandbox, so a lifted one fails from another sandbox;
|
|
8
|
+
* - audience, expiry, and algorithm confusion are all refused.
|
|
9
|
+
*--------------------------------------------------------------------------------------------*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
require("mocha");
|
|
45
|
+
const assert = __importStar(require("assert"));
|
|
46
|
+
const crypto = __importStar(require("node:crypto"));
|
|
47
|
+
const warrant_1 = require("../authority/warrant");
|
|
48
|
+
const KEY = (0, warrant_1.generateWarrantKeypair)('kid-1');
|
|
49
|
+
const PUBS = { [KEY.kid]: KEY.publicKeyPem };
|
|
50
|
+
function mint(over = {}) {
|
|
51
|
+
return (0, warrant_1.mintWarrant)({
|
|
52
|
+
user: 'alice@corp.com', pack: 'analyst', packVersion: '1.4.2',
|
|
53
|
+
sandboxId: 'sandbox-1', tier: 2, aud: 'data', ...over,
|
|
54
|
+
}, KEY);
|
|
55
|
+
}
|
|
56
|
+
suite('signed warrants', () => {
|
|
57
|
+
test('round-trips and carries the full evidence key', () => {
|
|
58
|
+
const res = (0, warrant_1.verifyWarrant)(mint(), PUBS, { audience: 'data', channelSandboxId: 'sandbox-1' });
|
|
59
|
+
assert.ok(res.ok, `expected ok, got ${JSON.stringify(res)}`);
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
assert.strictEqual(res.claims.user, 'alice@corp.com');
|
|
64
|
+
assert.strictEqual(res.claims.packVersion, '1.4.2');
|
|
65
|
+
assert.strictEqual(res.claims.tier, 2, 'the isolation tier in force is recorded on every turn');
|
|
66
|
+
assert.ok(res.claims.jti, 'warrant id present — the key observers join evidence on');
|
|
67
|
+
});
|
|
68
|
+
test('a warrant lifted into another sandbox FAILS — channel binding, not bearer', () => {
|
|
69
|
+
const stolen = mint({ sandboxId: 'sandbox-1' });
|
|
70
|
+
const asOwner = (0, warrant_1.verifyWarrant)(stolen, PUBS, { audience: 'data', channelSandboxId: 'sandbox-1' });
|
|
71
|
+
const asThief = (0, warrant_1.verifyWarrant)(stolen, PUBS, { audience: 'data', channelSandboxId: 'sandbox-2' });
|
|
72
|
+
assert.ok(asOwner.ok, 'valid in its own sandbox');
|
|
73
|
+
assert.ok(!asThief.ok && /not bound to this channel/.test(asThief.reason), 'the same signature is refused from a different sandbox');
|
|
74
|
+
});
|
|
75
|
+
test('a gateway holding only the public key cannot mint', () => {
|
|
76
|
+
// The structural claim: verification material is not signing material. Attempting to
|
|
77
|
+
// sign with the SPKI public key throws — there is no path from verify to mint.
|
|
78
|
+
assert.throws(() => {
|
|
79
|
+
crypto.sign('sha256', Buffer.from('x.y'), {
|
|
80
|
+
key: crypto.createPrivateKey(KEY.publicKeyPem),
|
|
81
|
+
dsaEncoding: 'ieee-p1363',
|
|
82
|
+
});
|
|
83
|
+
}, 'a public key cannot be loaded as a private key');
|
|
84
|
+
const doc = (0, warrant_1.jwks)([KEY]);
|
|
85
|
+
const serialized = JSON.stringify(doc);
|
|
86
|
+
assert.ok(!serialized.includes('PRIVATE'), 'JWKS publishes no private material');
|
|
87
|
+
assert.ok(!doc.keys[0].d, 'the EC private scalar `d` must never appear in JWKS');
|
|
88
|
+
});
|
|
89
|
+
test('wrong audience is refused — a data warrant cannot spend at the inference proxy', () => {
|
|
90
|
+
const res = (0, warrant_1.verifyWarrant)(mint({ aud: 'data' }), PUBS, { audience: 'inference', channelSandboxId: 'sandbox-1' });
|
|
91
|
+
assert.ok(!res.ok && /wrong audience/.test(res.reason));
|
|
92
|
+
});
|
|
93
|
+
test('expiry is enforced and TTL is hard-capped at turn length', () => {
|
|
94
|
+
const now = 1_800_000_000;
|
|
95
|
+
const t = mint({ nowSeconds: now, ttlSeconds: 10 });
|
|
96
|
+
assert.ok((0, warrant_1.verifyWarrant)(t, PUBS, { audience: 'data', channelSandboxId: 'sandbox-1', nowSeconds: now + 5 }).ok);
|
|
97
|
+
const late = (0, warrant_1.verifyWarrant)(t, PUBS, { audience: 'data', channelSandboxId: 'sandbox-1', nowSeconds: now + 11 });
|
|
98
|
+
assert.ok(!late.ok && /expired/.test(late.reason));
|
|
99
|
+
// A caller asking for a day-long warrant gets a turn-long one — there is no revocation
|
|
100
|
+
// registry, so lifetime IS the bound.
|
|
101
|
+
const greedy = mint({ nowSeconds: now, ttlSeconds: 86_400 });
|
|
102
|
+
const capped = (0, warrant_1.verifyWarrant)(greedy, PUBS, { audience: 'data', channelSandboxId: 'sandbox-1', nowSeconds: now + 301 });
|
|
103
|
+
assert.ok(!capped.ok && /expired/.test(capped.reason), 'TTL capped at 300s regardless of request');
|
|
104
|
+
});
|
|
105
|
+
test('algorithm confusion is refused (alg=none, alg=HS256)', () => {
|
|
106
|
+
const claims = Buffer.from(JSON.stringify({
|
|
107
|
+
user: 'mallory', pack: 'p', packVersion: '1', sandboxId: 'sandbox-1', tier: 2,
|
|
108
|
+
aud: 'data', iat: 0, exp: 9_999_999_999, jti: 'x',
|
|
109
|
+
})).toString('base64url');
|
|
110
|
+
const none = `${Buffer.from(JSON.stringify({ alg: 'none', kid: KEY.kid })).toString('base64url')}.${claims}.`;
|
|
111
|
+
assert.ok(!(0, warrant_1.verifyWarrant)(none, PUBS, { audience: 'data' }).ok, 'alg=none refused');
|
|
112
|
+
// The classic break: sign with HMAC using the PUBLIC key as the secret.
|
|
113
|
+
const hdr = Buffer.from(JSON.stringify({ alg: 'HS256', kid: KEY.kid })).toString('base64url');
|
|
114
|
+
const forged = crypto.createHmac('sha256', KEY.publicKeyPem).update(`${hdr}.${claims}`).digest('base64url');
|
|
115
|
+
assert.ok(!(0, warrant_1.verifyWarrant)(`${hdr}.${claims}.${forged}`, PUBS, { audience: 'data' }).ok, 'HS256 confusion refused');
|
|
116
|
+
});
|
|
117
|
+
test('a tampered claim invalidates the signature', () => {
|
|
118
|
+
const [h, c, s] = mint().split('.');
|
|
119
|
+
const swapped = JSON.parse(Buffer.from(c, 'base64url').toString('utf8'));
|
|
120
|
+
swapped.user = 'mallory@evil.com'; // escalate to another principal
|
|
121
|
+
const tampered = `${h}.${Buffer.from(JSON.stringify(swapped)).toString('base64url')}.${s}`;
|
|
122
|
+
const res = (0, warrant_1.verifyWarrant)(tampered, PUBS, { audience: 'data', channelSandboxId: 'sandbox-1' });
|
|
123
|
+
assert.ok(!res.ok && /bad signature/.test(res.reason), 'rewriting the principal breaks the signature');
|
|
124
|
+
});
|
|
125
|
+
test('an unknown key id is refused (no implicit trust on rotation)', () => {
|
|
126
|
+
const other = (0, warrant_1.generateWarrantKeypair)('kid-2');
|
|
127
|
+
const res = (0, warrant_1.verifyWarrant)((0, warrant_1.mintWarrant)({
|
|
128
|
+
user: 'a', pack: 'p', packVersion: '1', sandboxId: 'sandbox-1', tier: 2, aud: 'data',
|
|
129
|
+
}, other), PUBS, { audience: 'data', channelSandboxId: 'sandbox-1' });
|
|
130
|
+
assert.ok(!res.ok && /unknown kid/.test(res.reason));
|
|
131
|
+
});
|
|
132
|
+
test('malformed input never throws — the sandbox is untrusted toward the gateway', () => {
|
|
133
|
+
for (const bad of ['', 'a', 'a.b', 'a.b.c', '..', 'null.null.null']) {
|
|
134
|
+
const res = (0, warrant_1.verifyWarrant)(bad, PUBS, { audience: 'data' });
|
|
135
|
+
assert.ok(!res.ok, `must refuse: ${bad}`);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
package/out/utils.js
CHANGED
|
@@ -55,6 +55,7 @@ exports.shortSig = shortSig;
|
|
|
55
55
|
exports.isLocalHttps = isLocalHttps;
|
|
56
56
|
exports.applyLocalTlsBypass = applyLocalTlsBypass;
|
|
57
57
|
exports.getOverrideOpenAIBaseUrl = getOverrideOpenAIBaseUrl;
|
|
58
|
+
exports.inferModelProvider = inferModelProvider;
|
|
58
59
|
exports.resolveApiRouting = resolveApiRouting;
|
|
59
60
|
exports.getExplicitConfigValue = getExplicitConfigValue;
|
|
60
61
|
exports.getAgentideEngineSettings = getAgentideEngineSettings;
|
|
@@ -75,6 +76,7 @@ const path = __importStar(require("path"));
|
|
|
75
76
|
const crypto = __importStar(require("crypto"));
|
|
76
77
|
const shared_1 = require("./shared");
|
|
77
78
|
const branding_1 = require("./branding");
|
|
79
|
+
const customRegistry_1 = require("./engines/customRegistry");
|
|
78
80
|
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
79
81
|
// Lazy-load vscode to allow bundling for CLI/headless mode
|
|
80
82
|
// Use eval to prevent Bun from statically analyzing the require
|
|
@@ -462,12 +464,19 @@ async function syncDirByFileLinks(sourceDir, targetDir, logger, label) {
|
|
|
462
464
|
};
|
|
463
465
|
await cleanup(targetDir, '');
|
|
464
466
|
}
|
|
467
|
+
function getManagedWorkspaceDirs(workspaceRoot, extraSearchPath) {
|
|
468
|
+
const builtins = Object.values(shared_1.AGENT_ENGINES).map(engine => engine.workspaceDir);
|
|
469
|
+
const custom = (0, customRegistry_1.discoverCustomEngineManifests)(workspaceRoot, extraSearchPath)
|
|
470
|
+
.filter(manifest => manifest.workspaceSync?.enabled)
|
|
471
|
+
.map(manifest => manifest.workspaceSync.dir);
|
|
472
|
+
return [...new Set([...builtins, ...custom])];
|
|
473
|
+
}
|
|
465
474
|
/**
|
|
466
475
|
* Ensure workspace root has git initialized and .gitignore entries for agent directories.
|
|
467
476
|
* If the folder is not a git repository, runs `git init`.
|
|
468
|
-
* Adds
|
|
477
|
+
* Adds managed engine workspace dirs (builtins + custom engines with workspaceSync) to root .gitignore.
|
|
469
478
|
*/
|
|
470
|
-
async function ensureWorkspaceGitAndGitignore(workspaceRoot, logger) {
|
|
479
|
+
async function ensureWorkspaceGitAndGitignore(workspaceRoot, logger, options) {
|
|
471
480
|
const gitDir = path.join(workspaceRoot, '.git');
|
|
472
481
|
// Check if .git exists
|
|
473
482
|
const gitExists = await pathExists(gitDir);
|
|
@@ -484,10 +493,7 @@ async function ensureWorkspaceGitAndGitignore(workspaceRoot, logger) {
|
|
|
484
493
|
}
|
|
485
494
|
// Ensure root .gitignore has entries for agent directories
|
|
486
495
|
const rootGitignorePath = path.join(workspaceRoot, '.gitignore');
|
|
487
|
-
|
|
488
|
-
const agentideDirs = [
|
|
489
|
-
...Object.values(shared_1.AGENT_ENGINES).map(engine => engine.workspaceDir),
|
|
490
|
-
];
|
|
496
|
+
const agentideDirs = getManagedWorkspaceDirs(workspaceRoot, options?.extraSearchPath);
|
|
491
497
|
const managedBlockLines = [
|
|
492
498
|
`# ${branding_1.AGENTIDE_BRAND_NAME} (managed)`,
|
|
493
499
|
`# These directories are managed by ${branding_1.AGENTIDE_BRAND_NAME} and should not be committed.`,
|
|
@@ -608,17 +614,49 @@ function shortSig(input) {
|
|
|
608
614
|
// Environment Utilities
|
|
609
615
|
// -----------------------------------------------------------------------------
|
|
610
616
|
/**
|
|
611
|
-
*
|
|
617
|
+
* Hostnames that are unambiguously this machine. Compared against the PARSED hostname, never a
|
|
618
|
+
* substring of the URL.
|
|
619
|
+
*/
|
|
620
|
+
const LOCAL_TLS_HOSTNAMES = new Set(['localhost', '127.0.0.1', '0.0.0.0', '::1', '[::1]']);
|
|
621
|
+
/**
|
|
622
|
+
* Is this URL an HTTPS endpoint on the local machine (a dev LiteLLM/proxy with a self-signed cert)?
|
|
623
|
+
*
|
|
624
|
+
* This used to be `url.includes('localhost')`, which is a substring test on the WHOLE URL — so
|
|
625
|
+
* `https://localhost.attacker.tld/v1` matched, and the caller then disabled TLS verification for
|
|
626
|
+
* the engine child process talking to it. The hostname is parsed and compared exactly now: a
|
|
627
|
+
* registrable domain that merely *contains* "localhost" is a remote host like any other.
|
|
628
|
+
*
|
|
629
|
+
* Note the IPv6 form: `new URL('https://[::1]/').hostname` is `[::1]` (brackets included), so both
|
|
630
|
+
* spellings are in the set.
|
|
612
631
|
*/
|
|
613
632
|
function isLocalHttps(url) {
|
|
614
633
|
if (!url) {
|
|
615
634
|
return false;
|
|
616
635
|
}
|
|
617
|
-
|
|
636
|
+
try {
|
|
637
|
+
const parsed = new URL(url);
|
|
638
|
+
return parsed.protocol === 'https:' && LOCAL_TLS_HOSTNAMES.has(parsed.hostname.toLowerCase());
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
618
643
|
}
|
|
619
644
|
/**
|
|
620
645
|
* Apply TLS verification bypass for local HTTPS in an environment object.
|
|
621
|
-
* Returns
|
|
646
|
+
* Returns whether TLS verification was disabled.
|
|
647
|
+
*
|
|
648
|
+
* **Residual risk, stated plainly.** `NODE_TLS_REJECT_UNAUTHORIZED=0` is PROCESS-WIDE inside the
|
|
649
|
+
* engine child: it disables certificate verification for every TLS connection that child makes,
|
|
650
|
+
* not just the one to the local proxy. Scoping it per-request is not available to us here — the
|
|
651
|
+
* engines are vendor SDKs (Claude Agent SDK, Codex SDK, arbitrary custom-engine binaries) spawned
|
|
652
|
+
* as separate processes, and the only channel we have into their HTTP stack is the environment.
|
|
653
|
+
* Passing a CA bundle instead (`NODE_EXTRA_CA_CERTS`) would be the real fix and requires the local
|
|
654
|
+
* proxy to publish its cert, which it does not today.
|
|
655
|
+
*
|
|
656
|
+
* What bounds the risk is therefore entirely the check above: the bypass is only ever enabled when
|
|
657
|
+
* the routing endpoint is literally this machine, which is also the only place a self-signed cert
|
|
658
|
+
* is expected. That is why the substring match mattered — it turned an opt-in for `127.0.0.1` into
|
|
659
|
+
* an opt-in for any hostname an attacker could register.
|
|
622
660
|
*/
|
|
623
661
|
function applyLocalTlsBypass(env, baseUrl) {
|
|
624
662
|
if (isLocalHttps(baseUrl)) {
|
|
@@ -651,20 +689,35 @@ function getOverrideOpenAIBaseUrl(baseUrl, configProvider) {
|
|
|
651
689
|
}
|
|
652
690
|
return undefined;
|
|
653
691
|
}
|
|
692
|
+
/** Infer the direct provider from a model id — used by engines (e.g. custom) that aren't pinned to one vendor. */
|
|
693
|
+
function inferModelProvider(modelId) {
|
|
694
|
+
return modelId && /claude|anthropic/i.test(modelId) ? 'anthropic' : 'openai';
|
|
695
|
+
}
|
|
654
696
|
/**
|
|
655
|
-
* Resolve API key
|
|
656
|
-
*
|
|
657
|
-
*
|
|
697
|
+
* Resolve the API key + base URL for a turn by the DESTINATION it dials, not a global
|
|
698
|
+
* precedence over provider keys.
|
|
699
|
+
*
|
|
700
|
+
* - LiteLLM proxy selected (`useLiteLLM`) → the LiteLLM key.
|
|
701
|
+
* - direct → the key for THIS engine's provider
|
|
702
|
+
* (codex→openai, claude→anthropic; custom infers
|
|
703
|
+
* from its model). Never the "other" provider's key.
|
|
704
|
+
*
|
|
705
|
+
* An explicit per-request `options.apiKey` (the IDE's single configured key) still wins — the
|
|
706
|
+
* per-provider `creds` are the headless/serve fallback that replaces the old
|
|
707
|
+
* `anthropic || openai` collapse (which sent, e.g., a codex turn to OpenAI with an Anthropic key).
|
|
658
708
|
*
|
|
659
|
-
* @param options - Agent request options
|
|
660
|
-
* @param
|
|
661
|
-
* @param
|
|
662
|
-
* @param configProvider - Optional config provider for resolving
|
|
709
|
+
* @param options - Agent request options (carries an optional explicit apiKey / baseUrl / useLiteLLM)
|
|
710
|
+
* @param creds - Per-destination credentials (EngineConfig satisfies this)
|
|
711
|
+
* @param provider - The engine's direct provider, used only when NOT routing through LiteLLM
|
|
712
|
+
* @param configProvider - Optional config provider for resolving overrideOpenAIBaseUrl
|
|
663
713
|
*/
|
|
664
|
-
function resolveApiRouting(options,
|
|
714
|
+
function resolveApiRouting(options, creds, provider, configProvider) {
|
|
665
715
|
const useLiteLLM = options.useLiteLLM === true;
|
|
666
|
-
const baseUrl = options.baseUrl || (useLiteLLM ? litellmBaseUrl : undefined);
|
|
667
|
-
const
|
|
716
|
+
const baseUrl = options.baseUrl || (useLiteLLM ? creds.litellmBaseUrl : undefined);
|
|
717
|
+
const directKey = provider === 'anthropic' ? creds.anthropicApiKey : creds.openaiApiKey;
|
|
718
|
+
const resolvedApiKey = useLiteLLM
|
|
719
|
+
? (options.apiKey || creds.litellmApiKey || 'sk-dummy')
|
|
720
|
+
: (options.apiKey || directKey || '');
|
|
668
721
|
// For OpenAI-compatible endpoints, check for per-provider overrideOpenAIBaseUrl
|
|
669
722
|
let openaiBaseUrl = baseUrl;
|
|
670
723
|
if (baseUrl) {
|
|
@@ -674,7 +727,7 @@ function resolveApiRouting(options, litellmApiKey, litellmBaseUrl, configProvide
|
|
|
674
727
|
}
|
|
675
728
|
}
|
|
676
729
|
return {
|
|
677
|
-
apiKey:
|
|
730
|
+
apiKey: resolvedApiKey,
|
|
678
731
|
baseUrl,
|
|
679
732
|
openaiBaseUrl,
|
|
680
733
|
useLiteLLM,
|
package/package.json
CHANGED