@agenthippo-ai/agenthippo 0.2.3-dataplane → 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.
Files changed (130) hide show
  1. package/out/analytics/dashboard/provider.js +13 -2
  2. package/out/analytics/dashboard/webview/panel.js +3 -2
  3. package/out/analytics/identity.js +0 -0
  4. package/out/analytics/service.js +23 -25
  5. package/out/analytics/sessionEvents.js +98 -23
  6. package/out/auth/authBaseUrl.js +59 -0
  7. package/out/auth/bootstrapApply.js +9 -10
  8. package/out/auth/brokerClient.js +48 -14
  9. package/out/auth/provider.js +8 -21
  10. package/out/auth/sessionStore.js +3 -40
  11. package/out/auth/signInFlow.js +2 -1
  12. package/out/auth-broker/config.js +50 -5
  13. package/out/auth-broker/server.js +122 -26
  14. package/out/auth-broker/store.js +25 -24
  15. package/out/authority/warrant.js +206 -0
  16. package/out/branding.js +27 -1
  17. package/out/cli/commands/conformance.js +249 -0
  18. package/out/cli/commands/data-plane.js +67 -0
  19. package/out/cli/commands/deploy.js +1 -0
  20. package/out/cli/commands/gateway.js +4 -0
  21. package/out/cli/commands/pack.js +182 -3
  22. package/out/cli/commands/sandbox.js +332 -0
  23. package/out/cli/commands/serve.js +875 -52
  24. package/out/cli/commands/store.js +12 -4
  25. package/out/cli/data-plane/gateway.js +28 -5
  26. package/out/cli/data-plane/index.js +17 -2
  27. package/out/cli/data-plane/registry.js +9 -0
  28. package/out/cli/data-plane/remote.js +106 -0
  29. package/out/cli/data-plane/sidecar.js +161 -0
  30. package/out/cli/deploy/layers/identity.js +85 -4
  31. package/out/cli/deploy-init.js +89 -24
  32. package/out/cli/deps.js +106 -2
  33. package/out/cli/idp-init.js +35 -0
  34. package/out/cli/index.js +155 -16
  35. package/out/cli/pack-install-ref.js +15 -0
  36. package/out/cli/pack-loader.js +8 -1
  37. package/out/cli/pack-runtime.js +61 -0
  38. package/out/cli/runtime.js +35 -1
  39. package/out/cli/serve-hardening.js +80 -3
  40. package/out/cli/utils.js +12 -1
  41. package/out/engines/base.js +32 -3
  42. package/out/engines/claude.js +33 -15
  43. package/out/engines/codex.js +110 -26
  44. package/out/engines/custom.js +8 -0
  45. package/out/evidence/sessionShip.js +610 -0
  46. package/out/extension.js +11 -2
  47. package/out/gateway/bridge.js +40 -2
  48. package/out/gateway/client.js +34 -1
  49. package/out/gateway/installSource.js +68 -0
  50. package/out/gateway/lifecycle.js +185 -51
  51. package/out/gateway/outboundPaths.js +4 -1
  52. package/out/gateway/secretsPreflight.js +90 -0
  53. package/out/gateway/service.js +7 -2
  54. package/out/headless/index.js +17 -1
  55. package/out/live-e2e/mcp-demo/server.mjs +32 -0
  56. package/out/mcp.js +30 -0
  57. package/out/pack/caps.js +271 -0
  58. package/out/pack/governor.js +309 -0
  59. package/out/pack/lease-schema.js +270 -0
  60. package/out/pack/lease.js +640 -0
  61. package/out/pack/reconcile.js +217 -0
  62. package/out/pack/signature.js +839 -0
  63. package/out/remote/handleRemoteChat.js +44 -3
  64. package/out/remote/remoteTrust.js +277 -0
  65. package/out/remote/serveClient.js +52 -5
  66. package/out/runner/host.js +131 -0
  67. package/out/runner/protocol.js +108 -0
  68. package/out/runner/remoteEngine.js +130 -0
  69. package/out/runtime/interfaces/edit-tracker.js +2 -0
  70. package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
  71. package/out/sandbox/containerDriver.js +570 -0
  72. package/out/sandbox/processDriver.js +315 -0
  73. package/out/sandbox/sandboxRunner.js +191 -0
  74. package/out/sandbox/support.js +301 -0
  75. package/out/sandbox/types.js +55 -0
  76. package/out/scripts/agentide-litellm.sh +67 -5
  77. package/out/scripts/agentide_litellm_hooks_v1.py +182 -21
  78. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +94 -132
  79. package/out/scripts/analytics/setup-analytics.ps1 +37 -13
  80. package/out/scripts/analytics/setup-analytics.sh +93 -13
  81. package/out/scripts/analytics/start-analytics.sh +86 -14
  82. package/out/scripts/gateway/setup-gateway.sh +72 -32
  83. package/out/shared/models.js +11 -110
  84. package/out/spotlight/install.js +7 -1
  85. package/out/store/index.js +41 -10
  86. package/out/sync.js +3 -1
  87. package/out/test/analytics.test.js +162 -23
  88. package/out/test/auth-baseurl.test.js +132 -0
  89. package/out/test/auth-bootstrapApply.test.js +27 -6
  90. package/out/test/auth-broker-hardening.test.js +227 -0
  91. package/out/test/auth-broker.test.js +59 -9
  92. package/out/test/branding.test.js +13 -0
  93. package/out/test/caps-governor.test.js +455 -0
  94. package/out/test/codex-new-file-survival.test.js +119 -0
  95. package/out/test/configuration-scopes.test.js +131 -0
  96. package/out/test/data-plane.test.js +67 -0
  97. package/out/test/deploy-init.test.js +40 -0
  98. package/out/test/gateway-client.test.js +28 -0
  99. package/out/test/gateway-install-source.test.js +111 -0
  100. package/out/test/gateway-lifecycle.test.js +1 -1
  101. package/out/test/gateway-outbound.test.js +7 -0
  102. package/out/test/gateway-secrets-preflight.test.js +127 -0
  103. package/out/test/gateway-session-routing.test.js +18 -0
  104. package/out/test/governor-hardening.test.js +217 -0
  105. package/out/test/headless-sync.test.js +22 -0
  106. package/out/test/lease-signature.test.js +357 -0
  107. package/out/test/lease-trust-root.test.js +273 -0
  108. package/out/test/lease-validation.test.js +169 -0
  109. package/out/test/lease.test.js +114 -0
  110. package/out/test/mcp.test.js +27 -1
  111. package/out/test/pack-admission.test.js +216 -0
  112. package/out/test/pack-digest-parity.test.js +140 -0
  113. package/out/test/pack-reconcile.test.js +183 -0
  114. package/out/test/pack-signature-artifact.test.js +103 -0
  115. package/out/test/pack-signature.test.js +176 -0
  116. package/out/test/pack-trust-root.test.js +202 -0
  117. package/out/test/remote-trust.test.js +242 -0
  118. package/out/test/runner-protocol.test.js +249 -0
  119. package/out/test/sandbox-driver.test.js +553 -0
  120. package/out/test/sandbox-hardening.test.js +395 -0
  121. package/out/test/sandbox-runner.test.js +343 -0
  122. package/out/test/serve-hardening.test.js +45 -6
  123. package/out/test/serveClient.test.js +6 -5
  124. package/out/test/sessionShip.test.js +560 -0
  125. package/out/test/spotlightPublish.test.js +28 -0
  126. package/out/test/store-install-safe.test.js +69 -0
  127. package/out/test/tls-bypass.test.js +90 -0
  128. package/out/test/warrant.test.js +138 -0
  129. package/out/utils.js +46 -9
  130. package/package.json +1 -1
@@ -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
@@ -76,6 +76,7 @@ const path = __importStar(require("path"));
76
76
  const crypto = __importStar(require("crypto"));
77
77
  const shared_1 = require("./shared");
78
78
  const branding_1 = require("./branding");
79
+ const customRegistry_1 = require("./engines/customRegistry");
79
80
  const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
80
81
  // Lazy-load vscode to allow bundling for CLI/headless mode
81
82
  // Use eval to prevent Bun from statically analyzing the require
@@ -463,12 +464,19 @@ async function syncDirByFileLinks(sourceDir, targetDir, logger, label) {
463
464
  };
464
465
  await cleanup(targetDir, '');
465
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
+ }
466
474
  /**
467
475
  * Ensure workspace root has git initialized and .gitignore entries for agent directories.
468
476
  * If the folder is not a git repository, runs `git init`.
469
- * Adds the agent root, .claude, .codex to the root .gitignore if not already present.
477
+ * Adds managed engine workspace dirs (builtins + custom engines with workspaceSync) to root .gitignore.
470
478
  */
471
- async function ensureWorkspaceGitAndGitignore(workspaceRoot, logger) {
479
+ async function ensureWorkspaceGitAndGitignore(workspaceRoot, logger, options) {
472
480
  const gitDir = path.join(workspaceRoot, '.git');
473
481
  // Check if .git exists
474
482
  const gitExists = await pathExists(gitDir);
@@ -485,10 +493,7 @@ async function ensureWorkspaceGitAndGitignore(workspaceRoot, logger) {
485
493
  }
486
494
  // Ensure root .gitignore has entries for agent directories
487
495
  const rootGitignorePath = path.join(workspaceRoot, '.gitignore');
488
- // Build the list of directories to ignore from AGENT_ENGINES
489
- const agentideDirs = [
490
- ...Object.values(shared_1.AGENT_ENGINES).map(engine => engine.workspaceDir),
491
- ];
496
+ const agentideDirs = getManagedWorkspaceDirs(workspaceRoot, options?.extraSearchPath);
492
497
  const managedBlockLines = [
493
498
  `# ${branding_1.AGENTIDE_BRAND_NAME} (managed)`,
494
499
  `# These directories are managed by ${branding_1.AGENTIDE_BRAND_NAME} and should not be committed.`,
@@ -609,17 +614,49 @@ function shortSig(input) {
609
614
  // Environment Utilities
610
615
  // -----------------------------------------------------------------------------
611
616
  /**
612
- * Check if a URL is a local HTTPS address (localhost, 127.0.0.1, 0.0.0.0).
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.
613
631
  */
614
632
  function isLocalHttps(url) {
615
633
  if (!url) {
616
634
  return false;
617
635
  }
618
- return url.startsWith('https://') && (url.includes('localhost') || url.includes('127.0.0.1') || url.includes('0.0.0.0'));
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
+ }
619
643
  }
620
644
  /**
621
645
  * Apply TLS verification bypass for local HTTPS in an environment object.
622
- * Returns the modified env and whether TLS was disabled.
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.
623
660
  */
624
661
  function applyLocalTlsBypass(env, baseUrl) {
625
662
  if (isLocalHttps(baseUrl)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenthippo-ai/agenthippo",
3
- "version": "0.2.3-dataplane",
3
+ "version": "1.110.9",
4
4
  "description": "AgentHippo CLI — serve, chat, and deploy agent packs",
5
5
  "author": "AgentHippo <dev@agenthippo.ai>",
6
6
  "license": "SEE LICENSE IN LICENSE",