@openape/proxy 0.2.15 → 0.3.1

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 CHANGED
@@ -41,7 +41,6 @@ listen = "127.0.0.1:9090"
41
41
  idp_url = "https://id.example.com"
42
42
  agent_email = "agent@example.com"
43
43
  default_action = "block"
44
- audit_log = "/var/log/openape-proxy/audit.jsonl"
45
44
 
46
45
  # Always allow (no grant needed)
47
46
  [[allow]]
@@ -72,7 +71,12 @@ duration = 3600
72
71
  | `idp_url` | `string` | IdP URL for grant requests |
73
72
  | `agent_email` | `string` | Agent identity |
74
73
  | `default_action` | `string` | Action when no rule matches (see below) |
75
- | `audit_log` | `string` | Path to JSONL audit log file |
74
+
75
+ > Audit data is **not** written to disk locally. The proxy emits an
76
+ > operator-readable summary line to stderr for each decision; the trustworthy,
77
+ > tamper-resistant audit is recorded server-side by the IdP every time it
78
+ > processes a grant request. Anything stored on the agent's host is also
79
+ > writable by the agent, so it can't be relied on as evidence.
76
80
 
77
81
  ### Rule Options
78
82
 
package/dist/index.cjs CHANGED
@@ -23,7 +23,6 @@ function loadMultiAgentConfig(path, overrides) {
23
23
  const baseProxy = {
24
24
  listen: proxy.listen,
25
25
  default_action: proxy.default_action ?? "block",
26
- audit_log: proxy.audit_log,
27
26
  mandatory_auth: overrides?.mandatoryAuth ?? proxy.mandatory_auth
28
27
  };
29
28
  if (Array.isArray(parsed.agents)) {
@@ -88,6 +87,9 @@ function evaluateRules(config2, domain, method, path) {
88
87
  if (config2.proxy.default_action === "block") {
89
88
  return { type: "deny", note: "No matching rule (default: block)" };
90
89
  }
90
+ if (config2.proxy.default_action === "allow") {
91
+ return { type: "allow" };
92
+ }
91
93
  return {
92
94
  type: "grant_required",
93
95
  rule: {
@@ -225,18 +227,12 @@ var GrantsClient = class {
225
227
  };
226
228
 
227
229
  // src/audit.ts
228
- var import_node_fs2 = require("fs");
229
- var auditPath;
230
- function initAudit(path) {
231
- auditPath = path;
230
+ function formatTarget(entry) {
231
+ return entry.path.startsWith("/") ? `${entry.domain}${entry.path}` : entry.path;
232
232
  }
233
233
  function writeAudit(entry) {
234
- const line = JSON.stringify(entry);
235
- console.error(`[audit] ${entry.action} ${entry.method} ${entry.domain}${entry.path}${entry.grant_id ? ` grant=${entry.grant_id}` : ""}`);
236
- if (auditPath) {
237
- (0, import_node_fs2.appendFileSync)(auditPath, `${line}
238
- `);
239
- }
234
+ const grantSuffix = entry.grant_id ? ` grant=${entry.grant_id}` : "";
235
+ console.error(`[audit] ${entry.action} ${entry.method} ${formatTarget(entry)}${grantSuffix}`);
240
236
  }
241
237
 
242
238
  // src/ssrf.ts
@@ -303,7 +299,7 @@ async function isPrivateOrLoopback(hostname2) {
303
299
 
304
300
  // src/connect.ts
305
301
  var import_node_net2 = require("net");
306
- async function handleConnect(config2, req, clientSocket, _head) {
302
+ async function handleConnect(config2, grantsClients, req, clientSocket, _head) {
307
303
  const target = req.url ?? "";
308
304
  const [host, portStr] = target.split(":");
309
305
  const port2 = Number.parseInt(portStr || "443");
@@ -312,15 +308,37 @@ async function handleConnect(config2, req, clientSocket, _head) {
312
308
  clientSocket.destroy();
313
309
  return;
314
310
  }
311
+ const idpHosts = new Set(
312
+ config2.agents.map((a) => {
313
+ try {
314
+ return new URL(a.idp_url).hostname.toLowerCase();
315
+ } catch {
316
+ return "";
317
+ }
318
+ }).filter((h) => h.length > 0)
319
+ );
320
+ if (idpHosts.has(host.toLowerCase())) {
321
+ writeAudit({
322
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
323
+ agent: "system",
324
+ action: "allow",
325
+ domain: host,
326
+ method: "CONNECT",
327
+ path: target,
328
+ rule: "idp-system-bypass"
329
+ });
330
+ tunnel(host, port2, clientSocket);
331
+ return;
332
+ }
315
333
  const mandatoryAuth = config2.proxy.mandatory_auth ?? false;
316
334
  let agentEmail;
317
335
  try {
318
336
  const authHeader = req.headers["proxy-authorization"];
319
337
  let identity = null;
320
- for (const agentConf of config2.agents) {
338
+ for (const agentConf2 of config2.agents) {
321
339
  identity = await verifyAgentAuth(
322
340
  authHeader ?? null,
323
- agentConf.idp_url,
341
+ agentConf2.idp_url,
324
342
  mandatoryAuth && config2.agents.length === 1
325
343
  );
326
344
  if (identity) break;
@@ -361,6 +379,91 @@ async function handleConnect(config2, req, clientSocket, _head) {
361
379
  clientSocket.destroy();
362
380
  return;
363
381
  }
382
+ const agentConf = agentEmail ? config2.agents.find((a) => a.email === agentEmail) : config2.agents[0];
383
+ if (!agentConf) {
384
+ clientSocket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
385
+ clientSocket.destroy();
386
+ return;
387
+ }
388
+ const effectiveEmail = agentEmail ?? agentConf.email;
389
+ const rulesConfig = {
390
+ proxy: {
391
+ listen: config2.proxy.listen,
392
+ idp_url: agentConf.idp_url,
393
+ agent_email: agentConf.email,
394
+ default_action: config2.proxy.default_action
395
+ },
396
+ allow: agentConf.allow ?? [],
397
+ deny: agentConf.deny ?? [],
398
+ grant_required: agentConf.grant_required ?? []
399
+ };
400
+ const action = evaluateRules(rulesConfig, host, "CONNECT", "/");
401
+ const baseAudit = {
402
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
403
+ agent: effectiveEmail,
404
+ domain: host,
405
+ method: "CONNECT",
406
+ path: target
407
+ };
408
+ if (action.type === "deny") {
409
+ writeAudit({ ...baseAudit, action: "deny", rule: "deny-list" });
410
+ const note = action.note ? ` ${action.note}` : "";
411
+ clientSocket.write(`HTTP/1.1 403 Forbidden\r
412
+ Content-Type: text/plain\r
413
+ \r
414
+ Blocked:${note}\r
415
+ `);
416
+ clientSocket.destroy();
417
+ return;
418
+ }
419
+ if (action.type === "grant_required") {
420
+ const grantsClient = grantsClients.get(agentConf.email);
421
+ if (!grantsClient) {
422
+ writeAudit({ ...baseAudit, action: "deny", rule: "grant_required (no client)" });
423
+ clientSocket.write("HTTP/1.1 500 Internal Server Error\r\n\r\nNo grants client for agent\r\n");
424
+ clientSocket.destroy();
425
+ return;
426
+ }
427
+ const startTs = Date.now();
428
+ try {
429
+ const grant = await grantsClient.requestGrant({
430
+ requester: effectiveEmail,
431
+ targetHost: host,
432
+ audience: "ape-proxy",
433
+ grantType: action.rule.grant_type,
434
+ permissions: action.rule.permissions,
435
+ reason: `CONNECT ${host}:${port2}`,
436
+ duration: action.rule.duration
437
+ });
438
+ const decided = await grantsClient.waitForApproval(grant.id);
439
+ const waitedMs = Date.now() - startTs;
440
+ if (decided.status === "approved") {
441
+ writeAudit({ ...baseAudit, action: "grant_approved", rule: "grant_required", grant_id: decided.id, waited_ms: waitedMs });
442
+ } else {
443
+ writeAudit({ ...baseAudit, action: "grant_denied", rule: "grant_required", grant_id: decided.id, waited_ms: waitedMs });
444
+ clientSocket.write(`HTTP/1.1 403 Forbidden\r
445
+ \r
446
+ Grant denied by ${decided.decided_by ?? "policy"}\r
447
+ `);
448
+ clientSocket.destroy();
449
+ return;
450
+ }
451
+ } catch (err) {
452
+ const msg = err instanceof Error ? err.message : "Unknown error";
453
+ writeAudit({ ...baseAudit, action: "grant_timeout", rule: "grant_required", error: msg });
454
+ clientSocket.write(`HTTP/1.1 504 Gateway Timeout\r
455
+ \r
456
+ Grant request failed: ${msg}\r
457
+ `);
458
+ clientSocket.destroy();
459
+ return;
460
+ }
461
+ } else {
462
+ writeAudit({ ...baseAudit, action: "allow", rule: "allow-list" });
463
+ }
464
+ tunnel(host, port2, clientSocket);
465
+ }
466
+ function tunnel(host, port2, clientSocket) {
364
467
  const targetSocket = (0, import_node_net2.connect)(port2, host, () => {
365
468
  clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
366
469
  targetSocket.pipe(clientSocket);
@@ -387,11 +490,19 @@ async function computeRequestHash(method, targetUrl, body) {
387
490
  }
388
491
  return hash.digest("hex");
389
492
  }
390
- function createMultiAgentProxy(config2) {
493
+ function buildGrantsClients(config2) {
391
494
  const grantsClients = /* @__PURE__ */ new Map();
392
495
  for (const agent of config2.agents) {
393
496
  grantsClients.set(agent.email, new GrantsClient(agent.idp_url));
394
497
  }
498
+ return grantsClients;
499
+ }
500
+ function createMultiAgentProxy(config2, grantsClients = buildGrantsClients(config2)) {
501
+ for (const agent of config2.agents) {
502
+ if (!grantsClients.has(agent.email)) {
503
+ grantsClients.set(agent.email, new GrantsClient(agent.idp_url));
504
+ }
505
+ }
395
506
  const mandatoryAuth = config2.proxy.mandatory_auth ?? false;
396
507
  return {
397
508
  port: Number.parseInt(config2.proxy.listen.split(":")[1] || "9090"),
@@ -509,7 +620,7 @@ function createMultiAgentProxy(config2) {
509
620
  const grant = await grantsClient.requestGrant({
510
621
  requester: effectiveEmail,
511
622
  targetHost: domain,
512
- audience: "proxy",
623
+ audience: "ape-proxy",
513
624
  grantType: rule.grant_type,
514
625
  permissions,
515
626
  reason: `${method} ${targetUrl}`,
@@ -536,7 +647,7 @@ function createMultiAgentProxy(config2) {
536
647
  const grant = await grantsClient.requestGrant({
537
648
  requester: effectiveEmail,
538
649
  targetHost: domain,
539
- audience: "proxy",
650
+ audience: "ape-proxy",
540
651
  grantType: rule.grant_type,
541
652
  permissions,
542
653
  reason: `${method} ${targetUrl}`,
@@ -578,10 +689,14 @@ function createMultiAgentProxy(config2) {
578
689
  };
579
690
  }
580
691
  function createNodeHandler(config2) {
581
- const proxy = createMultiAgentProxy(config2);
692
+ const grantsClients = buildGrantsClients(config2);
693
+ const proxy = createMultiAgentProxy(config2, grantsClients);
582
694
  return {
583
695
  handleRequest(req, res) {
584
- const url = `http://${req.headers.host || "localhost"}${req.url || "/"}`;
696
+ const reqUrl = req.url || "/";
697
+ const isAbsolute = reqUrl.startsWith("http://") || reqUrl.startsWith("https://");
698
+ const proxyHost = req.headers.host || "localhost";
699
+ const url = isAbsolute ? `http://${proxyHost}/${reqUrl}` : `http://${proxyHost}${reqUrl}`;
585
700
  const chunks = [];
586
701
  req.on("data", (chunk) => chunks.push(chunk));
587
702
  req.on("end", async () => {
@@ -623,7 +738,7 @@ function createNodeHandler(config2) {
623
738
  });
624
739
  },
625
740
  handleConnect(req, socket, head) {
626
- handleConnect(config2, req, socket, head);
741
+ handleConnect(config2, grantsClients, req, socket, head);
627
742
  }
628
743
  };
629
744
  }
@@ -668,7 +783,6 @@ console.log(`[openape-proxy] Loading config from ${configPath}`);
668
783
  var config = loadMultiAgentConfig(configPath, {
669
784
  mandatoryAuth: values["mandatory-auth"] || void 0
670
785
  });
671
- initAudit(config.proxy.audit_log);
672
786
  if (values["dry-run"]) {
673
787
  console.log("[openape-proxy] DRY RUN mode \u2014 logging only, not blocking");
674
788
  console.log("[openape-proxy] Config loaded:");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/proxy.ts","../src/matcher.ts","../src/auth.ts","../src/grants-client.ts","../src/audit.ts","../src/ssrf.ts","../src/connect.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createServer } from 'node:http'\nimport { parseArgs } from 'node:util'\nimport { loadMultiAgentConfig } from './config.js'\nimport { createNodeHandler } from './proxy.js'\nimport { initAudit } from './audit.js'\n\nconst { values } = parseArgs({\n options: {\n config: { type: 'string', short: 'c', default: 'config.toml' },\n 'dry-run': { type: 'boolean', default: false },\n 'mandatory-auth': { type: 'boolean', default: false },\n },\n})\n\nconst configPath = values.config!\n\nconsole.log(`[openape-proxy] Loading config from ${configPath}`)\nconst config = loadMultiAgentConfig(configPath, {\n mandatoryAuth: values['mandatory-auth'] || undefined,\n})\n\n// Init audit log\ninitAudit(config.proxy.audit_log)\n\nif (values['dry-run']) {\n console.log('[openape-proxy] DRY RUN mode — logging only, not blocking')\n console.log('[openape-proxy] Config loaded:')\n console.log(` Listen: ${config.proxy.listen}`)\n console.log(` Default action: ${config.proxy.default_action}`)\n console.log(` Mandatory auth: ${config.proxy.mandatory_auth ?? false}`)\n console.log(` Agents: ${config.agents.length}`)\n for (const agent of config.agents) {\n const allowCount = agent.allow?.length ?? 0\n const denyCount = agent.deny?.length ?? 0\n const grantCount = agent.grant_required?.length ?? 0\n console.log(` ${agent.email} (${agent.idp_url}) — ${allowCount} allow, ${denyCount} deny, ${grantCount} grant`)\n }\n process.exit(0)\n}\n\nconst handler = createNodeHandler(config)\n\nconst port = Number.parseInt(config.proxy.listen.split(':')[1] || '9090')\nconst hostname = config.proxy.listen.split(':')[0] || '127.0.0.1'\n\nconst server = createServer(handler.handleRequest)\nserver.on('connect', handler.handleConnect)\n\nserver.listen(port, hostname, () => {\n // When configured with port 0, the OS assigns a free port — use the actual\n // bound port for the log line so external orchestrators (apes proxy --) can\n // grep this line to discover where to connect.\n const addr = server.address()\n const actualPort = typeof addr === 'object' && addr ? addr.port : port\n console.log(`[openape-proxy] Listening on http://${hostname}:${actualPort}`)\n console.log(`[openape-proxy] CONNECT tunneling enabled`)\n console.log(`[openape-proxy] Mandatory auth: ${config.proxy.mandatory_auth ?? false}`)\n console.log(`[openape-proxy] Agents: ${config.agents.map(a => a.email).join(', ')}`)\n console.log(`[openape-proxy] Default action: ${config.proxy.default_action}`)\n})\n\n// Graceful shutdown\nprocess.on('SIGINT', () => {\n console.log('\\n[openape-proxy] Shutting down...')\n server.close()\n process.exit(0)\n})\n\nprocess.on('SIGTERM', () => {\n console.log('[openape-proxy] Shutting down...')\n server.close()\n process.exit(0)\n})\n","import { readFileSync } from 'node:fs'\nimport { parse as parseTOML } from 'smol-toml'\nimport type { AgentConfig, MultiAgentProxyConfig, ProxyConfig } from './types.js'\n\nexport function loadConfig(path: string): ProxyConfig {\n const raw = readFileSync(path, 'utf-8')\n\n let parsed: Record<string, unknown>\n if (path.endsWith('.json')) {\n parsed = JSON.parse(raw)\n }\n else {\n parsed = parseTOML(raw) as Record<string, unknown>\n }\n\n const proxy = parsed.proxy as ProxyConfig['proxy']\n if (!proxy?.listen || !proxy?.idp_url || !proxy?.agent_email) {\n throw new Error('Config must have [proxy] with listen, idp_url, and agent_email')\n }\n\n proxy.default_action ??= 'block'\n\n return {\n proxy,\n allow: (parsed.allow ?? []) as ProxyConfig['allow'],\n deny: (parsed.deny ?? []) as ProxyConfig['deny'],\n grant_required: (parsed.grant_required ?? []) as ProxyConfig['grant_required'],\n }\n}\n\n/**\n * Load config as multi-agent format.\n * If the config has an `agents` array, use it directly.\n * Otherwise, convert single-agent format to multi-agent for backward-compat.\n */\nexport function loadMultiAgentConfig(path: string, overrides?: { mandatoryAuth?: boolean }): MultiAgentProxyConfig {\n const raw = readFileSync(path, 'utf-8')\n\n let parsed: Record<string, unknown>\n if (path.endsWith('.json')) {\n parsed = JSON.parse(raw)\n }\n else {\n parsed = parseTOML(raw) as Record<string, unknown>\n }\n\n const proxy = parsed.proxy as Record<string, unknown>\n if (!proxy?.listen) {\n throw new Error('Config must have [proxy] with listen')\n }\n\n const baseProxy: MultiAgentProxyConfig['proxy'] = {\n listen: proxy.listen as string,\n default_action: (proxy.default_action as MultiAgentProxyConfig['proxy']['default_action']) ?? 'block',\n audit_log: proxy.audit_log as string | undefined,\n mandatory_auth: overrides?.mandatoryAuth ?? (proxy.mandatory_auth as boolean | undefined),\n }\n\n // Multi-agent format: has agents array\n if (Array.isArray(parsed.agents)) {\n return {\n proxy: baseProxy,\n agents: parsed.agents as AgentConfig[],\n }\n }\n\n // Single-agent format: convert to multi-agent\n const idpUrl = proxy.idp_url as string\n const agentEmail = proxy.agent_email as string\n if (!idpUrl || !agentEmail) {\n throw new Error('Single-agent config requires proxy.idp_url and proxy.agent_email')\n }\n\n return {\n proxy: baseProxy,\n agents: [{\n email: agentEmail,\n idp_url: idpUrl,\n allow: (parsed.allow ?? []) as AgentConfig['allow'],\n deny: (parsed.deny ?? []) as AgentConfig['deny'],\n grant_required: (parsed.grant_required ?? []) as AgentConfig['grant_required'],\n }],\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { createHash } from 'node:crypto'\nimport type { AgentConfig, AuditEntry, MultiAgentProxyConfig, ProxyConfig } from './types.js'\nimport { evaluateRules } from './matcher.js'\nimport { AuthError, verifyAgentAuth } from './auth.js'\nimport { GrantsClient } from './grants-client.js'\nimport { writeAudit } from './audit.js'\nimport { isPrivateOrLoopback } from './ssrf.js'\nimport { handleConnect } from './connect.js'\n\n/**\n * Compute a request hash that uniquely identifies the intent.\n * hash = sha256(METHOD + \" \" + FULL_URL + \"\\n\" + BODY)\n * This binds the grant to the exact request — no bait-and-switch.\n */\nasync function computeRequestHash(method: string, targetUrl: string, body: ArrayBuffer | null): Promise<string> {\n const hash = createHash('sha256')\n hash.update(`${method} ${targetUrl}\\n`)\n if (body && body.byteLength > 0) {\n hash.update(new Uint8Array(body))\n }\n return hash.digest('hex')\n}\n\n/** Legacy single-agent proxy */\nexport function createProxy(config: ProxyConfig) {\n const multiConfig: MultiAgentProxyConfig = {\n proxy: {\n listen: config.proxy.listen,\n default_action: config.proxy.default_action,\n audit_log: config.proxy.audit_log,\n mandatory_auth: config.proxy.mandatory_auth,\n },\n agents: [{\n email: config.proxy.agent_email,\n idp_url: config.proxy.idp_url,\n allow: config.allow,\n deny: config.deny,\n grant_required: config.grant_required,\n }],\n }\n return createMultiAgentProxy(multiConfig)\n}\n\n/** Multi-agent proxy with SSRF protection and mandatory auth */\nexport function createMultiAgentProxy(config: MultiAgentProxyConfig) {\n const grantsClients = new Map<string, GrantsClient>()\n for (const agent of config.agents) {\n grantsClients.set(agent.email, new GrantsClient(agent.idp_url))\n }\n\n const mandatoryAuth = config.proxy.mandatory_auth ?? false\n\n return {\n port: Number.parseInt(config.proxy.listen.split(':')[1] || '9090'),\n hostname: config.proxy.listen.split(':')[0] || '127.0.0.1',\n\n async fetch(req: Request): Promise<Response> {\n const url = new URL(req.url)\n const startTime = Date.now()\n\n // Health endpoint\n if (url.pathname === '/healthz') {\n return Response.json({\n status: 'ok',\n agents: config.agents.map(a => a.email),\n })\n }\n\n // Parse target URL from the path\n const targetUrl = url.pathname.slice(1) + url.search\n let targetParsed: URL\n try {\n targetParsed = new URL(targetUrl)\n }\n catch {\n return new Response(\n 'Invalid target URL. Send requests as: http://proxy:port/https://target.com/path',\n { status: 400 },\n )\n }\n\n const domain = targetParsed.hostname\n const method = req.method\n const path = targetParsed.pathname\n\n // SSRF protection — block private/loopback IPs before any rule evaluation\n if (await isPrivateOrLoopback(domain)) {\n return new Response('Blocked: private/loopback IP', { status: 403 })\n }\n\n // Read body once (needed for hash + forwarding)\n const bodyBuffer = req.body ? await req.arrayBuffer() : null\n\n // Verify agent identity — find IdP URL from first agent (for JWKS verification)\n // In multi-agent mode, we need the JWT to identify the agent first.\n // We try verification against each agent's IdP until one succeeds.\n let agentIdentity: { email: string, act: 'agent' } | null = null\n try {\n for (const agentConf of config.agents) {\n agentIdentity = await verifyAgentAuth(\n req.headers.get('proxy-authorization'),\n agentConf.idp_url,\n mandatoryAuth && config.agents.length === 1,\n )\n if (agentIdentity) break\n }\n\n // If mandatory auth and no identity found from any IdP\n if (mandatoryAuth && !agentIdentity) {\n throw new AuthError('JWT required')\n }\n }\n catch (err) {\n if (err instanceof AuthError) {\n return new Response(`Unauthorized: ${err.message}`, { status: 401 })\n }\n throw err\n }\n\n // Find the matching agent config\n const agentEmail = agentIdentity?.email\n let agentConf: AgentConfig | undefined\n\n if (agentEmail) {\n agentConf = config.agents.find(a => a.email === agentEmail)\n if (!agentConf) {\n return new Response(`Forbidden: unknown agent ${agentEmail}`, { status: 403 })\n }\n }\n else if (config.agents.length === 1) {\n // Non-mandatory auth, single agent: use the only agent config\n agentConf = config.agents[0]\n }\n else {\n return new Response('Unauthorized: JWT required for multi-agent proxy', { status: 401 })\n }\n\n const effectiveEmail = agentEmail ?? agentConf.email\n const grantsClient = grantsClients.get(agentConf.email)!\n\n // Build a ProxyConfig-shaped object for evaluateRules\n const rulesConfig: ProxyConfig = {\n proxy: {\n listen: config.proxy.listen,\n idp_url: agentConf.idp_url,\n agent_email: agentConf.email,\n default_action: config.proxy.default_action,\n },\n allow: agentConf.allow ?? [],\n deny: agentConf.deny ?? [],\n grant_required: agentConf.grant_required ?? [],\n }\n\n // Evaluate rules\n const action = evaluateRules(rulesConfig, domain, method, path)\n\n const baseAudit: Omit<AuditEntry, 'action' | 'rule'> = {\n ts: new Date().toISOString(),\n agent: effectiveEmail,\n domain,\n method,\n path,\n }\n\n // DENY\n if (action.type === 'deny') {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'deny-list', grant_id: null })\n return new Response(`Blocked: ${action.note || 'deny rule'}`, { status: 403 })\n }\n\n // ALLOW (no grant needed)\n if (action.type === 'allow') {\n writeAudit({ ...baseAudit, action: 'allow', rule: 'allow-list', grant_id: null })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n // GRANT REQUIRED\n const rule = action.rule\n const permissions = rule.permissions ?? [`${method.toLowerCase()}:${domain}`]\n\n // Compute request hash — binds grant to exact method + URL + body\n const requestHash = await computeRequestHash(method, targetUrl, bodyBuffer)\n\n // Check for existing grant\n const existing = await grantsClient.findExistingGrant(\n effectiveEmail,\n domain,\n 'proxy',\n permissions,\n ).catch(() => null)\n\n if (existing) {\n writeAudit({\n ...baseAudit,\n action: 'grant_approved',\n rule: 'standing-grant',\n grant_id: existing.id,\n request_hash: requestHash,\n })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n // No existing grant — behavior depends on default_action\n if (config.proxy.default_action === 'block') {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'no-grant (block mode)', grant_id: null })\n return new Response('No grant — blocked', { status: 403 })\n }\n\n if (config.proxy.default_action === 'request-async') {\n // Create grant request, return 407 immediately\n const grant = await grantsClient.requestGrant({\n requester: effectiveEmail,\n targetHost: domain,\n audience: 'proxy',\n grantType: rule.grant_type,\n permissions,\n reason: `${method} ${targetUrl}`,\n requestHash,\n duration: rule.duration,\n }).catch(() => null)\n\n writeAudit({\n ...baseAudit,\n action: 'grant_denied',\n rule: 'grant_required (async)',\n grant_id: grant?.id ?? null,\n })\n\n return new Response(\n JSON.stringify({\n error: 'Grant required',\n grant_id: grant?.id,\n message: 'Grant request created. Retry after approval.',\n }),\n { status: 407, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n // BLOCKING mode: create grant request and wait\n console.error(`[proxy] Requesting grant for ${method} ${domain}${path} — waiting for approval...`)\n\n try {\n const grant = await grantsClient.requestGrant({\n requester: effectiveEmail,\n targetHost: domain,\n audience: 'proxy',\n grantType: rule.grant_type,\n permissions,\n reason: `${method} ${targetUrl}`,\n requestHash,\n duration: rule.duration,\n })\n\n const approved = await grantsClient.waitForApproval(grant.id)\n\n const waitedMs = Date.now() - startTime\n\n if (approved.status === 'approved') {\n writeAudit({\n ...baseAudit,\n action: 'grant_approved',\n rule: 'grant_required',\n grant_id: approved.id,\n request_hash: requestHash,\n waited_ms: waitedMs,\n })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n writeAudit({\n ...baseAudit,\n action: 'grant_denied',\n rule: 'grant_required',\n grant_id: approved.id,\n waited_ms: waitedMs,\n })\n return new Response(`Grant denied by ${approved.decided_by}`, { status: 403 })\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : 'Unknown error'\n writeAudit({\n ...baseAudit,\n action: 'grant_timeout',\n rule: 'grant_required',\n error: msg,\n })\n return new Response(`Grant request failed: ${msg}`, { status: 504 })\n }\n },\n }\n}\n\n/**\n * Create a node:http compatible handler for use with http.createServer().\n * Returns both a request handler and a CONNECT handler.\n */\nexport function createNodeHandler(config: MultiAgentProxyConfig): {\n handleRequest: (req: IncomingMessage, res: ServerResponse) => void\n handleConnect: (req: IncomingMessage, socket: Socket, head: Buffer) => void\n} {\n const proxy = createMultiAgentProxy(config)\n\n return {\n handleRequest(req: IncomingMessage, res: ServerResponse) {\n // Convert IncomingMessage to Request and use existing fetch logic\n const url = `http://${req.headers.host || 'localhost'}${req.url || '/'}`\n const chunks: Buffer[] = []\n\n req.on('data', (chunk: Buffer) => chunks.push(chunk))\n req.on('end', async () => {\n try {\n const body = chunks.length > 0 ? Buffer.concat(chunks) : undefined\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n headers.set(key, Array.isArray(value) ? value.join(', ') : value)\n }\n }\n\n const request = new Request(url, {\n method: req.method,\n headers,\n body: body && req.method !== 'GET' && req.method !== 'HEAD' ? body : undefined,\n duplex: 'half',\n } as RequestInit)\n\n const response = await proxy.fetch(request)\n\n res.writeHead(response.status, response.statusText, Object.fromEntries(response.headers))\n if (response.body) {\n const reader = response.body.getReader()\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read()\n if (done) {\n res.end()\n return\n }\n res.write(value)\n return pump()\n }\n await pump()\n }\n else {\n res.end()\n }\n }\n catch {\n res.writeHead(502)\n res.end('Proxy error')\n }\n })\n },\n\n handleConnect(req: IncomingMessage, socket: Socket, head: Buffer) {\n handleConnect(config, req, socket, head)\n },\n }\n}\n\n/**\n * Forward a request to the target URL.\n * Strips proxy-specific headers, preserves the rest.\n */\nasync function forwardRequest(originalReq: Request, targetUrl: string, cachedBody?: ArrayBuffer | null): Promise<Response> {\n const headers = new Headers(originalReq.headers)\n // Remove proxy-specific headers\n headers.delete('proxy-authorization')\n headers.delete('proxy-connection')\n // Don't send host of the proxy\n headers.delete('host')\n\n const body = cachedBody && cachedBody.byteLength > 0 ? cachedBody : null\n\n try {\n const res = await fetch(targetUrl, {\n method: originalReq.method,\n headers,\n body,\n duplex: 'half',\n redirect: 'manual',\n })\n\n // Stream the response back\n const responseHeaders = new Headers(res.headers)\n // Remove hop-by-hop headers\n responseHeaders.delete('transfer-encoding')\n responseHeaders.delete('connection')\n\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers: responseHeaders,\n })\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : 'Upstream error'\n return new Response(`Proxy error: ${msg}`, { status: 502 })\n }\n}\n","import type { ProxyConfig, RuleAction, RuleEntry } from './types.js'\n\n/**\n * Match a glob pattern against a string.\n * Supports * (any segment) and ** (any number of segments).\n */\nfunction globMatch(pattern: string, value: string): boolean {\n // Simple glob: convert * to regex\n const regex = new RegExp(\n `^${\n pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex chars except *\n .replace(/\\*\\*/g, '<<<DOUBLESTAR>>>')\n .replace(/\\*/g, '[^/]*')\n .replace(/<<<DOUBLESTAR>>>/g, '.*')\n }$`,\n )\n return regex.test(value)\n}\n\nfunction matchesRule(rule: RuleEntry, domain: string, method: string, path: string): boolean {\n // Domain match (supports wildcards like *.github.com)\n if (!globMatch(rule.domain, domain)) return false\n\n // Method match (if specified)\n if (rule.methods && rule.methods.length > 0) {\n if (!rule.methods.includes(method.toUpperCase())) return false\n }\n\n // Path match (if specified)\n if (rule.path) {\n if (!globMatch(rule.path, path)) return false\n }\n\n return true\n}\n\n/**\n * Evaluate rules in order: deny → allow → grant_required → default_action\n */\nexport function evaluateRules(\n config: ProxyConfig,\n domain: string,\n method: string,\n path: string,\n): RuleAction {\n // 1. Check deny list first\n for (const rule of config.deny) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'deny', note: rule.note }\n }\n }\n\n // 2. Check allow list\n for (const rule of config.allow) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'allow' }\n }\n }\n\n // 3. Check grant_required rules (most specific first)\n for (const rule of config.grant_required) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'grant_required', rule }\n }\n }\n\n // 4. Default: treat as grant_required with 'once'\n if (config.proxy.default_action === 'block') {\n return { type: 'deny', note: 'No matching rule (default: block)' }\n }\n\n return {\n type: 'grant_required',\n rule: {\n domain: '*',\n grant_type: 'once',\n },\n }\n}\n","import { verifyJWT, createRemoteJWKS } from '@openape/core'\n\nexport interface AgentIdentity {\n email: string\n act: 'agent'\n}\n\nexport class AuthError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'AuthError'\n }\n}\n\n/**\n * Verify agent JWT from Proxy-Authorization header.\n * Returns the agent identity or null if invalid/missing.\n * When mandatory is true, throws AuthError if no valid JWT is provided.\n */\nexport async function verifyAgentAuth(\n authHeader: string | null,\n idpUrl: string,\n mandatory: boolean = false,\n): Promise<AgentIdentity | null> {\n if (!authHeader) {\n if (mandatory) throw new AuthError('JWT required')\n return null\n }\n\n const match = authHeader.match(/^Bearer (.+)$/i)\n if (!match) {\n if (mandatory) throw new AuthError('Invalid authorization header')\n return null\n }\n\n const token = match[1]\n\n try {\n const jwks = createRemoteJWKS(`${idpUrl}/.well-known/jwks.json`)\n const { payload } = await verifyJWT(token, jwks, { issuer: idpUrl })\n\n if (payload.act !== 'agent' || !payload.sub) {\n if (mandatory) throw new AuthError('Invalid agent token')\n return null\n }\n\n return {\n email: payload.sub as string,\n act: 'agent',\n }\n }\n catch (err) {\n if (err instanceof AuthError) throw err\n if (mandatory) throw new AuthError('JWT verification failed')\n return null\n }\n}\n","import type { OpenApeGrant, GrantType } from '@openape/core'\n\n/**\n * Client for the IdP's grant management API.\n * Creates grant requests and polls for approval.\n */\nexport class GrantsClient {\n private idpUrl: string\n private agentToken: string | undefined\n\n constructor(idpUrl: string) {\n this.idpUrl = idpUrl.replace(/\\/$/, '')\n }\n\n setAgentToken(token: string): void {\n this.agentToken = token\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' }\n if (this.agentToken) {\n h.Authorization = `Bearer ${this.agentToken}`\n }\n return h\n }\n\n /**\n * Create a grant request on the IdP.\n */\n async requestGrant(opts: {\n requester: string\n targetHost: string\n audience: string\n grantType: GrantType\n permissions?: string[]\n reason?: string\n requestHash?: string\n duration?: number\n }): Promise<OpenApeGrant> {\n const res = await fetch(`${this.idpUrl}/api/grants`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify({\n requester: opts.requester,\n target_host: opts.targetHost,\n audience: opts.audience,\n grant_type: opts.grantType,\n permissions: opts.permissions,\n reason: opts.reason,\n request_hash: opts.requestHash,\n duration: opts.duration,\n }),\n })\n\n if (!res.ok) {\n throw new Error(`Grant request failed: ${res.status} ${await res.text()}`)\n }\n\n return res.json() as Promise<OpenApeGrant>\n }\n\n /**\n * Poll a grant until it's approved, denied, or timeout.\n */\n async waitForApproval(\n grantId: string,\n timeoutMs: number = 300_000,\n pollIntervalMs: number = 2_000,\n ): Promise<OpenApeGrant> {\n const deadline = Date.now() + timeoutMs\n\n while (Date.now() < deadline) {\n const res = await fetch(`${this.idpUrl}/api/grants/${grantId}`, {\n headers: this.headers(),\n })\n\n if (!res.ok) {\n throw new Error(`Grant poll failed: ${res.status}`)\n }\n\n const grant = await res.json() as OpenApeGrant\n if (grant.status !== 'pending') {\n return grant\n }\n\n await new Promise(r => setTimeout(r, pollIntervalMs))\n }\n\n throw new Error(`Grant approval timed out after ${timeoutMs}ms`)\n }\n\n /**\n * Check if there's an existing approved grant for a host+audience+permissions combo.\n * Ignores `once` grants (they're single-use).\n */\n async findExistingGrant(\n requester: string,\n targetHost: string,\n audience: string,\n permissions?: string[],\n ): Promise<OpenApeGrant | null> {\n const params = new URLSearchParams({\n requester,\n status: 'approved',\n })\n\n const res = await fetch(`${this.idpUrl}/api/grants?${params}`, {\n headers: this.headers(),\n })\n\n if (!res.ok) return null\n\n const grants = await res.json() as OpenApeGrant[]\n const now = Math.floor(Date.now() / 1000)\n\n return grants.find((g) => {\n if (g.status !== 'approved') return false\n if (g.expires_at && g.expires_at <= now) return false\n if (g.request?.grant_type === 'once') return false\n if (g.request?.target_host !== targetHost) return false\n if (g.request?.audience !== audience) return false\n if (permissions?.length && g.request?.permissions?.length) {\n const grantedPerms = new Set(g.request.permissions)\n if (!permissions.every(p => grantedPerms.has(p))) return false\n }\n return true\n }) ?? null\n }\n}\n","import { appendFileSync } from 'node:fs'\nimport type { AuditEntry } from './types.js'\n\nlet auditPath: string | undefined\n\nexport function initAudit(path?: string): void {\n auditPath = path\n}\n\nexport function writeAudit(entry: AuditEntry): void {\n const line = JSON.stringify(entry)\n\n // Always log to stderr\n console.error(`[audit] ${entry.action} ${entry.method} ${entry.domain}${entry.path}${entry.grant_id ? ` grant=${entry.grant_id}` : ''}`)\n\n // Write to file if configured\n if (auditPath) {\n appendFileSync(auditPath, `${line}\\n`)\n }\n}\n","import { resolve4, resolve6 } from 'node:dns/promises'\nimport { isIP } from 'node:net'\n\nconst PRIVATE_RANGES_V4 = [\n { prefix: 0x7F000000, mask: 0xFF000000 }, // 127.0.0.0/8\n { prefix: 0x0A000000, mask: 0xFF000000 }, // 10.0.0.0/8\n { prefix: 0xAC100000, mask: 0xFFF00000 }, // 172.16.0.0/12\n { prefix: 0xC0A80000, mask: 0xFFFF0000 }, // 192.168.0.0/16\n { prefix: 0xA9FE0000, mask: 0xFFFF0000 }, // 169.254.0.0/16\n { prefix: 0x00000000, mask: 0xFF000000 }, // 0.0.0.0/8\n]\n\nfunction ipv4ToNumber(ip: string): number {\n const parts = ip.split('.').map(Number)\n return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0\n}\n\nfunction isPrivateIPv4(ip: string): boolean {\n const num = ipv4ToNumber(ip)\n return PRIVATE_RANGES_V4.some(r => ((num & r.mask) >>> 0) === r.prefix)\n}\n\nfunction isPrivateIPv6(ip: string): boolean {\n const normalized = ip.toLowerCase()\n\n // Loopback ::1\n if (normalized === '::1') return true\n\n // Unspecified ::\n if (normalized === '::') return true\n\n // Link-local fe80::/10\n if (normalized.startsWith('fe8') || normalized.startsWith('fe9')\n || normalized.startsWith('fea') || normalized.startsWith('feb')) {\n return true\n }\n\n // Unique local fd00::/8\n if (normalized.startsWith('fd')) return true\n\n // IPv4-mapped ::ffff:x.x.x.x\n const v4mapped = normalized.match(/^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/)\n if (v4mapped) return isPrivateIPv4(v4mapped[1])\n\n return false\n}\n\nfunction isPrivateIP(ip: string): boolean {\n if (isIP(ip) === 4) return isPrivateIPv4(ip)\n if (isIP(ip) === 6) return isPrivateIPv6(ip)\n return false\n}\n\n/**\n * Check if a hostname resolves to a private or loopback IP.\n * If the hostname is already an IP literal, check directly.\n * Otherwise, resolve via DNS and check all results.\n */\nexport async function isPrivateOrLoopback(hostname: string): Promise<boolean> {\n // Direct IP literal\n if (isIP(hostname)) {\n return isPrivateIP(hostname)\n }\n\n // localhost shortcut\n if (hostname === 'localhost') return true\n\n // DNS resolution — check both A and AAAA records\n try {\n const [v4, v6] = await Promise.allSettled([\n resolve4(hostname),\n resolve6(hostname),\n ])\n const addrs: string[] = []\n if (v4.status === 'fulfilled') addrs.push(...v4.value)\n if (v6.status === 'fulfilled') addrs.push(...v6.value)\n\n if (addrs.length === 0) return true // no records — block to be safe\n return addrs.some(addr => isPrivateIP(addr))\n }\n catch {\n // DNS failure — block to be safe\n return true\n }\n}\n","import type { IncomingMessage } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { connect } from 'node:net'\nimport type { MultiAgentProxyConfig } from './types.js'\nimport { AuthError, verifyAgentAuth } from './auth.js'\nimport { isPrivateOrLoopback } from './ssrf.js'\nimport { writeAudit } from './audit.js'\n\n/**\n * Handle HTTP CONNECT requests for tunneling (used by HTTP_PROXY clients).\n * Flow: Auth check → SSRF check → TCP connect → bidirectional pipe.\n */\nexport async function handleConnect(\n config: MultiAgentProxyConfig,\n req: IncomingMessage,\n clientSocket: Socket,\n _head: Buffer,\n): Promise<void> {\n const target = req.url ?? ''\n const [host, portStr] = target.split(':')\n const port = Number.parseInt(portStr || '443')\n\n if (!host || !port) {\n clientSocket.write('HTTP/1.1 400 Bad Request\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n\n const mandatoryAuth = config.proxy.mandatory_auth ?? false\n\n // Auth check — CONNECT always requires auth in mandatory mode\n let agentEmail: string | undefined\n try {\n const authHeader = req.headers['proxy-authorization'] as string | undefined\n let identity: { email: string, act: 'agent' } | null = null\n\n for (const agentConf of config.agents) {\n identity = await verifyAgentAuth(\n authHeader ?? null,\n agentConf.idp_url,\n mandatoryAuth && config.agents.length === 1,\n )\n if (identity) break\n }\n\n if (mandatoryAuth && !identity) {\n throw new AuthError('JWT required')\n }\n\n agentEmail = identity?.email\n\n // Verify agent is known\n if (agentEmail) {\n const known = config.agents.find(a => a.email === agentEmail)\n if (!known) {\n clientSocket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n }\n else if (config.agents.length > 1) {\n throw new AuthError('JWT required for multi-agent proxy')\n }\n }\n catch (err) {\n if (err instanceof AuthError) {\n clientSocket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n throw err\n }\n\n // SSRF check\n if (await isPrivateOrLoopback(host)) {\n writeAudit({\n ts: new Date().toISOString(),\n agent: agentEmail ?? config.agents[0]?.email ?? 'unknown',\n action: 'deny',\n domain: host,\n method: 'CONNECT',\n path: target,\n rule: 'ssrf-blocked',\n })\n clientSocket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n\n // Connect to target\n const targetSocket = connect(port, host, () => {\n clientSocket.write('HTTP/1.1 200 Connection Established\\r\\n\\r\\n')\n\n // Bidirectional pipe\n targetSocket.pipe(clientSocket)\n clientSocket.pipe(targetSocket)\n })\n\n targetSocket.on('error', () => {\n clientSocket.write('HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n clientSocket.destroy()\n })\n\n clientSocket.on('error', () => {\n targetSocket.destroy()\n })\n\n // Cleanup on close\n clientSocket.on('close', () => targetSocket.destroy())\n targetSocket.on('close', () => clientSocket.destroy())\n}\n"],"mappings":"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","names":["parseTOML","config","import_node_fs","hostname","import_node_net","config","port","config","agentConf"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/proxy.ts","../src/matcher.ts","../src/auth.ts","../src/grants-client.ts","../src/audit.ts","../src/ssrf.ts","../src/connect.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createServer } from 'node:http'\nimport { parseArgs } from 'node:util'\nimport { loadMultiAgentConfig } from './config.js'\nimport { createNodeHandler } from './proxy.js'\n\nconst { values } = parseArgs({\n options: {\n config: { type: 'string', short: 'c', default: 'config.toml' },\n 'dry-run': { type: 'boolean', default: false },\n 'mandatory-auth': { type: 'boolean', default: false },\n },\n})\n\nconst configPath = values.config!\n\nconsole.log(`[openape-proxy] Loading config from ${configPath}`)\nconst config = loadMultiAgentConfig(configPath, {\n mandatoryAuth: values['mandatory-auth'] || undefined,\n})\n\nif (values['dry-run']) {\n console.log('[openape-proxy] DRY RUN mode — logging only, not blocking')\n console.log('[openape-proxy] Config loaded:')\n console.log(` Listen: ${config.proxy.listen}`)\n console.log(` Default action: ${config.proxy.default_action}`)\n console.log(` Mandatory auth: ${config.proxy.mandatory_auth ?? false}`)\n console.log(` Agents: ${config.agents.length}`)\n for (const agent of config.agents) {\n const allowCount = agent.allow?.length ?? 0\n const denyCount = agent.deny?.length ?? 0\n const grantCount = agent.grant_required?.length ?? 0\n console.log(` ${agent.email} (${agent.idp_url}) — ${allowCount} allow, ${denyCount} deny, ${grantCount} grant`)\n }\n process.exit(0)\n}\n\nconst handler = createNodeHandler(config)\n\nconst port = Number.parseInt(config.proxy.listen.split(':')[1] || '9090')\nconst hostname = config.proxy.listen.split(':')[0] || '127.0.0.1'\n\nconst server = createServer(handler.handleRequest)\nserver.on('connect', handler.handleConnect)\n\nserver.listen(port, hostname, () => {\n // When configured with port 0, the OS assigns a free port — use the actual\n // bound port for the log line so external orchestrators (apes proxy --) can\n // grep this line to discover where to connect.\n const addr = server.address()\n const actualPort = typeof addr === 'object' && addr ? addr.port : port\n console.log(`[openape-proxy] Listening on http://${hostname}:${actualPort}`)\n console.log(`[openape-proxy] CONNECT tunneling enabled`)\n console.log(`[openape-proxy] Mandatory auth: ${config.proxy.mandatory_auth ?? false}`)\n console.log(`[openape-proxy] Agents: ${config.agents.map(a => a.email).join(', ')}`)\n console.log(`[openape-proxy] Default action: ${config.proxy.default_action}`)\n})\n\n// Graceful shutdown\nprocess.on('SIGINT', () => {\n console.log('\\n[openape-proxy] Shutting down...')\n server.close()\n process.exit(0)\n})\n\nprocess.on('SIGTERM', () => {\n console.log('[openape-proxy] Shutting down...')\n server.close()\n process.exit(0)\n})\n","import { readFileSync } from 'node:fs'\nimport { parse as parseTOML } from 'smol-toml'\nimport type { AgentConfig, MultiAgentProxyConfig, ProxyConfig } from './types.js'\n\nexport function loadConfig(path: string): ProxyConfig {\n const raw = readFileSync(path, 'utf-8')\n\n let parsed: Record<string, unknown>\n if (path.endsWith('.json')) {\n parsed = JSON.parse(raw)\n }\n else {\n parsed = parseTOML(raw) as Record<string, unknown>\n }\n\n const proxy = parsed.proxy as ProxyConfig['proxy']\n if (!proxy?.listen || !proxy?.idp_url || !proxy?.agent_email) {\n throw new Error('Config must have [proxy] with listen, idp_url, and agent_email')\n }\n\n proxy.default_action ??= 'block'\n\n return {\n proxy,\n allow: (parsed.allow ?? []) as ProxyConfig['allow'],\n deny: (parsed.deny ?? []) as ProxyConfig['deny'],\n grant_required: (parsed.grant_required ?? []) as ProxyConfig['grant_required'],\n }\n}\n\n/**\n * Load config as multi-agent format.\n * If the config has an `agents` array, use it directly.\n * Otherwise, convert single-agent format to multi-agent for backward-compat.\n */\nexport function loadMultiAgentConfig(path: string, overrides?: { mandatoryAuth?: boolean }): MultiAgentProxyConfig {\n const raw = readFileSync(path, 'utf-8')\n\n let parsed: Record<string, unknown>\n if (path.endsWith('.json')) {\n parsed = JSON.parse(raw)\n }\n else {\n parsed = parseTOML(raw) as Record<string, unknown>\n }\n\n const proxy = parsed.proxy as Record<string, unknown>\n if (!proxy?.listen) {\n throw new Error('Config must have [proxy] with listen')\n }\n\n const baseProxy: MultiAgentProxyConfig['proxy'] = {\n listen: proxy.listen as string,\n default_action: (proxy.default_action as MultiAgentProxyConfig['proxy']['default_action']) ?? 'block',\n mandatory_auth: overrides?.mandatoryAuth ?? (proxy.mandatory_auth as boolean | undefined),\n }\n\n // Multi-agent format: has agents array\n if (Array.isArray(parsed.agents)) {\n return {\n proxy: baseProxy,\n agents: parsed.agents as AgentConfig[],\n }\n }\n\n // Single-agent format: convert to multi-agent\n const idpUrl = proxy.idp_url as string\n const agentEmail = proxy.agent_email as string\n if (!idpUrl || !agentEmail) {\n throw new Error('Single-agent config requires proxy.idp_url and proxy.agent_email')\n }\n\n return {\n proxy: baseProxy,\n agents: [{\n email: agentEmail,\n idp_url: idpUrl,\n allow: (parsed.allow ?? []) as AgentConfig['allow'],\n deny: (parsed.deny ?? []) as AgentConfig['deny'],\n grant_required: (parsed.grant_required ?? []) as AgentConfig['grant_required'],\n }],\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { createHash } from 'node:crypto'\nimport type { AgentConfig, AuditEntry, MultiAgentProxyConfig, ProxyConfig } from './types.js'\nimport { evaluateRules } from './matcher.js'\nimport { AuthError, verifyAgentAuth } from './auth.js'\nimport { GrantsClient } from './grants-client.js'\nimport { writeAudit } from './audit.js'\nimport { isPrivateOrLoopback } from './ssrf.js'\nimport { handleConnect } from './connect.js'\n\n/**\n * Compute a request hash that uniquely identifies the intent.\n * hash = sha256(METHOD + \" \" + FULL_URL + \"\\n\" + BODY)\n * This binds the grant to the exact request — no bait-and-switch.\n */\nasync function computeRequestHash(method: string, targetUrl: string, body: ArrayBuffer | null): Promise<string> {\n const hash = createHash('sha256')\n hash.update(`${method} ${targetUrl}\\n`)\n if (body && body.byteLength > 0) {\n hash.update(new Uint8Array(body))\n }\n return hash.digest('hex')\n}\n\n/** Legacy single-agent proxy */\nexport function createProxy(config: ProxyConfig) {\n const multiConfig: MultiAgentProxyConfig = {\n proxy: {\n listen: config.proxy.listen,\n default_action: config.proxy.default_action,\n mandatory_auth: config.proxy.mandatory_auth,\n },\n agents: [{\n email: config.proxy.agent_email,\n idp_url: config.proxy.idp_url,\n allow: config.allow,\n deny: config.deny,\n grant_required: config.grant_required,\n }],\n }\n return createMultiAgentProxy(multiConfig)\n}\n\n/**\n * Build the per-agent GrantsClient map used by both the HTTP forward-proxy\n * handler (in this file's `createMultiAgentProxy`) and the CONNECT handler\n * (in `connect.ts`). Exposed so `createNodeHandler` can share a single map\n * instead of letting each handler construct its own — keeps any future\n * per-agent token state coherent.\n */\nexport function buildGrantsClients(config: MultiAgentProxyConfig): Map<string, GrantsClient> {\n const grantsClients = new Map<string, GrantsClient>()\n for (const agent of config.agents) {\n grantsClients.set(agent.email, new GrantsClient(agent.idp_url))\n }\n return grantsClients\n}\n\n/** Multi-agent proxy with SSRF protection and mandatory auth */\nexport function createMultiAgentProxy(\n config: MultiAgentProxyConfig,\n grantsClients: Map<string, GrantsClient> = buildGrantsClients(config),\n) {\n // Backwards-compat: if caller didn't pre-build the map, we build our own.\n // createNodeHandler always passes one in so the CONNECT handler can share it.\n for (const agent of config.agents) {\n if (!grantsClients.has(agent.email)) {\n grantsClients.set(agent.email, new GrantsClient(agent.idp_url))\n }\n }\n\n const mandatoryAuth = config.proxy.mandatory_auth ?? false\n\n return {\n port: Number.parseInt(config.proxy.listen.split(':')[1] || '9090'),\n hostname: config.proxy.listen.split(':')[0] || '127.0.0.1',\n\n async fetch(req: Request): Promise<Response> {\n const url = new URL(req.url)\n const startTime = Date.now()\n\n // Health endpoint\n if (url.pathname === '/healthz') {\n return Response.json({\n status: 'ok',\n agents: config.agents.map(a => a.email),\n })\n }\n\n // Parse target URL from the path\n const targetUrl = url.pathname.slice(1) + url.search\n let targetParsed: URL\n try {\n targetParsed = new URL(targetUrl)\n }\n catch {\n return new Response(\n 'Invalid target URL. Send requests as: http://proxy:port/https://target.com/path',\n { status: 400 },\n )\n }\n\n const domain = targetParsed.hostname\n const method = req.method\n const path = targetParsed.pathname\n\n // SSRF protection — block private/loopback IPs before any rule evaluation\n if (await isPrivateOrLoopback(domain)) {\n return new Response('Blocked: private/loopback IP', { status: 403 })\n }\n\n // Read body once (needed for hash + forwarding)\n const bodyBuffer = req.body ? await req.arrayBuffer() : null\n\n // Verify agent identity — find IdP URL from first agent (for JWKS verification)\n // In multi-agent mode, we need the JWT to identify the agent first.\n // We try verification against each agent's IdP until one succeeds.\n let agentIdentity: { email: string, act: 'agent' } | null = null\n try {\n for (const agentConf of config.agents) {\n agentIdentity = await verifyAgentAuth(\n req.headers.get('proxy-authorization'),\n agentConf.idp_url,\n mandatoryAuth && config.agents.length === 1,\n )\n if (agentIdentity) break\n }\n\n // If mandatory auth and no identity found from any IdP\n if (mandatoryAuth && !agentIdentity) {\n throw new AuthError('JWT required')\n }\n }\n catch (err) {\n if (err instanceof AuthError) {\n return new Response(`Unauthorized: ${err.message}`, { status: 401 })\n }\n throw err\n }\n\n // Find the matching agent config\n const agentEmail = agentIdentity?.email\n let agentConf: AgentConfig | undefined\n\n if (agentEmail) {\n agentConf = config.agents.find(a => a.email === agentEmail)\n if (!agentConf) {\n return new Response(`Forbidden: unknown agent ${agentEmail}`, { status: 403 })\n }\n }\n else if (config.agents.length === 1) {\n // Non-mandatory auth, single agent: use the only agent config\n agentConf = config.agents[0]\n }\n else {\n return new Response('Unauthorized: JWT required for multi-agent proxy', { status: 401 })\n }\n\n const effectiveEmail = agentEmail ?? agentConf.email\n const grantsClient = grantsClients.get(agentConf.email)!\n\n // Build a ProxyConfig-shaped object for evaluateRules\n const rulesConfig: ProxyConfig = {\n proxy: {\n listen: config.proxy.listen,\n idp_url: agentConf.idp_url,\n agent_email: agentConf.email,\n default_action: config.proxy.default_action,\n },\n allow: agentConf.allow ?? [],\n deny: agentConf.deny ?? [],\n grant_required: agentConf.grant_required ?? [],\n }\n\n // Evaluate rules\n const action = evaluateRules(rulesConfig, domain, method, path)\n\n const baseAudit: Omit<AuditEntry, 'action' | 'rule'> = {\n ts: new Date().toISOString(),\n agent: effectiveEmail,\n domain,\n method,\n path,\n }\n\n // DENY\n if (action.type === 'deny') {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'deny-list', grant_id: null })\n return new Response(`Blocked: ${action.note || 'deny rule'}`, { status: 403 })\n }\n\n // ALLOW (no grant needed)\n if (action.type === 'allow') {\n writeAudit({ ...baseAudit, action: 'allow', rule: 'allow-list', grant_id: null })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n // GRANT REQUIRED\n const rule = action.rule\n const permissions = rule.permissions ?? [`${method.toLowerCase()}:${domain}`]\n\n // Compute request hash — binds grant to exact method + URL + body\n const requestHash = await computeRequestHash(method, targetUrl, bodyBuffer)\n\n // Check for existing grant\n const existing = await grantsClient.findExistingGrant(\n effectiveEmail,\n domain,\n 'proxy',\n permissions,\n ).catch(() => null)\n\n if (existing) {\n writeAudit({\n ...baseAudit,\n action: 'grant_approved',\n rule: 'standing-grant',\n grant_id: existing.id,\n request_hash: requestHash,\n })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n // No existing grant — behavior depends on default_action\n if (config.proxy.default_action === 'block') {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'no-grant (block mode)', grant_id: null })\n return new Response('No grant — blocked', { status: 403 })\n }\n\n if (config.proxy.default_action === 'request-async') {\n // Create grant request, return 407 immediately\n const grant = await grantsClient.requestGrant({\n requester: effectiveEmail,\n targetHost: domain,\n audience: 'ape-proxy',\n grantType: rule.grant_type,\n permissions,\n reason: `${method} ${targetUrl}`,\n requestHash,\n duration: rule.duration,\n }).catch(() => null)\n\n writeAudit({\n ...baseAudit,\n action: 'grant_denied',\n rule: 'grant_required (async)',\n grant_id: grant?.id ?? null,\n })\n\n return new Response(\n JSON.stringify({\n error: 'Grant required',\n grant_id: grant?.id,\n message: 'Grant request created. Retry after approval.',\n }),\n { status: 407, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n // BLOCKING mode: create grant request and wait\n console.error(`[proxy] Requesting grant for ${method} ${domain}${path} — waiting for approval...`)\n\n try {\n const grant = await grantsClient.requestGrant({\n requester: effectiveEmail,\n targetHost: domain,\n audience: 'ape-proxy',\n grantType: rule.grant_type,\n permissions,\n reason: `${method} ${targetUrl}`,\n requestHash,\n duration: rule.duration,\n })\n\n const approved = await grantsClient.waitForApproval(grant.id)\n\n const waitedMs = Date.now() - startTime\n\n if (approved.status === 'approved') {\n writeAudit({\n ...baseAudit,\n action: 'grant_approved',\n rule: 'grant_required',\n grant_id: approved.id,\n request_hash: requestHash,\n waited_ms: waitedMs,\n })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n writeAudit({\n ...baseAudit,\n action: 'grant_denied',\n rule: 'grant_required',\n grant_id: approved.id,\n waited_ms: waitedMs,\n })\n return new Response(`Grant denied by ${approved.decided_by}`, { status: 403 })\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : 'Unknown error'\n writeAudit({\n ...baseAudit,\n action: 'grant_timeout',\n rule: 'grant_required',\n error: msg,\n })\n return new Response(`Grant request failed: ${msg}`, { status: 504 })\n }\n },\n }\n}\n\n/**\n * Create a node:http compatible handler for use with http.createServer().\n * Returns both a request handler and a CONNECT handler.\n */\nexport function createNodeHandler(config: MultiAgentProxyConfig): {\n handleRequest: (req: IncomingMessage, res: ServerResponse) => void\n handleConnect: (req: IncomingMessage, socket: Socket, head: Buffer) => void\n} {\n // Build grantsClients once; share with both forward-proxy fetch path\n // (createMultiAgentProxy) and the CONNECT path (handleConnect). Without\n // sharing, CONNECT-time grant_required rules couldn't reach the IdP in\n // multi-agent mode without duplicating constructor work.\n const grantsClients = buildGrantsClients(config)\n const proxy = createMultiAgentProxy(config, grantsClients)\n\n return {\n handleRequest(req: IncomingMessage, res: ServerResponse) {\n // Standard HTTP_PROXY clients (curl, gh, git, npm) send the target as\n // an absolute URL in the request line for cleartext forward-proxying:\n // `GET http://example.com/path HTTP/1.1`\n // node:http surfaces that absolute URL via `req.url`. The legacy\n // `proxy.fetch` extraction expects a path-encoded form\n // (`/<full-target-url>`), so we adapt here: when `req.url` is\n // absolute, prefix it with a slash so `pathname.slice(1)` recovers\n // the same target string. Path-form clients (legacy) keep working.\n const reqUrl = req.url || '/'\n const isAbsolute = reqUrl.startsWith('http://') || reqUrl.startsWith('https://')\n const proxyHost = req.headers.host || 'localhost'\n const url = isAbsolute\n ? `http://${proxyHost}/${reqUrl}`\n : `http://${proxyHost}${reqUrl}`\n const chunks: Buffer[] = []\n\n req.on('data', (chunk: Buffer) => chunks.push(chunk))\n req.on('end', async () => {\n try {\n const body = chunks.length > 0 ? Buffer.concat(chunks) : undefined\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n headers.set(key, Array.isArray(value) ? value.join(', ') : value)\n }\n }\n\n const request = new Request(url, {\n method: req.method,\n headers,\n body: body && req.method !== 'GET' && req.method !== 'HEAD' ? body : undefined,\n duplex: 'half',\n } as RequestInit)\n\n const response = await proxy.fetch(request)\n\n res.writeHead(response.status, response.statusText, Object.fromEntries(response.headers))\n if (response.body) {\n const reader = response.body.getReader()\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read()\n if (done) {\n res.end()\n return\n }\n res.write(value)\n return pump()\n }\n await pump()\n }\n else {\n res.end()\n }\n }\n catch {\n res.writeHead(502)\n res.end('Proxy error')\n }\n })\n },\n\n handleConnect(req: IncomingMessage, socket: Socket, head: Buffer) {\n handleConnect(config, grantsClients, req, socket, head)\n },\n }\n}\n\n/**\n * Forward a request to the target URL.\n * Strips proxy-specific headers, preserves the rest.\n */\nasync function forwardRequest(originalReq: Request, targetUrl: string, cachedBody?: ArrayBuffer | null): Promise<Response> {\n const headers = new Headers(originalReq.headers)\n // Remove proxy-specific headers\n headers.delete('proxy-authorization')\n headers.delete('proxy-connection')\n // Don't send host of the proxy\n headers.delete('host')\n\n const body = cachedBody && cachedBody.byteLength > 0 ? cachedBody : null\n\n try {\n const res = await fetch(targetUrl, {\n method: originalReq.method,\n headers,\n body,\n duplex: 'half',\n redirect: 'manual',\n })\n\n // Stream the response back\n const responseHeaders = new Headers(res.headers)\n // Remove hop-by-hop headers\n responseHeaders.delete('transfer-encoding')\n responseHeaders.delete('connection')\n\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers: responseHeaders,\n })\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : 'Upstream error'\n return new Response(`Proxy error: ${msg}`, { status: 502 })\n }\n}\n","import type { ProxyConfig, RuleAction, RuleEntry } from './types.js'\n\n/**\n * Match a glob pattern against a string.\n * Supports * (any segment) and ** (any number of segments).\n */\nfunction globMatch(pattern: string, value: string): boolean {\n // Simple glob: convert * to regex\n const regex = new RegExp(\n `^${\n pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex chars except *\n .replace(/\\*\\*/g, '<<<DOUBLESTAR>>>')\n .replace(/\\*/g, '[^/]*')\n .replace(/<<<DOUBLESTAR>>>/g, '.*')\n }$`,\n )\n return regex.test(value)\n}\n\nfunction matchesRule(rule: RuleEntry, domain: string, method: string, path: string): boolean {\n // Domain match (supports wildcards like *.github.com)\n if (!globMatch(rule.domain, domain)) return false\n\n // Method match (if specified)\n if (rule.methods && rule.methods.length > 0) {\n if (!rule.methods.includes(method.toUpperCase())) return false\n }\n\n // Path match (if specified)\n if (rule.path) {\n if (!globMatch(rule.path, path)) return false\n }\n\n return true\n}\n\n/**\n * Evaluate rules in order: deny → allow → grant_required → default_action\n */\nexport function evaluateRules(\n config: ProxyConfig,\n domain: string,\n method: string,\n path: string,\n): RuleAction {\n // 1. Check deny list first\n for (const rule of config.deny) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'deny', note: rule.note }\n }\n }\n\n // 2. Check allow list\n for (const rule of config.allow) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'allow' }\n }\n }\n\n // 3. Check grant_required rules (most specific first)\n for (const rule of config.grant_required) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'grant_required', rule }\n }\n }\n\n // 4. Default action for unmatched hosts. Conceptually the proxy-side\n // counterpart of the IdP's per-agent YOLO policy — both decide what\n // happens when no specific rule matches, both live server-side (proxy or\n // IdP), neither leaks the decision to the agent. Differences:\n // - IdP YOLO is per-agent, evaluated at grant time, can have deny-patterns\n // and an expiry window.\n // - Proxy default_action is per-proxy-instance, evaluated at request time,\n // binary outcome (allow/deny/request-grant).\n //\n // Modes:\n // - 'block': hard deny — paranoid agent profile.\n // - 'allow': hard pass — transparent-audit profile (log every call,\n // enforce nothing). Equivalent role to a YOLO policy without a\n // deny-list.\n // - 'request' / 'request-async' (OpenApe-default): treat as\n // grant_required with a once-grant catch-all so every new host\n // surfaces an interactive grant decision.\n if (config.proxy.default_action === 'block') {\n return { type: 'deny', note: 'No matching rule (default: block)' }\n }\n if (config.proxy.default_action === 'allow') {\n return { type: 'allow' }\n }\n\n return {\n type: 'grant_required',\n rule: {\n domain: '*',\n grant_type: 'once',\n },\n }\n}\n","import { verifyJWT, createRemoteJWKS } from '@openape/core'\n\nexport interface AgentIdentity {\n email: string\n act: 'agent'\n}\n\nexport class AuthError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'AuthError'\n }\n}\n\n/**\n * Verify agent JWT from Proxy-Authorization header.\n * Returns the agent identity or null if invalid/missing.\n * When mandatory is true, throws AuthError if no valid JWT is provided.\n */\nexport async function verifyAgentAuth(\n authHeader: string | null,\n idpUrl: string,\n mandatory: boolean = false,\n): Promise<AgentIdentity | null> {\n if (!authHeader) {\n if (mandatory) throw new AuthError('JWT required')\n return null\n }\n\n const match = authHeader.match(/^Bearer (.+)$/i)\n if (!match) {\n if (mandatory) throw new AuthError('Invalid authorization header')\n return null\n }\n\n const token = match[1]\n\n try {\n const jwks = createRemoteJWKS(`${idpUrl}/.well-known/jwks.json`)\n const { payload } = await verifyJWT(token, jwks, { issuer: idpUrl })\n\n if (payload.act !== 'agent' || !payload.sub) {\n if (mandatory) throw new AuthError('Invalid agent token')\n return null\n }\n\n return {\n email: payload.sub as string,\n act: 'agent',\n }\n }\n catch (err) {\n if (err instanceof AuthError) throw err\n if (mandatory) throw new AuthError('JWT verification failed')\n return null\n }\n}\n","import type { OpenApeGrant, GrantType } from '@openape/core'\n\n/**\n * Client for the IdP's grant management API.\n * Creates grant requests and polls for approval.\n */\nexport class GrantsClient {\n private idpUrl: string\n private agentToken: string | undefined\n\n constructor(idpUrl: string) {\n this.idpUrl = idpUrl.replace(/\\/$/, '')\n }\n\n setAgentToken(token: string): void {\n this.agentToken = token\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' }\n if (this.agentToken) {\n h.Authorization = `Bearer ${this.agentToken}`\n }\n return h\n }\n\n /**\n * Create a grant request on the IdP.\n */\n async requestGrant(opts: {\n requester: string\n targetHost: string\n audience: string\n grantType: GrantType\n permissions?: string[]\n reason?: string\n requestHash?: string\n duration?: number\n }): Promise<OpenApeGrant> {\n const res = await fetch(`${this.idpUrl}/api/grants`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify({\n requester: opts.requester,\n target_host: opts.targetHost,\n audience: opts.audience,\n grant_type: opts.grantType,\n permissions: opts.permissions,\n reason: opts.reason,\n request_hash: opts.requestHash,\n duration: opts.duration,\n }),\n })\n\n if (!res.ok) {\n throw new Error(`Grant request failed: ${res.status} ${await res.text()}`)\n }\n\n return res.json() as Promise<OpenApeGrant>\n }\n\n /**\n * Poll a grant until it's approved, denied, or timeout.\n */\n async waitForApproval(\n grantId: string,\n timeoutMs: number = 300_000,\n pollIntervalMs: number = 2_000,\n ): Promise<OpenApeGrant> {\n const deadline = Date.now() + timeoutMs\n\n while (Date.now() < deadline) {\n const res = await fetch(`${this.idpUrl}/api/grants/${grantId}`, {\n headers: this.headers(),\n })\n\n if (!res.ok) {\n throw new Error(`Grant poll failed: ${res.status}`)\n }\n\n const grant = await res.json() as OpenApeGrant\n if (grant.status !== 'pending') {\n return grant\n }\n\n await new Promise(r => setTimeout(r, pollIntervalMs))\n }\n\n throw new Error(`Grant approval timed out after ${timeoutMs}ms`)\n }\n\n /**\n * Check if there's an existing approved grant for a host+audience+permissions combo.\n * Ignores `once` grants (they're single-use).\n */\n async findExistingGrant(\n requester: string,\n targetHost: string,\n audience: string,\n permissions?: string[],\n ): Promise<OpenApeGrant | null> {\n const params = new URLSearchParams({\n requester,\n status: 'approved',\n })\n\n const res = await fetch(`${this.idpUrl}/api/grants?${params}`, {\n headers: this.headers(),\n })\n\n if (!res.ok) return null\n\n const grants = await res.json() as OpenApeGrant[]\n const now = Math.floor(Date.now() / 1000)\n\n return grants.find((g) => {\n if (g.status !== 'approved') return false\n if (g.expires_at && g.expires_at <= now) return false\n if (g.request?.grant_type === 'once') return false\n if (g.request?.target_host !== targetHost) return false\n if (g.request?.audience !== audience) return false\n if (permissions?.length && g.request?.permissions?.length) {\n const grantedPerms = new Set(g.request.permissions)\n if (!permissions.every(p => grantedPerms.has(p))) return false\n }\n return true\n }) ?? null\n }\n}\n","import type { AuditEntry } from './types.js'\n\n/**\n * Format the request target for the stderr summary line. For HTTP forward-proxy\n * the path starts with `/` (so `${domain}${path}` reads `example.com/foo`), but\n * for CONNECT the path field already carries `host:port` and concatenating\n * with `domain` would print `example.comexample.com:443`.\n */\nfunction formatTarget(entry: AuditEntry): string {\n return entry.path.startsWith('/') ? `${entry.domain}${entry.path}` : entry.path\n}\n\n/**\n * Emit one operator-readable audit summary line to stderr. Intentionally NOT a\n * tamper-proof audit trail: anything written on the user's machine is also\n * writable by the user, so there's no integrity story we'd be willing to put\n * in front of a reviewer. The trustworthy audit lives server-side, recorded\n * by the IdP every time it processes a grant request — see the planned\n * per-agent audit route on `id.openape.ai`.\n *\n * Stderr here is purely a debugging convenience for the operator running\n * `apes proxy --` interactively. Persist nothing locally.\n */\nexport function writeAudit(entry: AuditEntry): void {\n const grantSuffix = entry.grant_id ? ` grant=${entry.grant_id}` : ''\n console.error(`[audit] ${entry.action} ${entry.method} ${formatTarget(entry)}${grantSuffix}`)\n}\n","import { resolve4, resolve6 } from 'node:dns/promises'\nimport { isIP } from 'node:net'\n\nconst PRIVATE_RANGES_V4 = [\n { prefix: 0x7F000000, mask: 0xFF000000 }, // 127.0.0.0/8\n { prefix: 0x0A000000, mask: 0xFF000000 }, // 10.0.0.0/8\n { prefix: 0xAC100000, mask: 0xFFF00000 }, // 172.16.0.0/12\n { prefix: 0xC0A80000, mask: 0xFFFF0000 }, // 192.168.0.0/16\n { prefix: 0xA9FE0000, mask: 0xFFFF0000 }, // 169.254.0.0/16\n { prefix: 0x00000000, mask: 0xFF000000 }, // 0.0.0.0/8\n]\n\nfunction ipv4ToNumber(ip: string): number {\n const parts = ip.split('.').map(Number)\n return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0\n}\n\nfunction isPrivateIPv4(ip: string): boolean {\n const num = ipv4ToNumber(ip)\n return PRIVATE_RANGES_V4.some(r => ((num & r.mask) >>> 0) === r.prefix)\n}\n\nfunction isPrivateIPv6(ip: string): boolean {\n const normalized = ip.toLowerCase()\n\n // Loopback ::1\n if (normalized === '::1') return true\n\n // Unspecified ::\n if (normalized === '::') return true\n\n // Link-local fe80::/10\n if (normalized.startsWith('fe8') || normalized.startsWith('fe9')\n || normalized.startsWith('fea') || normalized.startsWith('feb')) {\n return true\n }\n\n // Unique local fd00::/8\n if (normalized.startsWith('fd')) return true\n\n // IPv4-mapped ::ffff:x.x.x.x\n const v4mapped = normalized.match(/^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/)\n if (v4mapped) return isPrivateIPv4(v4mapped[1])\n\n return false\n}\n\nfunction isPrivateIP(ip: string): boolean {\n if (isIP(ip) === 4) return isPrivateIPv4(ip)\n if (isIP(ip) === 6) return isPrivateIPv6(ip)\n return false\n}\n\n/**\n * Check if a hostname resolves to a private or loopback IP.\n * If the hostname is already an IP literal, check directly.\n * Otherwise, resolve via DNS and check all results.\n */\nexport async function isPrivateOrLoopback(hostname: string): Promise<boolean> {\n // Direct IP literal\n if (isIP(hostname)) {\n return isPrivateIP(hostname)\n }\n\n // localhost shortcut\n if (hostname === 'localhost') return true\n\n // DNS resolution — check both A and AAAA records\n try {\n const [v4, v6] = await Promise.allSettled([\n resolve4(hostname),\n resolve6(hostname),\n ])\n const addrs: string[] = []\n if (v4.status === 'fulfilled') addrs.push(...v4.value)\n if (v6.status === 'fulfilled') addrs.push(...v6.value)\n\n if (addrs.length === 0) return true // no records — block to be safe\n return addrs.some(addr => isPrivateIP(addr))\n }\n catch {\n // DNS failure — block to be safe\n return true\n }\n}\n","import type { IncomingMessage } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { connect } from 'node:net'\nimport type { AgentConfig, MultiAgentProxyConfig, ProxyConfig } from './types.js'\nimport { AuthError, verifyAgentAuth } from './auth.js'\nimport { isPrivateOrLoopback } from './ssrf.js'\nimport { writeAudit } from './audit.js'\nimport { evaluateRules } from './matcher.js'\nimport type { GrantsClient } from './grants-client.js'\n\n/**\n * Handle HTTP CONNECT requests for tunneling (used by HTTP_PROXY clients).\n * Flow: Auth → SSRF → host-based rule evaluation (allow / deny / grant_required)\n * → TCP connect → bidirectional pipe.\n *\n * For HTTPS via CONNECT we only see the hostname (the TLS payload is opaque).\n * Rules with `methods` or `path` filters cannot be enforced at CONNECT time and\n * are skipped — they'd only match if the client also carried the same hostname\n * over cleartext-HTTP forward-proxy. This is intentional: there's no honest way\n * to gate a method we can't observe.\n */\nexport async function handleConnect(\n config: MultiAgentProxyConfig,\n grantsClients: Map<string, GrantsClient>,\n req: IncomingMessage,\n clientSocket: Socket,\n _head: Buffer,\n): Promise<void> {\n const target = req.url ?? ''\n const [host, portStr] = target.split(':')\n const port = Number.parseInt(portStr || '443')\n\n if (!host || !port) {\n clientSocket.write('HTTP/1.1 400 Bad Request\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n\n // SYSTEM BYPASS: outbound to any configured IdP host is unconditionally\n // allowed. The proxy itself talks to the IdP for JWT verification + grant\n // approval, and any process inside the proxy boundary may need to call the\n // IdP for `apes login` / `apes whoami` / token-exchange BEFORE it can ever\n // produce a valid Proxy-Authorization JWT. Blocking IdP traffic would\n // either deadlock the grant flow or lock users out of authentication.\n // We therefore skip auth (no JWT yet for first-login), SSRF (local-dev IdPs\n // can live at 127.0.0.1), and policy rules (operator must not be able to\n // override this system invariant).\n const idpHosts = new Set(\n config.agents\n .map((a) => {\n try {\n return new URL(a.idp_url).hostname.toLowerCase()\n }\n catch {\n return ''\n }\n })\n .filter(h => h.length > 0),\n )\n if (idpHosts.has(host.toLowerCase())) {\n writeAudit({\n ts: new Date().toISOString(),\n agent: 'system',\n action: 'allow',\n domain: host,\n method: 'CONNECT',\n path: target,\n rule: 'idp-system-bypass',\n })\n tunnel(host, port, clientSocket)\n return\n }\n\n const mandatoryAuth = config.proxy.mandatory_auth ?? false\n\n // Auth check — CONNECT always requires auth in mandatory mode\n let agentEmail: string | undefined\n try {\n const authHeader = req.headers['proxy-authorization'] as string | undefined\n let identity: { email: string, act: 'agent' } | null = null\n\n for (const agentConf of config.agents) {\n identity = await verifyAgentAuth(\n authHeader ?? null,\n agentConf.idp_url,\n mandatoryAuth && config.agents.length === 1,\n )\n if (identity) break\n }\n\n if (mandatoryAuth && !identity) {\n throw new AuthError('JWT required')\n }\n\n agentEmail = identity?.email\n\n // Verify agent is known\n if (agentEmail) {\n const known = config.agents.find(a => a.email === agentEmail)\n if (!known) {\n clientSocket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n }\n else if (config.agents.length > 1) {\n throw new AuthError('JWT required for multi-agent proxy')\n }\n }\n catch (err) {\n if (err instanceof AuthError) {\n clientSocket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n throw err\n }\n\n // SSRF check\n if (await isPrivateOrLoopback(host)) {\n writeAudit({\n ts: new Date().toISOString(),\n agent: agentEmail ?? config.agents[0]?.email ?? 'unknown',\n action: 'deny',\n domain: host,\n method: 'CONNECT',\n path: target,\n rule: 'ssrf-blocked',\n })\n clientSocket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n\n // Host-based rule evaluation. Pick the agent's config: in single-agent or\n // unauth mode this is just config.agents[0]; in multi-agent mode we use the\n // identity established above.\n const agentConf: AgentConfig | undefined = agentEmail\n ? config.agents.find(a => a.email === agentEmail)\n : config.agents[0]\n if (!agentConf) {\n clientSocket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n const effectiveEmail = agentEmail ?? agentConf.email\n\n const rulesConfig: ProxyConfig = {\n proxy: {\n listen: config.proxy.listen,\n idp_url: agentConf.idp_url,\n agent_email: agentConf.email,\n default_action: config.proxy.default_action,\n },\n allow: agentConf.allow ?? [],\n deny: agentConf.deny ?? [],\n grant_required: agentConf.grant_required ?? [],\n }\n\n // CONNECT carries no method/path beyond host:port. Pass 'CONNECT' as method\n // and '/' as path so rules with method-filters (which we can't enforce here)\n // simply don't match — operator must specify method-less rules to gate\n // HTTPS hosts.\n const action = evaluateRules(rulesConfig, host, 'CONNECT', '/')\n const baseAudit = {\n ts: new Date().toISOString(),\n agent: effectiveEmail,\n domain: host,\n method: 'CONNECT',\n path: target,\n } as const\n\n if (action.type === 'deny') {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'deny-list' })\n const note = action.note ? ` ${action.note}` : ''\n clientSocket.write(`HTTP/1.1 403 Forbidden\\r\\nContent-Type: text/plain\\r\\n\\r\\nBlocked:${note}\\r\\n`)\n clientSocket.destroy()\n return\n }\n\n if (action.type === 'grant_required') {\n const grantsClient = grantsClients.get(agentConf.email)\n if (!grantsClient) {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'grant_required (no client)' })\n clientSocket.write('HTTP/1.1 500 Internal Server Error\\r\\n\\r\\nNo grants client for agent\\r\\n')\n clientSocket.destroy()\n return\n }\n\n // Async-mode (`request-async`) is meaningful for HTTP forward-proxy where\n // the client can re-issue the request after approval. CONNECT clients\n // (curl, gh, …) won't transparently retry on 407 mid-handshake, so we\n // always block the tunnel until the grant resolves. Operator can shorten\n // the wait via per-rule `duration` or via the IdP's grant TTL.\n const startTs = Date.now()\n try {\n const grant = await grantsClient.requestGrant({\n requester: effectiveEmail,\n targetHost: host,\n audience: 'ape-proxy',\n grantType: action.rule.grant_type,\n permissions: action.rule.permissions,\n reason: `CONNECT ${host}:${port}`,\n duration: action.rule.duration,\n })\n const decided = await grantsClient.waitForApproval(grant.id)\n const waitedMs = Date.now() - startTs\n if (decided.status === 'approved') {\n writeAudit({ ...baseAudit, action: 'grant_approved', rule: 'grant_required', grant_id: decided.id, waited_ms: waitedMs })\n // Fall through to the TCP connect below.\n }\n else {\n writeAudit({ ...baseAudit, action: 'grant_denied', rule: 'grant_required', grant_id: decided.id, waited_ms: waitedMs })\n clientSocket.write(`HTTP/1.1 403 Forbidden\\r\\n\\r\\nGrant denied by ${decided.decided_by ?? 'policy'}\\r\\n`)\n clientSocket.destroy()\n return\n }\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : 'Unknown error'\n writeAudit({ ...baseAudit, action: 'grant_timeout', rule: 'grant_required', error: msg })\n clientSocket.write(`HTTP/1.1 504 Gateway Timeout\\r\\n\\r\\nGrant request failed: ${msg}\\r\\n`)\n clientSocket.destroy()\n return\n }\n }\n else {\n // 'allow' — log and continue.\n writeAudit({ ...baseAudit, action: 'allow', rule: 'allow-list' })\n }\n\n tunnel(host, port, clientSocket)\n}\n\n/**\n * Open a TCP socket to host:port and bidirectionally pipe it with the client\n * socket. Used by both the policy-pass path (auth + rules approved) and the\n * IdP system-bypass path (no auth/policy involved).\n */\nfunction tunnel(host: string, port: number, clientSocket: Socket): void {\n const targetSocket = connect(port, host, () => {\n clientSocket.write('HTTP/1.1 200 Connection Established\\r\\n\\r\\n')\n targetSocket.pipe(clientSocket)\n clientSocket.pipe(targetSocket)\n })\n\n targetSocket.on('error', () => {\n clientSocket.write('HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n clientSocket.destroy()\n })\n\n clientSocket.on('error', () => {\n targetSocket.destroy()\n })\n\n clientSocket.on('close', () => targetSocket.destroy())\n targetSocket.on('close', () => clientSocket.destroy())\n}\n"],"mappings":"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","names":["parseTOML","config","hostname","import_node_net","config","port","agentConf","config","agentConf"]}
package/dist/index.js CHANGED
@@ -22,7 +22,6 @@ function loadMultiAgentConfig(path, overrides) {
22
22
  const baseProxy = {
23
23
  listen: proxy.listen,
24
24
  default_action: proxy.default_action ?? "block",
25
- audit_log: proxy.audit_log,
26
25
  mandatory_auth: overrides?.mandatoryAuth ?? proxy.mandatory_auth
27
26
  };
28
27
  if (Array.isArray(parsed.agents)) {
@@ -87,6 +86,9 @@ function evaluateRules(config2, domain, method, path) {
87
86
  if (config2.proxy.default_action === "block") {
88
87
  return { type: "deny", note: "No matching rule (default: block)" };
89
88
  }
89
+ if (config2.proxy.default_action === "allow") {
90
+ return { type: "allow" };
91
+ }
90
92
  return {
91
93
  type: "grant_required",
92
94
  rule: {
@@ -224,18 +226,12 @@ var GrantsClient = class {
224
226
  };
225
227
 
226
228
  // src/audit.ts
227
- import { appendFileSync } from "fs";
228
- var auditPath;
229
- function initAudit(path) {
230
- auditPath = path;
229
+ function formatTarget(entry) {
230
+ return entry.path.startsWith("/") ? `${entry.domain}${entry.path}` : entry.path;
231
231
  }
232
232
  function writeAudit(entry) {
233
- const line = JSON.stringify(entry);
234
- console.error(`[audit] ${entry.action} ${entry.method} ${entry.domain}${entry.path}${entry.grant_id ? ` grant=${entry.grant_id}` : ""}`);
235
- if (auditPath) {
236
- appendFileSync(auditPath, `${line}
237
- `);
238
- }
233
+ const grantSuffix = entry.grant_id ? ` grant=${entry.grant_id}` : "";
234
+ console.error(`[audit] ${entry.action} ${entry.method} ${formatTarget(entry)}${grantSuffix}`);
239
235
  }
240
236
 
241
237
  // src/ssrf.ts
@@ -302,7 +298,7 @@ async function isPrivateOrLoopback(hostname2) {
302
298
 
303
299
  // src/connect.ts
304
300
  import { connect } from "net";
305
- async function handleConnect(config2, req, clientSocket, _head) {
301
+ async function handleConnect(config2, grantsClients, req, clientSocket, _head) {
306
302
  const target = req.url ?? "";
307
303
  const [host, portStr] = target.split(":");
308
304
  const port2 = Number.parseInt(portStr || "443");
@@ -311,15 +307,37 @@ async function handleConnect(config2, req, clientSocket, _head) {
311
307
  clientSocket.destroy();
312
308
  return;
313
309
  }
310
+ const idpHosts = new Set(
311
+ config2.agents.map((a) => {
312
+ try {
313
+ return new URL(a.idp_url).hostname.toLowerCase();
314
+ } catch {
315
+ return "";
316
+ }
317
+ }).filter((h) => h.length > 0)
318
+ );
319
+ if (idpHosts.has(host.toLowerCase())) {
320
+ writeAudit({
321
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
322
+ agent: "system",
323
+ action: "allow",
324
+ domain: host,
325
+ method: "CONNECT",
326
+ path: target,
327
+ rule: "idp-system-bypass"
328
+ });
329
+ tunnel(host, port2, clientSocket);
330
+ return;
331
+ }
314
332
  const mandatoryAuth = config2.proxy.mandatory_auth ?? false;
315
333
  let agentEmail;
316
334
  try {
317
335
  const authHeader = req.headers["proxy-authorization"];
318
336
  let identity = null;
319
- for (const agentConf of config2.agents) {
337
+ for (const agentConf2 of config2.agents) {
320
338
  identity = await verifyAgentAuth(
321
339
  authHeader ?? null,
322
- agentConf.idp_url,
340
+ agentConf2.idp_url,
323
341
  mandatoryAuth && config2.agents.length === 1
324
342
  );
325
343
  if (identity) break;
@@ -360,6 +378,91 @@ async function handleConnect(config2, req, clientSocket, _head) {
360
378
  clientSocket.destroy();
361
379
  return;
362
380
  }
381
+ const agentConf = agentEmail ? config2.agents.find((a) => a.email === agentEmail) : config2.agents[0];
382
+ if (!agentConf) {
383
+ clientSocket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
384
+ clientSocket.destroy();
385
+ return;
386
+ }
387
+ const effectiveEmail = agentEmail ?? agentConf.email;
388
+ const rulesConfig = {
389
+ proxy: {
390
+ listen: config2.proxy.listen,
391
+ idp_url: agentConf.idp_url,
392
+ agent_email: agentConf.email,
393
+ default_action: config2.proxy.default_action
394
+ },
395
+ allow: agentConf.allow ?? [],
396
+ deny: agentConf.deny ?? [],
397
+ grant_required: agentConf.grant_required ?? []
398
+ };
399
+ const action = evaluateRules(rulesConfig, host, "CONNECT", "/");
400
+ const baseAudit = {
401
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
402
+ agent: effectiveEmail,
403
+ domain: host,
404
+ method: "CONNECT",
405
+ path: target
406
+ };
407
+ if (action.type === "deny") {
408
+ writeAudit({ ...baseAudit, action: "deny", rule: "deny-list" });
409
+ const note = action.note ? ` ${action.note}` : "";
410
+ clientSocket.write(`HTTP/1.1 403 Forbidden\r
411
+ Content-Type: text/plain\r
412
+ \r
413
+ Blocked:${note}\r
414
+ `);
415
+ clientSocket.destroy();
416
+ return;
417
+ }
418
+ if (action.type === "grant_required") {
419
+ const grantsClient = grantsClients.get(agentConf.email);
420
+ if (!grantsClient) {
421
+ writeAudit({ ...baseAudit, action: "deny", rule: "grant_required (no client)" });
422
+ clientSocket.write("HTTP/1.1 500 Internal Server Error\r\n\r\nNo grants client for agent\r\n");
423
+ clientSocket.destroy();
424
+ return;
425
+ }
426
+ const startTs = Date.now();
427
+ try {
428
+ const grant = await grantsClient.requestGrant({
429
+ requester: effectiveEmail,
430
+ targetHost: host,
431
+ audience: "ape-proxy",
432
+ grantType: action.rule.grant_type,
433
+ permissions: action.rule.permissions,
434
+ reason: `CONNECT ${host}:${port2}`,
435
+ duration: action.rule.duration
436
+ });
437
+ const decided = await grantsClient.waitForApproval(grant.id);
438
+ const waitedMs = Date.now() - startTs;
439
+ if (decided.status === "approved") {
440
+ writeAudit({ ...baseAudit, action: "grant_approved", rule: "grant_required", grant_id: decided.id, waited_ms: waitedMs });
441
+ } else {
442
+ writeAudit({ ...baseAudit, action: "grant_denied", rule: "grant_required", grant_id: decided.id, waited_ms: waitedMs });
443
+ clientSocket.write(`HTTP/1.1 403 Forbidden\r
444
+ \r
445
+ Grant denied by ${decided.decided_by ?? "policy"}\r
446
+ `);
447
+ clientSocket.destroy();
448
+ return;
449
+ }
450
+ } catch (err) {
451
+ const msg = err instanceof Error ? err.message : "Unknown error";
452
+ writeAudit({ ...baseAudit, action: "grant_timeout", rule: "grant_required", error: msg });
453
+ clientSocket.write(`HTTP/1.1 504 Gateway Timeout\r
454
+ \r
455
+ Grant request failed: ${msg}\r
456
+ `);
457
+ clientSocket.destroy();
458
+ return;
459
+ }
460
+ } else {
461
+ writeAudit({ ...baseAudit, action: "allow", rule: "allow-list" });
462
+ }
463
+ tunnel(host, port2, clientSocket);
464
+ }
465
+ function tunnel(host, port2, clientSocket) {
363
466
  const targetSocket = connect(port2, host, () => {
364
467
  clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
365
468
  targetSocket.pipe(clientSocket);
@@ -386,11 +489,19 @@ async function computeRequestHash(method, targetUrl, body) {
386
489
  }
387
490
  return hash.digest("hex");
388
491
  }
389
- function createMultiAgentProxy(config2) {
492
+ function buildGrantsClients(config2) {
390
493
  const grantsClients = /* @__PURE__ */ new Map();
391
494
  for (const agent of config2.agents) {
392
495
  grantsClients.set(agent.email, new GrantsClient(agent.idp_url));
393
496
  }
497
+ return grantsClients;
498
+ }
499
+ function createMultiAgentProxy(config2, grantsClients = buildGrantsClients(config2)) {
500
+ for (const agent of config2.agents) {
501
+ if (!grantsClients.has(agent.email)) {
502
+ grantsClients.set(agent.email, new GrantsClient(agent.idp_url));
503
+ }
504
+ }
394
505
  const mandatoryAuth = config2.proxy.mandatory_auth ?? false;
395
506
  return {
396
507
  port: Number.parseInt(config2.proxy.listen.split(":")[1] || "9090"),
@@ -508,7 +619,7 @@ function createMultiAgentProxy(config2) {
508
619
  const grant = await grantsClient.requestGrant({
509
620
  requester: effectiveEmail,
510
621
  targetHost: domain,
511
- audience: "proxy",
622
+ audience: "ape-proxy",
512
623
  grantType: rule.grant_type,
513
624
  permissions,
514
625
  reason: `${method} ${targetUrl}`,
@@ -535,7 +646,7 @@ function createMultiAgentProxy(config2) {
535
646
  const grant = await grantsClient.requestGrant({
536
647
  requester: effectiveEmail,
537
648
  targetHost: domain,
538
- audience: "proxy",
649
+ audience: "ape-proxy",
539
650
  grantType: rule.grant_type,
540
651
  permissions,
541
652
  reason: `${method} ${targetUrl}`,
@@ -577,10 +688,14 @@ function createMultiAgentProxy(config2) {
577
688
  };
578
689
  }
579
690
  function createNodeHandler(config2) {
580
- const proxy = createMultiAgentProxy(config2);
691
+ const grantsClients = buildGrantsClients(config2);
692
+ const proxy = createMultiAgentProxy(config2, grantsClients);
581
693
  return {
582
694
  handleRequest(req, res) {
583
- const url = `http://${req.headers.host || "localhost"}${req.url || "/"}`;
695
+ const reqUrl = req.url || "/";
696
+ const isAbsolute = reqUrl.startsWith("http://") || reqUrl.startsWith("https://");
697
+ const proxyHost = req.headers.host || "localhost";
698
+ const url = isAbsolute ? `http://${proxyHost}/${reqUrl}` : `http://${proxyHost}${reqUrl}`;
584
699
  const chunks = [];
585
700
  req.on("data", (chunk) => chunks.push(chunk));
586
701
  req.on("end", async () => {
@@ -622,7 +737,7 @@ function createNodeHandler(config2) {
622
737
  });
623
738
  },
624
739
  handleConnect(req, socket, head) {
625
- handleConnect(config2, req, socket, head);
740
+ handleConnect(config2, grantsClients, req, socket, head);
626
741
  }
627
742
  };
628
743
  }
@@ -667,7 +782,6 @@ console.log(`[openape-proxy] Loading config from ${configPath}`);
667
782
  var config = loadMultiAgentConfig(configPath, {
668
783
  mandatoryAuth: values["mandatory-auth"] || void 0
669
784
  });
670
- initAudit(config.proxy.audit_log);
671
785
  if (values["dry-run"]) {
672
786
  console.log("[openape-proxy] DRY RUN mode \u2014 logging only, not blocking");
673
787
  console.log("[openape-proxy] Config loaded:");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/proxy.ts","../src/matcher.ts","../src/auth.ts","../src/grants-client.ts","../src/audit.ts","../src/ssrf.ts","../src/connect.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createServer } from 'node:http'\nimport { parseArgs } from 'node:util'\nimport { loadMultiAgentConfig } from './config.js'\nimport { createNodeHandler } from './proxy.js'\nimport { initAudit } from './audit.js'\n\nconst { values } = parseArgs({\n options: {\n config: { type: 'string', short: 'c', default: 'config.toml' },\n 'dry-run': { type: 'boolean', default: false },\n 'mandatory-auth': { type: 'boolean', default: false },\n },\n})\n\nconst configPath = values.config!\n\nconsole.log(`[openape-proxy] Loading config from ${configPath}`)\nconst config = loadMultiAgentConfig(configPath, {\n mandatoryAuth: values['mandatory-auth'] || undefined,\n})\n\n// Init audit log\ninitAudit(config.proxy.audit_log)\n\nif (values['dry-run']) {\n console.log('[openape-proxy] DRY RUN mode — logging only, not blocking')\n console.log('[openape-proxy] Config loaded:')\n console.log(` Listen: ${config.proxy.listen}`)\n console.log(` Default action: ${config.proxy.default_action}`)\n console.log(` Mandatory auth: ${config.proxy.mandatory_auth ?? false}`)\n console.log(` Agents: ${config.agents.length}`)\n for (const agent of config.agents) {\n const allowCount = agent.allow?.length ?? 0\n const denyCount = agent.deny?.length ?? 0\n const grantCount = agent.grant_required?.length ?? 0\n console.log(` ${agent.email} (${agent.idp_url}) — ${allowCount} allow, ${denyCount} deny, ${grantCount} grant`)\n }\n process.exit(0)\n}\n\nconst handler = createNodeHandler(config)\n\nconst port = Number.parseInt(config.proxy.listen.split(':')[1] || '9090')\nconst hostname = config.proxy.listen.split(':')[0] || '127.0.0.1'\n\nconst server = createServer(handler.handleRequest)\nserver.on('connect', handler.handleConnect)\n\nserver.listen(port, hostname, () => {\n // When configured with port 0, the OS assigns a free port — use the actual\n // bound port for the log line so external orchestrators (apes proxy --) can\n // grep this line to discover where to connect.\n const addr = server.address()\n const actualPort = typeof addr === 'object' && addr ? addr.port : port\n console.log(`[openape-proxy] Listening on http://${hostname}:${actualPort}`)\n console.log(`[openape-proxy] CONNECT tunneling enabled`)\n console.log(`[openape-proxy] Mandatory auth: ${config.proxy.mandatory_auth ?? false}`)\n console.log(`[openape-proxy] Agents: ${config.agents.map(a => a.email).join(', ')}`)\n console.log(`[openape-proxy] Default action: ${config.proxy.default_action}`)\n})\n\n// Graceful shutdown\nprocess.on('SIGINT', () => {\n console.log('\\n[openape-proxy] Shutting down...')\n server.close()\n process.exit(0)\n})\n\nprocess.on('SIGTERM', () => {\n console.log('[openape-proxy] Shutting down...')\n server.close()\n process.exit(0)\n})\n","import { readFileSync } from 'node:fs'\nimport { parse as parseTOML } from 'smol-toml'\nimport type { AgentConfig, MultiAgentProxyConfig, ProxyConfig } from './types.js'\n\nexport function loadConfig(path: string): ProxyConfig {\n const raw = readFileSync(path, 'utf-8')\n\n let parsed: Record<string, unknown>\n if (path.endsWith('.json')) {\n parsed = JSON.parse(raw)\n }\n else {\n parsed = parseTOML(raw) as Record<string, unknown>\n }\n\n const proxy = parsed.proxy as ProxyConfig['proxy']\n if (!proxy?.listen || !proxy?.idp_url || !proxy?.agent_email) {\n throw new Error('Config must have [proxy] with listen, idp_url, and agent_email')\n }\n\n proxy.default_action ??= 'block'\n\n return {\n proxy,\n allow: (parsed.allow ?? []) as ProxyConfig['allow'],\n deny: (parsed.deny ?? []) as ProxyConfig['deny'],\n grant_required: (parsed.grant_required ?? []) as ProxyConfig['grant_required'],\n }\n}\n\n/**\n * Load config as multi-agent format.\n * If the config has an `agents` array, use it directly.\n * Otherwise, convert single-agent format to multi-agent for backward-compat.\n */\nexport function loadMultiAgentConfig(path: string, overrides?: { mandatoryAuth?: boolean }): MultiAgentProxyConfig {\n const raw = readFileSync(path, 'utf-8')\n\n let parsed: Record<string, unknown>\n if (path.endsWith('.json')) {\n parsed = JSON.parse(raw)\n }\n else {\n parsed = parseTOML(raw) as Record<string, unknown>\n }\n\n const proxy = parsed.proxy as Record<string, unknown>\n if (!proxy?.listen) {\n throw new Error('Config must have [proxy] with listen')\n }\n\n const baseProxy: MultiAgentProxyConfig['proxy'] = {\n listen: proxy.listen as string,\n default_action: (proxy.default_action as MultiAgentProxyConfig['proxy']['default_action']) ?? 'block',\n audit_log: proxy.audit_log as string | undefined,\n mandatory_auth: overrides?.mandatoryAuth ?? (proxy.mandatory_auth as boolean | undefined),\n }\n\n // Multi-agent format: has agents array\n if (Array.isArray(parsed.agents)) {\n return {\n proxy: baseProxy,\n agents: parsed.agents as AgentConfig[],\n }\n }\n\n // Single-agent format: convert to multi-agent\n const idpUrl = proxy.idp_url as string\n const agentEmail = proxy.agent_email as string\n if (!idpUrl || !agentEmail) {\n throw new Error('Single-agent config requires proxy.idp_url and proxy.agent_email')\n }\n\n return {\n proxy: baseProxy,\n agents: [{\n email: agentEmail,\n idp_url: idpUrl,\n allow: (parsed.allow ?? []) as AgentConfig['allow'],\n deny: (parsed.deny ?? []) as AgentConfig['deny'],\n grant_required: (parsed.grant_required ?? []) as AgentConfig['grant_required'],\n }],\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { createHash } from 'node:crypto'\nimport type { AgentConfig, AuditEntry, MultiAgentProxyConfig, ProxyConfig } from './types.js'\nimport { evaluateRules } from './matcher.js'\nimport { AuthError, verifyAgentAuth } from './auth.js'\nimport { GrantsClient } from './grants-client.js'\nimport { writeAudit } from './audit.js'\nimport { isPrivateOrLoopback } from './ssrf.js'\nimport { handleConnect } from './connect.js'\n\n/**\n * Compute a request hash that uniquely identifies the intent.\n * hash = sha256(METHOD + \" \" + FULL_URL + \"\\n\" + BODY)\n * This binds the grant to the exact request — no bait-and-switch.\n */\nasync function computeRequestHash(method: string, targetUrl: string, body: ArrayBuffer | null): Promise<string> {\n const hash = createHash('sha256')\n hash.update(`${method} ${targetUrl}\\n`)\n if (body && body.byteLength > 0) {\n hash.update(new Uint8Array(body))\n }\n return hash.digest('hex')\n}\n\n/** Legacy single-agent proxy */\nexport function createProxy(config: ProxyConfig) {\n const multiConfig: MultiAgentProxyConfig = {\n proxy: {\n listen: config.proxy.listen,\n default_action: config.proxy.default_action,\n audit_log: config.proxy.audit_log,\n mandatory_auth: config.proxy.mandatory_auth,\n },\n agents: [{\n email: config.proxy.agent_email,\n idp_url: config.proxy.idp_url,\n allow: config.allow,\n deny: config.deny,\n grant_required: config.grant_required,\n }],\n }\n return createMultiAgentProxy(multiConfig)\n}\n\n/** Multi-agent proxy with SSRF protection and mandatory auth */\nexport function createMultiAgentProxy(config: MultiAgentProxyConfig) {\n const grantsClients = new Map<string, GrantsClient>()\n for (const agent of config.agents) {\n grantsClients.set(agent.email, new GrantsClient(agent.idp_url))\n }\n\n const mandatoryAuth = config.proxy.mandatory_auth ?? false\n\n return {\n port: Number.parseInt(config.proxy.listen.split(':')[1] || '9090'),\n hostname: config.proxy.listen.split(':')[0] || '127.0.0.1',\n\n async fetch(req: Request): Promise<Response> {\n const url = new URL(req.url)\n const startTime = Date.now()\n\n // Health endpoint\n if (url.pathname === '/healthz') {\n return Response.json({\n status: 'ok',\n agents: config.agents.map(a => a.email),\n })\n }\n\n // Parse target URL from the path\n const targetUrl = url.pathname.slice(1) + url.search\n let targetParsed: URL\n try {\n targetParsed = new URL(targetUrl)\n }\n catch {\n return new Response(\n 'Invalid target URL. Send requests as: http://proxy:port/https://target.com/path',\n { status: 400 },\n )\n }\n\n const domain = targetParsed.hostname\n const method = req.method\n const path = targetParsed.pathname\n\n // SSRF protection — block private/loopback IPs before any rule evaluation\n if (await isPrivateOrLoopback(domain)) {\n return new Response('Blocked: private/loopback IP', { status: 403 })\n }\n\n // Read body once (needed for hash + forwarding)\n const bodyBuffer = req.body ? await req.arrayBuffer() : null\n\n // Verify agent identity — find IdP URL from first agent (for JWKS verification)\n // In multi-agent mode, we need the JWT to identify the agent first.\n // We try verification against each agent's IdP until one succeeds.\n let agentIdentity: { email: string, act: 'agent' } | null = null\n try {\n for (const agentConf of config.agents) {\n agentIdentity = await verifyAgentAuth(\n req.headers.get('proxy-authorization'),\n agentConf.idp_url,\n mandatoryAuth && config.agents.length === 1,\n )\n if (agentIdentity) break\n }\n\n // If mandatory auth and no identity found from any IdP\n if (mandatoryAuth && !agentIdentity) {\n throw new AuthError('JWT required')\n }\n }\n catch (err) {\n if (err instanceof AuthError) {\n return new Response(`Unauthorized: ${err.message}`, { status: 401 })\n }\n throw err\n }\n\n // Find the matching agent config\n const agentEmail = agentIdentity?.email\n let agentConf: AgentConfig | undefined\n\n if (agentEmail) {\n agentConf = config.agents.find(a => a.email === agentEmail)\n if (!agentConf) {\n return new Response(`Forbidden: unknown agent ${agentEmail}`, { status: 403 })\n }\n }\n else if (config.agents.length === 1) {\n // Non-mandatory auth, single agent: use the only agent config\n agentConf = config.agents[0]\n }\n else {\n return new Response('Unauthorized: JWT required for multi-agent proxy', { status: 401 })\n }\n\n const effectiveEmail = agentEmail ?? agentConf.email\n const grantsClient = grantsClients.get(agentConf.email)!\n\n // Build a ProxyConfig-shaped object for evaluateRules\n const rulesConfig: ProxyConfig = {\n proxy: {\n listen: config.proxy.listen,\n idp_url: agentConf.idp_url,\n agent_email: agentConf.email,\n default_action: config.proxy.default_action,\n },\n allow: agentConf.allow ?? [],\n deny: agentConf.deny ?? [],\n grant_required: agentConf.grant_required ?? [],\n }\n\n // Evaluate rules\n const action = evaluateRules(rulesConfig, domain, method, path)\n\n const baseAudit: Omit<AuditEntry, 'action' | 'rule'> = {\n ts: new Date().toISOString(),\n agent: effectiveEmail,\n domain,\n method,\n path,\n }\n\n // DENY\n if (action.type === 'deny') {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'deny-list', grant_id: null })\n return new Response(`Blocked: ${action.note || 'deny rule'}`, { status: 403 })\n }\n\n // ALLOW (no grant needed)\n if (action.type === 'allow') {\n writeAudit({ ...baseAudit, action: 'allow', rule: 'allow-list', grant_id: null })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n // GRANT REQUIRED\n const rule = action.rule\n const permissions = rule.permissions ?? [`${method.toLowerCase()}:${domain}`]\n\n // Compute request hash — binds grant to exact method + URL + body\n const requestHash = await computeRequestHash(method, targetUrl, bodyBuffer)\n\n // Check for existing grant\n const existing = await grantsClient.findExistingGrant(\n effectiveEmail,\n domain,\n 'proxy',\n permissions,\n ).catch(() => null)\n\n if (existing) {\n writeAudit({\n ...baseAudit,\n action: 'grant_approved',\n rule: 'standing-grant',\n grant_id: existing.id,\n request_hash: requestHash,\n })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n // No existing grant — behavior depends on default_action\n if (config.proxy.default_action === 'block') {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'no-grant (block mode)', grant_id: null })\n return new Response('No grant — blocked', { status: 403 })\n }\n\n if (config.proxy.default_action === 'request-async') {\n // Create grant request, return 407 immediately\n const grant = await grantsClient.requestGrant({\n requester: effectiveEmail,\n targetHost: domain,\n audience: 'proxy',\n grantType: rule.grant_type,\n permissions,\n reason: `${method} ${targetUrl}`,\n requestHash,\n duration: rule.duration,\n }).catch(() => null)\n\n writeAudit({\n ...baseAudit,\n action: 'grant_denied',\n rule: 'grant_required (async)',\n grant_id: grant?.id ?? null,\n })\n\n return new Response(\n JSON.stringify({\n error: 'Grant required',\n grant_id: grant?.id,\n message: 'Grant request created. Retry after approval.',\n }),\n { status: 407, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n // BLOCKING mode: create grant request and wait\n console.error(`[proxy] Requesting grant for ${method} ${domain}${path} — waiting for approval...`)\n\n try {\n const grant = await grantsClient.requestGrant({\n requester: effectiveEmail,\n targetHost: domain,\n audience: 'proxy',\n grantType: rule.grant_type,\n permissions,\n reason: `${method} ${targetUrl}`,\n requestHash,\n duration: rule.duration,\n })\n\n const approved = await grantsClient.waitForApproval(grant.id)\n\n const waitedMs = Date.now() - startTime\n\n if (approved.status === 'approved') {\n writeAudit({\n ...baseAudit,\n action: 'grant_approved',\n rule: 'grant_required',\n grant_id: approved.id,\n request_hash: requestHash,\n waited_ms: waitedMs,\n })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n writeAudit({\n ...baseAudit,\n action: 'grant_denied',\n rule: 'grant_required',\n grant_id: approved.id,\n waited_ms: waitedMs,\n })\n return new Response(`Grant denied by ${approved.decided_by}`, { status: 403 })\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : 'Unknown error'\n writeAudit({\n ...baseAudit,\n action: 'grant_timeout',\n rule: 'grant_required',\n error: msg,\n })\n return new Response(`Grant request failed: ${msg}`, { status: 504 })\n }\n },\n }\n}\n\n/**\n * Create a node:http compatible handler for use with http.createServer().\n * Returns both a request handler and a CONNECT handler.\n */\nexport function createNodeHandler(config: MultiAgentProxyConfig): {\n handleRequest: (req: IncomingMessage, res: ServerResponse) => void\n handleConnect: (req: IncomingMessage, socket: Socket, head: Buffer) => void\n} {\n const proxy = createMultiAgentProxy(config)\n\n return {\n handleRequest(req: IncomingMessage, res: ServerResponse) {\n // Convert IncomingMessage to Request and use existing fetch logic\n const url = `http://${req.headers.host || 'localhost'}${req.url || '/'}`\n const chunks: Buffer[] = []\n\n req.on('data', (chunk: Buffer) => chunks.push(chunk))\n req.on('end', async () => {\n try {\n const body = chunks.length > 0 ? Buffer.concat(chunks) : undefined\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n headers.set(key, Array.isArray(value) ? value.join(', ') : value)\n }\n }\n\n const request = new Request(url, {\n method: req.method,\n headers,\n body: body && req.method !== 'GET' && req.method !== 'HEAD' ? body : undefined,\n duplex: 'half',\n } as RequestInit)\n\n const response = await proxy.fetch(request)\n\n res.writeHead(response.status, response.statusText, Object.fromEntries(response.headers))\n if (response.body) {\n const reader = response.body.getReader()\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read()\n if (done) {\n res.end()\n return\n }\n res.write(value)\n return pump()\n }\n await pump()\n }\n else {\n res.end()\n }\n }\n catch {\n res.writeHead(502)\n res.end('Proxy error')\n }\n })\n },\n\n handleConnect(req: IncomingMessage, socket: Socket, head: Buffer) {\n handleConnect(config, req, socket, head)\n },\n }\n}\n\n/**\n * Forward a request to the target URL.\n * Strips proxy-specific headers, preserves the rest.\n */\nasync function forwardRequest(originalReq: Request, targetUrl: string, cachedBody?: ArrayBuffer | null): Promise<Response> {\n const headers = new Headers(originalReq.headers)\n // Remove proxy-specific headers\n headers.delete('proxy-authorization')\n headers.delete('proxy-connection')\n // Don't send host of the proxy\n headers.delete('host')\n\n const body = cachedBody && cachedBody.byteLength > 0 ? cachedBody : null\n\n try {\n const res = await fetch(targetUrl, {\n method: originalReq.method,\n headers,\n body,\n duplex: 'half',\n redirect: 'manual',\n })\n\n // Stream the response back\n const responseHeaders = new Headers(res.headers)\n // Remove hop-by-hop headers\n responseHeaders.delete('transfer-encoding')\n responseHeaders.delete('connection')\n\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers: responseHeaders,\n })\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : 'Upstream error'\n return new Response(`Proxy error: ${msg}`, { status: 502 })\n }\n}\n","import type { ProxyConfig, RuleAction, RuleEntry } from './types.js'\n\n/**\n * Match a glob pattern against a string.\n * Supports * (any segment) and ** (any number of segments).\n */\nfunction globMatch(pattern: string, value: string): boolean {\n // Simple glob: convert * to regex\n const regex = new RegExp(\n `^${\n pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex chars except *\n .replace(/\\*\\*/g, '<<<DOUBLESTAR>>>')\n .replace(/\\*/g, '[^/]*')\n .replace(/<<<DOUBLESTAR>>>/g, '.*')\n }$`,\n )\n return regex.test(value)\n}\n\nfunction matchesRule(rule: RuleEntry, domain: string, method: string, path: string): boolean {\n // Domain match (supports wildcards like *.github.com)\n if (!globMatch(rule.domain, domain)) return false\n\n // Method match (if specified)\n if (rule.methods && rule.methods.length > 0) {\n if (!rule.methods.includes(method.toUpperCase())) return false\n }\n\n // Path match (if specified)\n if (rule.path) {\n if (!globMatch(rule.path, path)) return false\n }\n\n return true\n}\n\n/**\n * Evaluate rules in order: deny → allow → grant_required → default_action\n */\nexport function evaluateRules(\n config: ProxyConfig,\n domain: string,\n method: string,\n path: string,\n): RuleAction {\n // 1. Check deny list first\n for (const rule of config.deny) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'deny', note: rule.note }\n }\n }\n\n // 2. Check allow list\n for (const rule of config.allow) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'allow' }\n }\n }\n\n // 3. Check grant_required rules (most specific first)\n for (const rule of config.grant_required) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'grant_required', rule }\n }\n }\n\n // 4. Default: treat as grant_required with 'once'\n if (config.proxy.default_action === 'block') {\n return { type: 'deny', note: 'No matching rule (default: block)' }\n }\n\n return {\n type: 'grant_required',\n rule: {\n domain: '*',\n grant_type: 'once',\n },\n }\n}\n","import { verifyJWT, createRemoteJWKS } from '@openape/core'\n\nexport interface AgentIdentity {\n email: string\n act: 'agent'\n}\n\nexport class AuthError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'AuthError'\n }\n}\n\n/**\n * Verify agent JWT from Proxy-Authorization header.\n * Returns the agent identity or null if invalid/missing.\n * When mandatory is true, throws AuthError if no valid JWT is provided.\n */\nexport async function verifyAgentAuth(\n authHeader: string | null,\n idpUrl: string,\n mandatory: boolean = false,\n): Promise<AgentIdentity | null> {\n if (!authHeader) {\n if (mandatory) throw new AuthError('JWT required')\n return null\n }\n\n const match = authHeader.match(/^Bearer (.+)$/i)\n if (!match) {\n if (mandatory) throw new AuthError('Invalid authorization header')\n return null\n }\n\n const token = match[1]\n\n try {\n const jwks = createRemoteJWKS(`${idpUrl}/.well-known/jwks.json`)\n const { payload } = await verifyJWT(token, jwks, { issuer: idpUrl })\n\n if (payload.act !== 'agent' || !payload.sub) {\n if (mandatory) throw new AuthError('Invalid agent token')\n return null\n }\n\n return {\n email: payload.sub as string,\n act: 'agent',\n }\n }\n catch (err) {\n if (err instanceof AuthError) throw err\n if (mandatory) throw new AuthError('JWT verification failed')\n return null\n }\n}\n","import type { OpenApeGrant, GrantType } from '@openape/core'\n\n/**\n * Client for the IdP's grant management API.\n * Creates grant requests and polls for approval.\n */\nexport class GrantsClient {\n private idpUrl: string\n private agentToken: string | undefined\n\n constructor(idpUrl: string) {\n this.idpUrl = idpUrl.replace(/\\/$/, '')\n }\n\n setAgentToken(token: string): void {\n this.agentToken = token\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' }\n if (this.agentToken) {\n h.Authorization = `Bearer ${this.agentToken}`\n }\n return h\n }\n\n /**\n * Create a grant request on the IdP.\n */\n async requestGrant(opts: {\n requester: string\n targetHost: string\n audience: string\n grantType: GrantType\n permissions?: string[]\n reason?: string\n requestHash?: string\n duration?: number\n }): Promise<OpenApeGrant> {\n const res = await fetch(`${this.idpUrl}/api/grants`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify({\n requester: opts.requester,\n target_host: opts.targetHost,\n audience: opts.audience,\n grant_type: opts.grantType,\n permissions: opts.permissions,\n reason: opts.reason,\n request_hash: opts.requestHash,\n duration: opts.duration,\n }),\n })\n\n if (!res.ok) {\n throw new Error(`Grant request failed: ${res.status} ${await res.text()}`)\n }\n\n return res.json() as Promise<OpenApeGrant>\n }\n\n /**\n * Poll a grant until it's approved, denied, or timeout.\n */\n async waitForApproval(\n grantId: string,\n timeoutMs: number = 300_000,\n pollIntervalMs: number = 2_000,\n ): Promise<OpenApeGrant> {\n const deadline = Date.now() + timeoutMs\n\n while (Date.now() < deadline) {\n const res = await fetch(`${this.idpUrl}/api/grants/${grantId}`, {\n headers: this.headers(),\n })\n\n if (!res.ok) {\n throw new Error(`Grant poll failed: ${res.status}`)\n }\n\n const grant = await res.json() as OpenApeGrant\n if (grant.status !== 'pending') {\n return grant\n }\n\n await new Promise(r => setTimeout(r, pollIntervalMs))\n }\n\n throw new Error(`Grant approval timed out after ${timeoutMs}ms`)\n }\n\n /**\n * Check if there's an existing approved grant for a host+audience+permissions combo.\n * Ignores `once` grants (they're single-use).\n */\n async findExistingGrant(\n requester: string,\n targetHost: string,\n audience: string,\n permissions?: string[],\n ): Promise<OpenApeGrant | null> {\n const params = new URLSearchParams({\n requester,\n status: 'approved',\n })\n\n const res = await fetch(`${this.idpUrl}/api/grants?${params}`, {\n headers: this.headers(),\n })\n\n if (!res.ok) return null\n\n const grants = await res.json() as OpenApeGrant[]\n const now = Math.floor(Date.now() / 1000)\n\n return grants.find((g) => {\n if (g.status !== 'approved') return false\n if (g.expires_at && g.expires_at <= now) return false\n if (g.request?.grant_type === 'once') return false\n if (g.request?.target_host !== targetHost) return false\n if (g.request?.audience !== audience) return false\n if (permissions?.length && g.request?.permissions?.length) {\n const grantedPerms = new Set(g.request.permissions)\n if (!permissions.every(p => grantedPerms.has(p))) return false\n }\n return true\n }) ?? null\n }\n}\n","import { appendFileSync } from 'node:fs'\nimport type { AuditEntry } from './types.js'\n\nlet auditPath: string | undefined\n\nexport function initAudit(path?: string): void {\n auditPath = path\n}\n\nexport function writeAudit(entry: AuditEntry): void {\n const line = JSON.stringify(entry)\n\n // Always log to stderr\n console.error(`[audit] ${entry.action} ${entry.method} ${entry.domain}${entry.path}${entry.grant_id ? ` grant=${entry.grant_id}` : ''}`)\n\n // Write to file if configured\n if (auditPath) {\n appendFileSync(auditPath, `${line}\\n`)\n }\n}\n","import { resolve4, resolve6 } from 'node:dns/promises'\nimport { isIP } from 'node:net'\n\nconst PRIVATE_RANGES_V4 = [\n { prefix: 0x7F000000, mask: 0xFF000000 }, // 127.0.0.0/8\n { prefix: 0x0A000000, mask: 0xFF000000 }, // 10.0.0.0/8\n { prefix: 0xAC100000, mask: 0xFFF00000 }, // 172.16.0.0/12\n { prefix: 0xC0A80000, mask: 0xFFFF0000 }, // 192.168.0.0/16\n { prefix: 0xA9FE0000, mask: 0xFFFF0000 }, // 169.254.0.0/16\n { prefix: 0x00000000, mask: 0xFF000000 }, // 0.0.0.0/8\n]\n\nfunction ipv4ToNumber(ip: string): number {\n const parts = ip.split('.').map(Number)\n return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0\n}\n\nfunction isPrivateIPv4(ip: string): boolean {\n const num = ipv4ToNumber(ip)\n return PRIVATE_RANGES_V4.some(r => ((num & r.mask) >>> 0) === r.prefix)\n}\n\nfunction isPrivateIPv6(ip: string): boolean {\n const normalized = ip.toLowerCase()\n\n // Loopback ::1\n if (normalized === '::1') return true\n\n // Unspecified ::\n if (normalized === '::') return true\n\n // Link-local fe80::/10\n if (normalized.startsWith('fe8') || normalized.startsWith('fe9')\n || normalized.startsWith('fea') || normalized.startsWith('feb')) {\n return true\n }\n\n // Unique local fd00::/8\n if (normalized.startsWith('fd')) return true\n\n // IPv4-mapped ::ffff:x.x.x.x\n const v4mapped = normalized.match(/^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/)\n if (v4mapped) return isPrivateIPv4(v4mapped[1])\n\n return false\n}\n\nfunction isPrivateIP(ip: string): boolean {\n if (isIP(ip) === 4) return isPrivateIPv4(ip)\n if (isIP(ip) === 6) return isPrivateIPv6(ip)\n return false\n}\n\n/**\n * Check if a hostname resolves to a private or loopback IP.\n * If the hostname is already an IP literal, check directly.\n * Otherwise, resolve via DNS and check all results.\n */\nexport async function isPrivateOrLoopback(hostname: string): Promise<boolean> {\n // Direct IP literal\n if (isIP(hostname)) {\n return isPrivateIP(hostname)\n }\n\n // localhost shortcut\n if (hostname === 'localhost') return true\n\n // DNS resolution — check both A and AAAA records\n try {\n const [v4, v6] = await Promise.allSettled([\n resolve4(hostname),\n resolve6(hostname),\n ])\n const addrs: string[] = []\n if (v4.status === 'fulfilled') addrs.push(...v4.value)\n if (v6.status === 'fulfilled') addrs.push(...v6.value)\n\n if (addrs.length === 0) return true // no records — block to be safe\n return addrs.some(addr => isPrivateIP(addr))\n }\n catch {\n // DNS failure — block to be safe\n return true\n }\n}\n","import type { IncomingMessage } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { connect } from 'node:net'\nimport type { MultiAgentProxyConfig } from './types.js'\nimport { AuthError, verifyAgentAuth } from './auth.js'\nimport { isPrivateOrLoopback } from './ssrf.js'\nimport { writeAudit } from './audit.js'\n\n/**\n * Handle HTTP CONNECT requests for tunneling (used by HTTP_PROXY clients).\n * Flow: Auth check → SSRF check → TCP connect → bidirectional pipe.\n */\nexport async function handleConnect(\n config: MultiAgentProxyConfig,\n req: IncomingMessage,\n clientSocket: Socket,\n _head: Buffer,\n): Promise<void> {\n const target = req.url ?? ''\n const [host, portStr] = target.split(':')\n const port = Number.parseInt(portStr || '443')\n\n if (!host || !port) {\n clientSocket.write('HTTP/1.1 400 Bad Request\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n\n const mandatoryAuth = config.proxy.mandatory_auth ?? false\n\n // Auth check — CONNECT always requires auth in mandatory mode\n let agentEmail: string | undefined\n try {\n const authHeader = req.headers['proxy-authorization'] as string | undefined\n let identity: { email: string, act: 'agent' } | null = null\n\n for (const agentConf of config.agents) {\n identity = await verifyAgentAuth(\n authHeader ?? null,\n agentConf.idp_url,\n mandatoryAuth && config.agents.length === 1,\n )\n if (identity) break\n }\n\n if (mandatoryAuth && !identity) {\n throw new AuthError('JWT required')\n }\n\n agentEmail = identity?.email\n\n // Verify agent is known\n if (agentEmail) {\n const known = config.agents.find(a => a.email === agentEmail)\n if (!known) {\n clientSocket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n }\n else if (config.agents.length > 1) {\n throw new AuthError('JWT required for multi-agent proxy')\n }\n }\n catch (err) {\n if (err instanceof AuthError) {\n clientSocket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n throw err\n }\n\n // SSRF check\n if (await isPrivateOrLoopback(host)) {\n writeAudit({\n ts: new Date().toISOString(),\n agent: agentEmail ?? config.agents[0]?.email ?? 'unknown',\n action: 'deny',\n domain: host,\n method: 'CONNECT',\n path: target,\n rule: 'ssrf-blocked',\n })\n clientSocket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n\n // Connect to target\n const targetSocket = connect(port, host, () => {\n clientSocket.write('HTTP/1.1 200 Connection Established\\r\\n\\r\\n')\n\n // Bidirectional pipe\n targetSocket.pipe(clientSocket)\n clientSocket.pipe(targetSocket)\n })\n\n targetSocket.on('error', () => {\n clientSocket.write('HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n clientSocket.destroy()\n })\n\n clientSocket.on('error', () => {\n targetSocket.destroy()\n })\n\n // Cleanup on close\n clientSocket.on('close', () => targetSocket.destroy())\n targetSocket.on('close', () => clientSocket.destroy())\n}\n"],"mappings":"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","names":["config","hostname","config","port","config","agentConf"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/proxy.ts","../src/matcher.ts","../src/auth.ts","../src/grants-client.ts","../src/audit.ts","../src/ssrf.ts","../src/connect.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { createServer } from 'node:http'\nimport { parseArgs } from 'node:util'\nimport { loadMultiAgentConfig } from './config.js'\nimport { createNodeHandler } from './proxy.js'\n\nconst { values } = parseArgs({\n options: {\n config: { type: 'string', short: 'c', default: 'config.toml' },\n 'dry-run': { type: 'boolean', default: false },\n 'mandatory-auth': { type: 'boolean', default: false },\n },\n})\n\nconst configPath = values.config!\n\nconsole.log(`[openape-proxy] Loading config from ${configPath}`)\nconst config = loadMultiAgentConfig(configPath, {\n mandatoryAuth: values['mandatory-auth'] || undefined,\n})\n\nif (values['dry-run']) {\n console.log('[openape-proxy] DRY RUN mode — logging only, not blocking')\n console.log('[openape-proxy] Config loaded:')\n console.log(` Listen: ${config.proxy.listen}`)\n console.log(` Default action: ${config.proxy.default_action}`)\n console.log(` Mandatory auth: ${config.proxy.mandatory_auth ?? false}`)\n console.log(` Agents: ${config.agents.length}`)\n for (const agent of config.agents) {\n const allowCount = agent.allow?.length ?? 0\n const denyCount = agent.deny?.length ?? 0\n const grantCount = agent.grant_required?.length ?? 0\n console.log(` ${agent.email} (${agent.idp_url}) — ${allowCount} allow, ${denyCount} deny, ${grantCount} grant`)\n }\n process.exit(0)\n}\n\nconst handler = createNodeHandler(config)\n\nconst port = Number.parseInt(config.proxy.listen.split(':')[1] || '9090')\nconst hostname = config.proxy.listen.split(':')[0] || '127.0.0.1'\n\nconst server = createServer(handler.handleRequest)\nserver.on('connect', handler.handleConnect)\n\nserver.listen(port, hostname, () => {\n // When configured with port 0, the OS assigns a free port — use the actual\n // bound port for the log line so external orchestrators (apes proxy --) can\n // grep this line to discover where to connect.\n const addr = server.address()\n const actualPort = typeof addr === 'object' && addr ? addr.port : port\n console.log(`[openape-proxy] Listening on http://${hostname}:${actualPort}`)\n console.log(`[openape-proxy] CONNECT tunneling enabled`)\n console.log(`[openape-proxy] Mandatory auth: ${config.proxy.mandatory_auth ?? false}`)\n console.log(`[openape-proxy] Agents: ${config.agents.map(a => a.email).join(', ')}`)\n console.log(`[openape-proxy] Default action: ${config.proxy.default_action}`)\n})\n\n// Graceful shutdown\nprocess.on('SIGINT', () => {\n console.log('\\n[openape-proxy] Shutting down...')\n server.close()\n process.exit(0)\n})\n\nprocess.on('SIGTERM', () => {\n console.log('[openape-proxy] Shutting down...')\n server.close()\n process.exit(0)\n})\n","import { readFileSync } from 'node:fs'\nimport { parse as parseTOML } from 'smol-toml'\nimport type { AgentConfig, MultiAgentProxyConfig, ProxyConfig } from './types.js'\n\nexport function loadConfig(path: string): ProxyConfig {\n const raw = readFileSync(path, 'utf-8')\n\n let parsed: Record<string, unknown>\n if (path.endsWith('.json')) {\n parsed = JSON.parse(raw)\n }\n else {\n parsed = parseTOML(raw) as Record<string, unknown>\n }\n\n const proxy = parsed.proxy as ProxyConfig['proxy']\n if (!proxy?.listen || !proxy?.idp_url || !proxy?.agent_email) {\n throw new Error('Config must have [proxy] with listen, idp_url, and agent_email')\n }\n\n proxy.default_action ??= 'block'\n\n return {\n proxy,\n allow: (parsed.allow ?? []) as ProxyConfig['allow'],\n deny: (parsed.deny ?? []) as ProxyConfig['deny'],\n grant_required: (parsed.grant_required ?? []) as ProxyConfig['grant_required'],\n }\n}\n\n/**\n * Load config as multi-agent format.\n * If the config has an `agents` array, use it directly.\n * Otherwise, convert single-agent format to multi-agent for backward-compat.\n */\nexport function loadMultiAgentConfig(path: string, overrides?: { mandatoryAuth?: boolean }): MultiAgentProxyConfig {\n const raw = readFileSync(path, 'utf-8')\n\n let parsed: Record<string, unknown>\n if (path.endsWith('.json')) {\n parsed = JSON.parse(raw)\n }\n else {\n parsed = parseTOML(raw) as Record<string, unknown>\n }\n\n const proxy = parsed.proxy as Record<string, unknown>\n if (!proxy?.listen) {\n throw new Error('Config must have [proxy] with listen')\n }\n\n const baseProxy: MultiAgentProxyConfig['proxy'] = {\n listen: proxy.listen as string,\n default_action: (proxy.default_action as MultiAgentProxyConfig['proxy']['default_action']) ?? 'block',\n mandatory_auth: overrides?.mandatoryAuth ?? (proxy.mandatory_auth as boolean | undefined),\n }\n\n // Multi-agent format: has agents array\n if (Array.isArray(parsed.agents)) {\n return {\n proxy: baseProxy,\n agents: parsed.agents as AgentConfig[],\n }\n }\n\n // Single-agent format: convert to multi-agent\n const idpUrl = proxy.idp_url as string\n const agentEmail = proxy.agent_email as string\n if (!idpUrl || !agentEmail) {\n throw new Error('Single-agent config requires proxy.idp_url and proxy.agent_email')\n }\n\n return {\n proxy: baseProxy,\n agents: [{\n email: agentEmail,\n idp_url: idpUrl,\n allow: (parsed.allow ?? []) as AgentConfig['allow'],\n deny: (parsed.deny ?? []) as AgentConfig['deny'],\n grant_required: (parsed.grant_required ?? []) as AgentConfig['grant_required'],\n }],\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { createHash } from 'node:crypto'\nimport type { AgentConfig, AuditEntry, MultiAgentProxyConfig, ProxyConfig } from './types.js'\nimport { evaluateRules } from './matcher.js'\nimport { AuthError, verifyAgentAuth } from './auth.js'\nimport { GrantsClient } from './grants-client.js'\nimport { writeAudit } from './audit.js'\nimport { isPrivateOrLoopback } from './ssrf.js'\nimport { handleConnect } from './connect.js'\n\n/**\n * Compute a request hash that uniquely identifies the intent.\n * hash = sha256(METHOD + \" \" + FULL_URL + \"\\n\" + BODY)\n * This binds the grant to the exact request — no bait-and-switch.\n */\nasync function computeRequestHash(method: string, targetUrl: string, body: ArrayBuffer | null): Promise<string> {\n const hash = createHash('sha256')\n hash.update(`${method} ${targetUrl}\\n`)\n if (body && body.byteLength > 0) {\n hash.update(new Uint8Array(body))\n }\n return hash.digest('hex')\n}\n\n/** Legacy single-agent proxy */\nexport function createProxy(config: ProxyConfig) {\n const multiConfig: MultiAgentProxyConfig = {\n proxy: {\n listen: config.proxy.listen,\n default_action: config.proxy.default_action,\n mandatory_auth: config.proxy.mandatory_auth,\n },\n agents: [{\n email: config.proxy.agent_email,\n idp_url: config.proxy.idp_url,\n allow: config.allow,\n deny: config.deny,\n grant_required: config.grant_required,\n }],\n }\n return createMultiAgentProxy(multiConfig)\n}\n\n/**\n * Build the per-agent GrantsClient map used by both the HTTP forward-proxy\n * handler (in this file's `createMultiAgentProxy`) and the CONNECT handler\n * (in `connect.ts`). Exposed so `createNodeHandler` can share a single map\n * instead of letting each handler construct its own — keeps any future\n * per-agent token state coherent.\n */\nexport function buildGrantsClients(config: MultiAgentProxyConfig): Map<string, GrantsClient> {\n const grantsClients = new Map<string, GrantsClient>()\n for (const agent of config.agents) {\n grantsClients.set(agent.email, new GrantsClient(agent.idp_url))\n }\n return grantsClients\n}\n\n/** Multi-agent proxy with SSRF protection and mandatory auth */\nexport function createMultiAgentProxy(\n config: MultiAgentProxyConfig,\n grantsClients: Map<string, GrantsClient> = buildGrantsClients(config),\n) {\n // Backwards-compat: if caller didn't pre-build the map, we build our own.\n // createNodeHandler always passes one in so the CONNECT handler can share it.\n for (const agent of config.agents) {\n if (!grantsClients.has(agent.email)) {\n grantsClients.set(agent.email, new GrantsClient(agent.idp_url))\n }\n }\n\n const mandatoryAuth = config.proxy.mandatory_auth ?? false\n\n return {\n port: Number.parseInt(config.proxy.listen.split(':')[1] || '9090'),\n hostname: config.proxy.listen.split(':')[0] || '127.0.0.1',\n\n async fetch(req: Request): Promise<Response> {\n const url = new URL(req.url)\n const startTime = Date.now()\n\n // Health endpoint\n if (url.pathname === '/healthz') {\n return Response.json({\n status: 'ok',\n agents: config.agents.map(a => a.email),\n })\n }\n\n // Parse target URL from the path\n const targetUrl = url.pathname.slice(1) + url.search\n let targetParsed: URL\n try {\n targetParsed = new URL(targetUrl)\n }\n catch {\n return new Response(\n 'Invalid target URL. Send requests as: http://proxy:port/https://target.com/path',\n { status: 400 },\n )\n }\n\n const domain = targetParsed.hostname\n const method = req.method\n const path = targetParsed.pathname\n\n // SSRF protection — block private/loopback IPs before any rule evaluation\n if (await isPrivateOrLoopback(domain)) {\n return new Response('Blocked: private/loopback IP', { status: 403 })\n }\n\n // Read body once (needed for hash + forwarding)\n const bodyBuffer = req.body ? await req.arrayBuffer() : null\n\n // Verify agent identity — find IdP URL from first agent (for JWKS verification)\n // In multi-agent mode, we need the JWT to identify the agent first.\n // We try verification against each agent's IdP until one succeeds.\n let agentIdentity: { email: string, act: 'agent' } | null = null\n try {\n for (const agentConf of config.agents) {\n agentIdentity = await verifyAgentAuth(\n req.headers.get('proxy-authorization'),\n agentConf.idp_url,\n mandatoryAuth && config.agents.length === 1,\n )\n if (agentIdentity) break\n }\n\n // If mandatory auth and no identity found from any IdP\n if (mandatoryAuth && !agentIdentity) {\n throw new AuthError('JWT required')\n }\n }\n catch (err) {\n if (err instanceof AuthError) {\n return new Response(`Unauthorized: ${err.message}`, { status: 401 })\n }\n throw err\n }\n\n // Find the matching agent config\n const agentEmail = agentIdentity?.email\n let agentConf: AgentConfig | undefined\n\n if (agentEmail) {\n agentConf = config.agents.find(a => a.email === agentEmail)\n if (!agentConf) {\n return new Response(`Forbidden: unknown agent ${agentEmail}`, { status: 403 })\n }\n }\n else if (config.agents.length === 1) {\n // Non-mandatory auth, single agent: use the only agent config\n agentConf = config.agents[0]\n }\n else {\n return new Response('Unauthorized: JWT required for multi-agent proxy', { status: 401 })\n }\n\n const effectiveEmail = agentEmail ?? agentConf.email\n const grantsClient = grantsClients.get(agentConf.email)!\n\n // Build a ProxyConfig-shaped object for evaluateRules\n const rulesConfig: ProxyConfig = {\n proxy: {\n listen: config.proxy.listen,\n idp_url: agentConf.idp_url,\n agent_email: agentConf.email,\n default_action: config.proxy.default_action,\n },\n allow: agentConf.allow ?? [],\n deny: agentConf.deny ?? [],\n grant_required: agentConf.grant_required ?? [],\n }\n\n // Evaluate rules\n const action = evaluateRules(rulesConfig, domain, method, path)\n\n const baseAudit: Omit<AuditEntry, 'action' | 'rule'> = {\n ts: new Date().toISOString(),\n agent: effectiveEmail,\n domain,\n method,\n path,\n }\n\n // DENY\n if (action.type === 'deny') {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'deny-list', grant_id: null })\n return new Response(`Blocked: ${action.note || 'deny rule'}`, { status: 403 })\n }\n\n // ALLOW (no grant needed)\n if (action.type === 'allow') {\n writeAudit({ ...baseAudit, action: 'allow', rule: 'allow-list', grant_id: null })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n // GRANT REQUIRED\n const rule = action.rule\n const permissions = rule.permissions ?? [`${method.toLowerCase()}:${domain}`]\n\n // Compute request hash — binds grant to exact method + URL + body\n const requestHash = await computeRequestHash(method, targetUrl, bodyBuffer)\n\n // Check for existing grant\n const existing = await grantsClient.findExistingGrant(\n effectiveEmail,\n domain,\n 'proxy',\n permissions,\n ).catch(() => null)\n\n if (existing) {\n writeAudit({\n ...baseAudit,\n action: 'grant_approved',\n rule: 'standing-grant',\n grant_id: existing.id,\n request_hash: requestHash,\n })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n // No existing grant — behavior depends on default_action\n if (config.proxy.default_action === 'block') {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'no-grant (block mode)', grant_id: null })\n return new Response('No grant — blocked', { status: 403 })\n }\n\n if (config.proxy.default_action === 'request-async') {\n // Create grant request, return 407 immediately\n const grant = await grantsClient.requestGrant({\n requester: effectiveEmail,\n targetHost: domain,\n audience: 'ape-proxy',\n grantType: rule.grant_type,\n permissions,\n reason: `${method} ${targetUrl}`,\n requestHash,\n duration: rule.duration,\n }).catch(() => null)\n\n writeAudit({\n ...baseAudit,\n action: 'grant_denied',\n rule: 'grant_required (async)',\n grant_id: grant?.id ?? null,\n })\n\n return new Response(\n JSON.stringify({\n error: 'Grant required',\n grant_id: grant?.id,\n message: 'Grant request created. Retry after approval.',\n }),\n { status: 407, headers: { 'Content-Type': 'application/json' } },\n )\n }\n\n // BLOCKING mode: create grant request and wait\n console.error(`[proxy] Requesting grant for ${method} ${domain}${path} — waiting for approval...`)\n\n try {\n const grant = await grantsClient.requestGrant({\n requester: effectiveEmail,\n targetHost: domain,\n audience: 'ape-proxy',\n grantType: rule.grant_type,\n permissions,\n reason: `${method} ${targetUrl}`,\n requestHash,\n duration: rule.duration,\n })\n\n const approved = await grantsClient.waitForApproval(grant.id)\n\n const waitedMs = Date.now() - startTime\n\n if (approved.status === 'approved') {\n writeAudit({\n ...baseAudit,\n action: 'grant_approved',\n rule: 'grant_required',\n grant_id: approved.id,\n request_hash: requestHash,\n waited_ms: waitedMs,\n })\n return forwardRequest(req, targetUrl, bodyBuffer)\n }\n\n writeAudit({\n ...baseAudit,\n action: 'grant_denied',\n rule: 'grant_required',\n grant_id: approved.id,\n waited_ms: waitedMs,\n })\n return new Response(`Grant denied by ${approved.decided_by}`, { status: 403 })\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : 'Unknown error'\n writeAudit({\n ...baseAudit,\n action: 'grant_timeout',\n rule: 'grant_required',\n error: msg,\n })\n return new Response(`Grant request failed: ${msg}`, { status: 504 })\n }\n },\n }\n}\n\n/**\n * Create a node:http compatible handler for use with http.createServer().\n * Returns both a request handler and a CONNECT handler.\n */\nexport function createNodeHandler(config: MultiAgentProxyConfig): {\n handleRequest: (req: IncomingMessage, res: ServerResponse) => void\n handleConnect: (req: IncomingMessage, socket: Socket, head: Buffer) => void\n} {\n // Build grantsClients once; share with both forward-proxy fetch path\n // (createMultiAgentProxy) and the CONNECT path (handleConnect). Without\n // sharing, CONNECT-time grant_required rules couldn't reach the IdP in\n // multi-agent mode without duplicating constructor work.\n const grantsClients = buildGrantsClients(config)\n const proxy = createMultiAgentProxy(config, grantsClients)\n\n return {\n handleRequest(req: IncomingMessage, res: ServerResponse) {\n // Standard HTTP_PROXY clients (curl, gh, git, npm) send the target as\n // an absolute URL in the request line for cleartext forward-proxying:\n // `GET http://example.com/path HTTP/1.1`\n // node:http surfaces that absolute URL via `req.url`. The legacy\n // `proxy.fetch` extraction expects a path-encoded form\n // (`/<full-target-url>`), so we adapt here: when `req.url` is\n // absolute, prefix it with a slash so `pathname.slice(1)` recovers\n // the same target string. Path-form clients (legacy) keep working.\n const reqUrl = req.url || '/'\n const isAbsolute = reqUrl.startsWith('http://') || reqUrl.startsWith('https://')\n const proxyHost = req.headers.host || 'localhost'\n const url = isAbsolute\n ? `http://${proxyHost}/${reqUrl}`\n : `http://${proxyHost}${reqUrl}`\n const chunks: Buffer[] = []\n\n req.on('data', (chunk: Buffer) => chunks.push(chunk))\n req.on('end', async () => {\n try {\n const body = chunks.length > 0 ? Buffer.concat(chunks) : undefined\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n headers.set(key, Array.isArray(value) ? value.join(', ') : value)\n }\n }\n\n const request = new Request(url, {\n method: req.method,\n headers,\n body: body && req.method !== 'GET' && req.method !== 'HEAD' ? body : undefined,\n duplex: 'half',\n } as RequestInit)\n\n const response = await proxy.fetch(request)\n\n res.writeHead(response.status, response.statusText, Object.fromEntries(response.headers))\n if (response.body) {\n const reader = response.body.getReader()\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read()\n if (done) {\n res.end()\n return\n }\n res.write(value)\n return pump()\n }\n await pump()\n }\n else {\n res.end()\n }\n }\n catch {\n res.writeHead(502)\n res.end('Proxy error')\n }\n })\n },\n\n handleConnect(req: IncomingMessage, socket: Socket, head: Buffer) {\n handleConnect(config, grantsClients, req, socket, head)\n },\n }\n}\n\n/**\n * Forward a request to the target URL.\n * Strips proxy-specific headers, preserves the rest.\n */\nasync function forwardRequest(originalReq: Request, targetUrl: string, cachedBody?: ArrayBuffer | null): Promise<Response> {\n const headers = new Headers(originalReq.headers)\n // Remove proxy-specific headers\n headers.delete('proxy-authorization')\n headers.delete('proxy-connection')\n // Don't send host of the proxy\n headers.delete('host')\n\n const body = cachedBody && cachedBody.byteLength > 0 ? cachedBody : null\n\n try {\n const res = await fetch(targetUrl, {\n method: originalReq.method,\n headers,\n body,\n duplex: 'half',\n redirect: 'manual',\n })\n\n // Stream the response back\n const responseHeaders = new Headers(res.headers)\n // Remove hop-by-hop headers\n responseHeaders.delete('transfer-encoding')\n responseHeaders.delete('connection')\n\n return new Response(res.body, {\n status: res.status,\n statusText: res.statusText,\n headers: responseHeaders,\n })\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : 'Upstream error'\n return new Response(`Proxy error: ${msg}`, { status: 502 })\n }\n}\n","import type { ProxyConfig, RuleAction, RuleEntry } from './types.js'\n\n/**\n * Match a glob pattern against a string.\n * Supports * (any segment) and ** (any number of segments).\n */\nfunction globMatch(pattern: string, value: string): boolean {\n // Simple glob: convert * to regex\n const regex = new RegExp(\n `^${\n pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // escape regex chars except *\n .replace(/\\*\\*/g, '<<<DOUBLESTAR>>>')\n .replace(/\\*/g, '[^/]*')\n .replace(/<<<DOUBLESTAR>>>/g, '.*')\n }$`,\n )\n return regex.test(value)\n}\n\nfunction matchesRule(rule: RuleEntry, domain: string, method: string, path: string): boolean {\n // Domain match (supports wildcards like *.github.com)\n if (!globMatch(rule.domain, domain)) return false\n\n // Method match (if specified)\n if (rule.methods && rule.methods.length > 0) {\n if (!rule.methods.includes(method.toUpperCase())) return false\n }\n\n // Path match (if specified)\n if (rule.path) {\n if (!globMatch(rule.path, path)) return false\n }\n\n return true\n}\n\n/**\n * Evaluate rules in order: deny → allow → grant_required → default_action\n */\nexport function evaluateRules(\n config: ProxyConfig,\n domain: string,\n method: string,\n path: string,\n): RuleAction {\n // 1. Check deny list first\n for (const rule of config.deny) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'deny', note: rule.note }\n }\n }\n\n // 2. Check allow list\n for (const rule of config.allow) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'allow' }\n }\n }\n\n // 3. Check grant_required rules (most specific first)\n for (const rule of config.grant_required) {\n if (matchesRule(rule, domain, method, path)) {\n return { type: 'grant_required', rule }\n }\n }\n\n // 4. Default action for unmatched hosts. Conceptually the proxy-side\n // counterpart of the IdP's per-agent YOLO policy — both decide what\n // happens when no specific rule matches, both live server-side (proxy or\n // IdP), neither leaks the decision to the agent. Differences:\n // - IdP YOLO is per-agent, evaluated at grant time, can have deny-patterns\n // and an expiry window.\n // - Proxy default_action is per-proxy-instance, evaluated at request time,\n // binary outcome (allow/deny/request-grant).\n //\n // Modes:\n // - 'block': hard deny — paranoid agent profile.\n // - 'allow': hard pass — transparent-audit profile (log every call,\n // enforce nothing). Equivalent role to a YOLO policy without a\n // deny-list.\n // - 'request' / 'request-async' (OpenApe-default): treat as\n // grant_required with a once-grant catch-all so every new host\n // surfaces an interactive grant decision.\n if (config.proxy.default_action === 'block') {\n return { type: 'deny', note: 'No matching rule (default: block)' }\n }\n if (config.proxy.default_action === 'allow') {\n return { type: 'allow' }\n }\n\n return {\n type: 'grant_required',\n rule: {\n domain: '*',\n grant_type: 'once',\n },\n }\n}\n","import { verifyJWT, createRemoteJWKS } from '@openape/core'\n\nexport interface AgentIdentity {\n email: string\n act: 'agent'\n}\n\nexport class AuthError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'AuthError'\n }\n}\n\n/**\n * Verify agent JWT from Proxy-Authorization header.\n * Returns the agent identity or null if invalid/missing.\n * When mandatory is true, throws AuthError if no valid JWT is provided.\n */\nexport async function verifyAgentAuth(\n authHeader: string | null,\n idpUrl: string,\n mandatory: boolean = false,\n): Promise<AgentIdentity | null> {\n if (!authHeader) {\n if (mandatory) throw new AuthError('JWT required')\n return null\n }\n\n const match = authHeader.match(/^Bearer (.+)$/i)\n if (!match) {\n if (mandatory) throw new AuthError('Invalid authorization header')\n return null\n }\n\n const token = match[1]\n\n try {\n const jwks = createRemoteJWKS(`${idpUrl}/.well-known/jwks.json`)\n const { payload } = await verifyJWT(token, jwks, { issuer: idpUrl })\n\n if (payload.act !== 'agent' || !payload.sub) {\n if (mandatory) throw new AuthError('Invalid agent token')\n return null\n }\n\n return {\n email: payload.sub as string,\n act: 'agent',\n }\n }\n catch (err) {\n if (err instanceof AuthError) throw err\n if (mandatory) throw new AuthError('JWT verification failed')\n return null\n }\n}\n","import type { OpenApeGrant, GrantType } from '@openape/core'\n\n/**\n * Client for the IdP's grant management API.\n * Creates grant requests and polls for approval.\n */\nexport class GrantsClient {\n private idpUrl: string\n private agentToken: string | undefined\n\n constructor(idpUrl: string) {\n this.idpUrl = idpUrl.replace(/\\/$/, '')\n }\n\n setAgentToken(token: string): void {\n this.agentToken = token\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' }\n if (this.agentToken) {\n h.Authorization = `Bearer ${this.agentToken}`\n }\n return h\n }\n\n /**\n * Create a grant request on the IdP.\n */\n async requestGrant(opts: {\n requester: string\n targetHost: string\n audience: string\n grantType: GrantType\n permissions?: string[]\n reason?: string\n requestHash?: string\n duration?: number\n }): Promise<OpenApeGrant> {\n const res = await fetch(`${this.idpUrl}/api/grants`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify({\n requester: opts.requester,\n target_host: opts.targetHost,\n audience: opts.audience,\n grant_type: opts.grantType,\n permissions: opts.permissions,\n reason: opts.reason,\n request_hash: opts.requestHash,\n duration: opts.duration,\n }),\n })\n\n if (!res.ok) {\n throw new Error(`Grant request failed: ${res.status} ${await res.text()}`)\n }\n\n return res.json() as Promise<OpenApeGrant>\n }\n\n /**\n * Poll a grant until it's approved, denied, or timeout.\n */\n async waitForApproval(\n grantId: string,\n timeoutMs: number = 300_000,\n pollIntervalMs: number = 2_000,\n ): Promise<OpenApeGrant> {\n const deadline = Date.now() + timeoutMs\n\n while (Date.now() < deadline) {\n const res = await fetch(`${this.idpUrl}/api/grants/${grantId}`, {\n headers: this.headers(),\n })\n\n if (!res.ok) {\n throw new Error(`Grant poll failed: ${res.status}`)\n }\n\n const grant = await res.json() as OpenApeGrant\n if (grant.status !== 'pending') {\n return grant\n }\n\n await new Promise(r => setTimeout(r, pollIntervalMs))\n }\n\n throw new Error(`Grant approval timed out after ${timeoutMs}ms`)\n }\n\n /**\n * Check if there's an existing approved grant for a host+audience+permissions combo.\n * Ignores `once` grants (they're single-use).\n */\n async findExistingGrant(\n requester: string,\n targetHost: string,\n audience: string,\n permissions?: string[],\n ): Promise<OpenApeGrant | null> {\n const params = new URLSearchParams({\n requester,\n status: 'approved',\n })\n\n const res = await fetch(`${this.idpUrl}/api/grants?${params}`, {\n headers: this.headers(),\n })\n\n if (!res.ok) return null\n\n const grants = await res.json() as OpenApeGrant[]\n const now = Math.floor(Date.now() / 1000)\n\n return grants.find((g) => {\n if (g.status !== 'approved') return false\n if (g.expires_at && g.expires_at <= now) return false\n if (g.request?.grant_type === 'once') return false\n if (g.request?.target_host !== targetHost) return false\n if (g.request?.audience !== audience) return false\n if (permissions?.length && g.request?.permissions?.length) {\n const grantedPerms = new Set(g.request.permissions)\n if (!permissions.every(p => grantedPerms.has(p))) return false\n }\n return true\n }) ?? null\n }\n}\n","import type { AuditEntry } from './types.js'\n\n/**\n * Format the request target for the stderr summary line. For HTTP forward-proxy\n * the path starts with `/` (so `${domain}${path}` reads `example.com/foo`), but\n * for CONNECT the path field already carries `host:port` and concatenating\n * with `domain` would print `example.comexample.com:443`.\n */\nfunction formatTarget(entry: AuditEntry): string {\n return entry.path.startsWith('/') ? `${entry.domain}${entry.path}` : entry.path\n}\n\n/**\n * Emit one operator-readable audit summary line to stderr. Intentionally NOT a\n * tamper-proof audit trail: anything written on the user's machine is also\n * writable by the user, so there's no integrity story we'd be willing to put\n * in front of a reviewer. The trustworthy audit lives server-side, recorded\n * by the IdP every time it processes a grant request — see the planned\n * per-agent audit route on `id.openape.ai`.\n *\n * Stderr here is purely a debugging convenience for the operator running\n * `apes proxy --` interactively. Persist nothing locally.\n */\nexport function writeAudit(entry: AuditEntry): void {\n const grantSuffix = entry.grant_id ? ` grant=${entry.grant_id}` : ''\n console.error(`[audit] ${entry.action} ${entry.method} ${formatTarget(entry)}${grantSuffix}`)\n}\n","import { resolve4, resolve6 } from 'node:dns/promises'\nimport { isIP } from 'node:net'\n\nconst PRIVATE_RANGES_V4 = [\n { prefix: 0x7F000000, mask: 0xFF000000 }, // 127.0.0.0/8\n { prefix: 0x0A000000, mask: 0xFF000000 }, // 10.0.0.0/8\n { prefix: 0xAC100000, mask: 0xFFF00000 }, // 172.16.0.0/12\n { prefix: 0xC0A80000, mask: 0xFFFF0000 }, // 192.168.0.0/16\n { prefix: 0xA9FE0000, mask: 0xFFFF0000 }, // 169.254.0.0/16\n { prefix: 0x00000000, mask: 0xFF000000 }, // 0.0.0.0/8\n]\n\nfunction ipv4ToNumber(ip: string): number {\n const parts = ip.split('.').map(Number)\n return ((parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3]) >>> 0\n}\n\nfunction isPrivateIPv4(ip: string): boolean {\n const num = ipv4ToNumber(ip)\n return PRIVATE_RANGES_V4.some(r => ((num & r.mask) >>> 0) === r.prefix)\n}\n\nfunction isPrivateIPv6(ip: string): boolean {\n const normalized = ip.toLowerCase()\n\n // Loopback ::1\n if (normalized === '::1') return true\n\n // Unspecified ::\n if (normalized === '::') return true\n\n // Link-local fe80::/10\n if (normalized.startsWith('fe8') || normalized.startsWith('fe9')\n || normalized.startsWith('fea') || normalized.startsWith('feb')) {\n return true\n }\n\n // Unique local fd00::/8\n if (normalized.startsWith('fd')) return true\n\n // IPv4-mapped ::ffff:x.x.x.x\n const v4mapped = normalized.match(/^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/)\n if (v4mapped) return isPrivateIPv4(v4mapped[1])\n\n return false\n}\n\nfunction isPrivateIP(ip: string): boolean {\n if (isIP(ip) === 4) return isPrivateIPv4(ip)\n if (isIP(ip) === 6) return isPrivateIPv6(ip)\n return false\n}\n\n/**\n * Check if a hostname resolves to a private or loopback IP.\n * If the hostname is already an IP literal, check directly.\n * Otherwise, resolve via DNS and check all results.\n */\nexport async function isPrivateOrLoopback(hostname: string): Promise<boolean> {\n // Direct IP literal\n if (isIP(hostname)) {\n return isPrivateIP(hostname)\n }\n\n // localhost shortcut\n if (hostname === 'localhost') return true\n\n // DNS resolution — check both A and AAAA records\n try {\n const [v4, v6] = await Promise.allSettled([\n resolve4(hostname),\n resolve6(hostname),\n ])\n const addrs: string[] = []\n if (v4.status === 'fulfilled') addrs.push(...v4.value)\n if (v6.status === 'fulfilled') addrs.push(...v6.value)\n\n if (addrs.length === 0) return true // no records — block to be safe\n return addrs.some(addr => isPrivateIP(addr))\n }\n catch {\n // DNS failure — block to be safe\n return true\n }\n}\n","import type { IncomingMessage } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { connect } from 'node:net'\nimport type { AgentConfig, MultiAgentProxyConfig, ProxyConfig } from './types.js'\nimport { AuthError, verifyAgentAuth } from './auth.js'\nimport { isPrivateOrLoopback } from './ssrf.js'\nimport { writeAudit } from './audit.js'\nimport { evaluateRules } from './matcher.js'\nimport type { GrantsClient } from './grants-client.js'\n\n/**\n * Handle HTTP CONNECT requests for tunneling (used by HTTP_PROXY clients).\n * Flow: Auth → SSRF → host-based rule evaluation (allow / deny / grant_required)\n * → TCP connect → bidirectional pipe.\n *\n * For HTTPS via CONNECT we only see the hostname (the TLS payload is opaque).\n * Rules with `methods` or `path` filters cannot be enforced at CONNECT time and\n * are skipped — they'd only match if the client also carried the same hostname\n * over cleartext-HTTP forward-proxy. This is intentional: there's no honest way\n * to gate a method we can't observe.\n */\nexport async function handleConnect(\n config: MultiAgentProxyConfig,\n grantsClients: Map<string, GrantsClient>,\n req: IncomingMessage,\n clientSocket: Socket,\n _head: Buffer,\n): Promise<void> {\n const target = req.url ?? ''\n const [host, portStr] = target.split(':')\n const port = Number.parseInt(portStr || '443')\n\n if (!host || !port) {\n clientSocket.write('HTTP/1.1 400 Bad Request\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n\n // SYSTEM BYPASS: outbound to any configured IdP host is unconditionally\n // allowed. The proxy itself talks to the IdP for JWT verification + grant\n // approval, and any process inside the proxy boundary may need to call the\n // IdP for `apes login` / `apes whoami` / token-exchange BEFORE it can ever\n // produce a valid Proxy-Authorization JWT. Blocking IdP traffic would\n // either deadlock the grant flow or lock users out of authentication.\n // We therefore skip auth (no JWT yet for first-login), SSRF (local-dev IdPs\n // can live at 127.0.0.1), and policy rules (operator must not be able to\n // override this system invariant).\n const idpHosts = new Set(\n config.agents\n .map((a) => {\n try {\n return new URL(a.idp_url).hostname.toLowerCase()\n }\n catch {\n return ''\n }\n })\n .filter(h => h.length > 0),\n )\n if (idpHosts.has(host.toLowerCase())) {\n writeAudit({\n ts: new Date().toISOString(),\n agent: 'system',\n action: 'allow',\n domain: host,\n method: 'CONNECT',\n path: target,\n rule: 'idp-system-bypass',\n })\n tunnel(host, port, clientSocket)\n return\n }\n\n const mandatoryAuth = config.proxy.mandatory_auth ?? false\n\n // Auth check — CONNECT always requires auth in mandatory mode\n let agentEmail: string | undefined\n try {\n const authHeader = req.headers['proxy-authorization'] as string | undefined\n let identity: { email: string, act: 'agent' } | null = null\n\n for (const agentConf of config.agents) {\n identity = await verifyAgentAuth(\n authHeader ?? null,\n agentConf.idp_url,\n mandatoryAuth && config.agents.length === 1,\n )\n if (identity) break\n }\n\n if (mandatoryAuth && !identity) {\n throw new AuthError('JWT required')\n }\n\n agentEmail = identity?.email\n\n // Verify agent is known\n if (agentEmail) {\n const known = config.agents.find(a => a.email === agentEmail)\n if (!known) {\n clientSocket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n }\n else if (config.agents.length > 1) {\n throw new AuthError('JWT required for multi-agent proxy')\n }\n }\n catch (err) {\n if (err instanceof AuthError) {\n clientSocket.write('HTTP/1.1 401 Unauthorized\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n throw err\n }\n\n // SSRF check\n if (await isPrivateOrLoopback(host)) {\n writeAudit({\n ts: new Date().toISOString(),\n agent: agentEmail ?? config.agents[0]?.email ?? 'unknown',\n action: 'deny',\n domain: host,\n method: 'CONNECT',\n path: target,\n rule: 'ssrf-blocked',\n })\n clientSocket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n\n // Host-based rule evaluation. Pick the agent's config: in single-agent or\n // unauth mode this is just config.agents[0]; in multi-agent mode we use the\n // identity established above.\n const agentConf: AgentConfig | undefined = agentEmail\n ? config.agents.find(a => a.email === agentEmail)\n : config.agents[0]\n if (!agentConf) {\n clientSocket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n')\n clientSocket.destroy()\n return\n }\n const effectiveEmail = agentEmail ?? agentConf.email\n\n const rulesConfig: ProxyConfig = {\n proxy: {\n listen: config.proxy.listen,\n idp_url: agentConf.idp_url,\n agent_email: agentConf.email,\n default_action: config.proxy.default_action,\n },\n allow: agentConf.allow ?? [],\n deny: agentConf.deny ?? [],\n grant_required: agentConf.grant_required ?? [],\n }\n\n // CONNECT carries no method/path beyond host:port. Pass 'CONNECT' as method\n // and '/' as path so rules with method-filters (which we can't enforce here)\n // simply don't match — operator must specify method-less rules to gate\n // HTTPS hosts.\n const action = evaluateRules(rulesConfig, host, 'CONNECT', '/')\n const baseAudit = {\n ts: new Date().toISOString(),\n agent: effectiveEmail,\n domain: host,\n method: 'CONNECT',\n path: target,\n } as const\n\n if (action.type === 'deny') {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'deny-list' })\n const note = action.note ? ` ${action.note}` : ''\n clientSocket.write(`HTTP/1.1 403 Forbidden\\r\\nContent-Type: text/plain\\r\\n\\r\\nBlocked:${note}\\r\\n`)\n clientSocket.destroy()\n return\n }\n\n if (action.type === 'grant_required') {\n const grantsClient = grantsClients.get(agentConf.email)\n if (!grantsClient) {\n writeAudit({ ...baseAudit, action: 'deny', rule: 'grant_required (no client)' })\n clientSocket.write('HTTP/1.1 500 Internal Server Error\\r\\n\\r\\nNo grants client for agent\\r\\n')\n clientSocket.destroy()\n return\n }\n\n // Async-mode (`request-async`) is meaningful for HTTP forward-proxy where\n // the client can re-issue the request after approval. CONNECT clients\n // (curl, gh, …) won't transparently retry on 407 mid-handshake, so we\n // always block the tunnel until the grant resolves. Operator can shorten\n // the wait via per-rule `duration` or via the IdP's grant TTL.\n const startTs = Date.now()\n try {\n const grant = await grantsClient.requestGrant({\n requester: effectiveEmail,\n targetHost: host,\n audience: 'ape-proxy',\n grantType: action.rule.grant_type,\n permissions: action.rule.permissions,\n reason: `CONNECT ${host}:${port}`,\n duration: action.rule.duration,\n })\n const decided = await grantsClient.waitForApproval(grant.id)\n const waitedMs = Date.now() - startTs\n if (decided.status === 'approved') {\n writeAudit({ ...baseAudit, action: 'grant_approved', rule: 'grant_required', grant_id: decided.id, waited_ms: waitedMs })\n // Fall through to the TCP connect below.\n }\n else {\n writeAudit({ ...baseAudit, action: 'grant_denied', rule: 'grant_required', grant_id: decided.id, waited_ms: waitedMs })\n clientSocket.write(`HTTP/1.1 403 Forbidden\\r\\n\\r\\nGrant denied by ${decided.decided_by ?? 'policy'}\\r\\n`)\n clientSocket.destroy()\n return\n }\n }\n catch (err) {\n const msg = err instanceof Error ? err.message : 'Unknown error'\n writeAudit({ ...baseAudit, action: 'grant_timeout', rule: 'grant_required', error: msg })\n clientSocket.write(`HTTP/1.1 504 Gateway Timeout\\r\\n\\r\\nGrant request failed: ${msg}\\r\\n`)\n clientSocket.destroy()\n return\n }\n }\n else {\n // 'allow' — log and continue.\n writeAudit({ ...baseAudit, action: 'allow', rule: 'allow-list' })\n }\n\n tunnel(host, port, clientSocket)\n}\n\n/**\n * Open a TCP socket to host:port and bidirectionally pipe it with the client\n * socket. Used by both the policy-pass path (auth + rules approved) and the\n * IdP system-bypass path (no auth/policy involved).\n */\nfunction tunnel(host: string, port: number, clientSocket: Socket): void {\n const targetSocket = connect(port, host, () => {\n clientSocket.write('HTTP/1.1 200 Connection Established\\r\\n\\r\\n')\n targetSocket.pipe(clientSocket)\n clientSocket.pipe(targetSocket)\n })\n\n targetSocket.on('error', () => {\n clientSocket.write('HTTP/1.1 502 Bad Gateway\\r\\n\\r\\n')\n clientSocket.destroy()\n })\n\n clientSocket.on('error', () => {\n targetSocket.destroy()\n })\n\n clientSocket.on('close', () => targetSocket.destroy())\n targetSocket.on('close', () => clientSocket.destroy())\n}\n"],"mappings":"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","names":["config","hostname","config","port","agentConf","config","agentConf"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openape/proxy",
3
3
  "type": "module",
4
- "version": "0.2.15",
4
+ "version": "0.3.1",
5
5
  "turbo": {
6
6
  "tags": [
7
7
  "publishable"