@gethelio/proxy 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@
6
6
  <meta name="referrer" content="no-referrer" />
7
7
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
8
8
  <title>Helio Dashboard</title>
9
- <script type="module" crossorigin src="/assets/index-DgywE2WQ.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-0ylAcvX3.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/assets/index-DZKoV0Vx.css">
11
11
  </head>
12
12
  <body>
package/dist/index.d.ts CHANGED
@@ -946,7 +946,7 @@ interface AuditTimeBucket {
946
946
  interface AuditAggregateStats {
947
947
  /** Total number of records in the time range. */
948
948
  readonly total: number;
949
- /** Total records that resolved without a block (`block_reason IS NULL`). */
949
+ /** Total records that resolved without a block (`block_reason IS NULL`), excluding drift events. */
950
950
  readonly allowed_total: number;
951
951
  /** Total records that resolved with a block (`block_reason IS NOT NULL`). */
952
952
  readonly blocked_total: number;
package/dist/index.js CHANGED
@@ -3141,6 +3141,7 @@ var GovernedForwarder = class {
3141
3141
  let result;
3142
3142
  let approvalOutcome;
3143
3143
  let approvalWaitMs = 0;
3144
+ let approvalContext;
3144
3145
  let rateLimitResult;
3145
3146
  let spendLimitResult;
3146
3147
  let forwardingError;
@@ -3170,6 +3171,7 @@ var GovernedForwarder = class {
3170
3171
  result = approvalResult.result;
3171
3172
  approvalOutcome = approvalResult.outcome;
3172
3173
  approvalWaitMs = approvalResult.approvalWaitMs;
3174
+ approvalContext = approvalResult.approvalContext;
3173
3175
  }
3174
3176
  } else if (decision.action === "rate_limit") {
3175
3177
  if (!this.rateLimiter) {
@@ -3222,6 +3224,7 @@ var GovernedForwarder = class {
3222
3224
  dependencyResult,
3223
3225
  evidenceBlocked,
3224
3226
  approvalOutcome,
3227
+ approvalContext,
3225
3228
  rateLimitResult,
3226
3229
  spendLimitResult,
3227
3230
  isDryRun,
@@ -3243,6 +3246,16 @@ var GovernedForwarder = class {
3243
3246
  request.signal
3244
3247
  );
3245
3248
  const approvalWaitMs = performance.now() - approvalStart;
3249
+ const ticket = outcome.ticketId ? router.getTicket(outcome.ticketId) : void 0;
3250
+ const denialReason = outcome.status === "denied" && outcome.reason ? outcome.reason : void 0;
3251
+ const approvalContext = outcome.ticketId && (denialReason || ticket?.escalated_at) ? {
3252
+ ticket_id: outcome.ticketId,
3253
+ ...denialReason ? { denial_reason: denialReason } : {},
3254
+ ...ticket?.escalated_at ? {
3255
+ escalated_at: ticket.escalated_at,
3256
+ escalated_to: [...ticket.escalated_to ?? []]
3257
+ } : {}
3258
+ } : void 0;
3246
3259
  let result;
3247
3260
  if (outcome.status === "approved" || outcome.status === "break_glass") {
3248
3261
  if (request.signal?.aborted) {
@@ -3271,7 +3284,7 @@ var GovernedForwarder = class {
3271
3284
  result = makeErrorResult(request, POLICY_DENIED, message, { ...feedback });
3272
3285
  }
3273
3286
  }
3274
- return { result, outcome, approvalWaitMs };
3287
+ return { result, outcome, approvalWaitMs, approvalContext };
3275
3288
  }
3276
3289
  async handleRateLimit(request, decision, toolName) {
3277
3290
  const limiter = this.rateLimiter;
@@ -3451,7 +3464,7 @@ var GovernedForwarder = class {
3451
3464
  wasForwardedUpstream(decision, approvalOutcome, rateLimitResult, spendLimitResult) {
3452
3465
  return decision.action === "allow" || approvalOutcome?.status === "approved" || approvalOutcome?.status === "break_glass" || approvalOutcome?.status === "timeout" && this.approvalRouter?.defaultOnTimeout === "allow" || rateLimitResult?.allowed === true || spendLimitResult?.allowed === true;
3453
3466
  }
3454
- writeAuditRecord(request, timestamp, toolName, toolArguments, decision, result, totalDurationMs, approvalWaitMs, flaggedDestructive, evidenceResult, dependencyResult, evidenceBlocked, approvalOutcome, rateLimitResult, spendLimitResult, isDryRun, forwardingError, drift) {
3467
+ writeAuditRecord(request, timestamp, toolName, toolArguments, decision, result, totalDurationMs, approvalWaitMs, flaggedDestructive, evidenceResult, dependencyResult, evidenceBlocked, approvalOutcome, approvalContext, rateLimitResult, spendLimitResult, isDryRun, forwardingError, drift) {
3455
3468
  if (!this.auditWriter) return;
3456
3469
  const wasForwarded = this.wasForwardedUpstream(
3457
3470
  decision,
@@ -3486,6 +3499,12 @@ var GovernedForwarder = class {
3486
3499
  }
3487
3500
  };
3488
3501
  }
3502
+ if (approvalContext) {
3503
+ evidenceChain = {
3504
+ ...evidenceChain ?? {},
3505
+ approval: { ...approvalContext }
3506
+ };
3507
+ }
3489
3508
  if (rateLimitResult) {
3490
3509
  evidenceChain = {
3491
3510
  ...evidenceChain ?? {},
@@ -4536,6 +4555,7 @@ var EvidenceStore = class _EvidenceStore {
4536
4555
  // src/evidence/api.ts
4537
4556
  import { Hono as Hono5 } from "hono";
4538
4557
  import { bodyLimit } from "hono/body-limit";
4558
+ import { HTTPException } from "hono/http-exception";
4539
4559
  import { z as z5 } from "zod";
4540
4560
 
4541
4561
  // src/auth/bearer.ts
@@ -4736,6 +4756,11 @@ function createSidebandApp(store, options = {}) {
4736
4756
  const app = new Hono5();
4737
4757
  const sdkToken = options.token && options.token.length > 0 ? options.token : void 0;
4738
4758
  const adapterToken = options.adapterToken && options.adapterToken.length > 0 ? options.adapterToken : void 0;
4759
+ app.onError((err, c) => {
4760
+ if (err instanceof HTTPException) return err.getResponse();
4761
+ console.error("[helio] Unhandled sideband API error:", err);
4762
+ return c.json({ error: "Internal server error" }, 500);
4763
+ });
4739
4764
  app.use("*", async (c, next) => {
4740
4765
  const origin = c.req.header("origin");
4741
4766
  if (origin) {
@@ -5113,6 +5138,7 @@ var GovernanceService = class {
5113
5138
  }
5114
5139
  let approvalStatus = null;
5115
5140
  let approvedBy = null;
5141
+ let approvalContext;
5116
5142
  if (entry.approvalTicketId) {
5117
5143
  const ticket = this.getTicketStatus(entry.approvalTicketId);
5118
5144
  const status = ticket?.status;
@@ -5121,6 +5147,16 @@ var GovernanceService = class {
5121
5147
  }
5122
5148
  approvalStatus = status;
5123
5149
  approvedBy = ticket.resolved_by ?? null;
5150
+ if (ticket.denial_reason || ticket.escalated_at) {
5151
+ approvalContext = {
5152
+ ticket_id: entry.approvalTicketId,
5153
+ ...ticket.denial_reason ? { denial_reason: ticket.denial_reason } : {},
5154
+ ...ticket.escalated_at ? {
5155
+ escalated_at: ticket.escalated_at,
5156
+ escalated_to: [...ticket.escalated_to ?? []]
5157
+ } : {}
5158
+ };
5159
+ }
5124
5160
  }
5125
5161
  if (req.actual_amount !== void 0) {
5126
5162
  if (!Number.isFinite(req.actual_amount) || req.actual_amount < 0) {
@@ -5157,6 +5193,7 @@ var GovernanceService = class {
5157
5193
  limitsChain,
5158
5194
  approvalStatus,
5159
5195
  approvedBy,
5196
+ approvalContext,
5160
5197
  upstreamError: req.status === "error" ? req.error ?? "tool call failed" : null,
5161
5198
  upstreamResponse: req.result ?? null,
5162
5199
  upstreamLatencyMs: req.duration_ms ?? null
@@ -5562,6 +5599,9 @@ var GovernanceService = class {
5562
5599
  if (args.sidebandUnreported) {
5563
5600
  evidenceChain = { ...evidenceChain ?? {}, sideband: { unreported: true } };
5564
5601
  }
5602
+ if (args.approvalContext) {
5603
+ evidenceChain = { ...evidenceChain ?? {}, approval: { ...args.approvalContext } };
5604
+ }
5565
5605
  const record = {
5566
5606
  timestamp: args.timestampIso,
5567
5607
  session_id: args.sessionId,
@@ -7231,6 +7271,7 @@ import { readFileSync } from "fs";
7231
7271
  import { join } from "path";
7232
7272
  import { randomUUID as randomUUID6 } from "crypto";
7233
7273
  import { Hono as Hono8 } from "hono";
7274
+ import { HTTPException as HTTPException2 } from "hono/http-exception";
7234
7275
  import { z as z8 } from "zod";
7235
7276
  import { cors } from "hono/cors";
7236
7277
  import { serveStatic } from "@hono/node-server/serve-static";
@@ -7497,6 +7538,16 @@ function shouldSetSecureCookie(url, xForwardedProto) {
7497
7538
  if (xForwardedProto?.toLowerCase() === "https") return true;
7498
7539
  return new URL(url).protocol === "https:";
7499
7540
  }
7541
+ function isPrivateIpv4(host) {
7542
+ const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
7543
+ if (!match) return false;
7544
+ const a = Number(match[1]);
7545
+ const b = Number(match[2]);
7546
+ const c = Number(match[3]);
7547
+ const d = Number(match[4]);
7548
+ if (a > 255 || b > 255 || c > 255 || d > 255) return false;
7549
+ return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
7550
+ }
7500
7551
  function createDashboardAppWithLifecycle(deps, options) {
7501
7552
  const {
7502
7553
  auditStore,
@@ -7510,6 +7561,11 @@ function createDashboardAppWithLifecycle(deps, options) {
7510
7561
  const apiSecret = options?.apiSecret;
7511
7562
  const sessionStore = apiSecret ? new DashboardSessionStore({ secret: apiSecret, ttlMs: SESSION_TTL_MS }) : void 0;
7512
7563
  const app = new Hono8();
7564
+ app.onError((err, c) => {
7565
+ if (err instanceof HTTPException2) return err.getResponse();
7566
+ console.error("[helio] Unhandled dashboard API error:", err);
7567
+ return c.json({ error: "Internal server error" }, 500);
7568
+ });
7513
7569
  app.use(
7514
7570
  "*",
7515
7571
  cors({
@@ -7519,7 +7575,7 @@ function createDashboardAppWithLifecycle(deps, options) {
7519
7575
  const url = new URL(origin);
7520
7576
  const h = url.hostname;
7521
7577
  if (h === "localhost" || h === "127.0.0.1" || h === "0.0.0.0") return origin;
7522
- if (/^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.)/.test(h)) return origin;
7578
+ if (isPrivateIpv4(h)) return origin;
7523
7579
  } catch {
7524
7580
  }
7525
7581
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gethelio/proxy",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Open-source MCP governance proxy for AI agents",
6
6
  "license": "Apache-2.0",