@astrasyncai/verification-gateway 5.16.0 → 5.17.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.
Files changed (94) hide show
  1. package/README.md +94 -18
  2. package/dist/adapters/express.d.mts +1 -1
  3. package/dist/adapters/express.d.ts +1 -1
  4. package/dist/adapters/express.js +33 -5
  5. package/dist/adapters/express.js.map +1 -1
  6. package/dist/adapters/express.mjs +33 -5
  7. package/dist/adapters/express.mjs.map +1 -1
  8. package/dist/adapters/mcp.d.mts +1 -1
  9. package/dist/adapters/mcp.d.ts +1 -1
  10. package/dist/adapters/mcp.js +24 -5
  11. package/dist/adapters/mcp.js.map +1 -1
  12. package/dist/adapters/mcp.mjs +24 -5
  13. package/dist/adapters/mcp.mjs.map +1 -1
  14. package/dist/adapters/nextjs.d.mts +1 -1
  15. package/dist/adapters/nextjs.d.ts +1 -1
  16. package/dist/adapters/nextjs.js +24 -5
  17. package/dist/adapters/nextjs.js.map +1 -1
  18. package/dist/adapters/nextjs.mjs +24 -5
  19. package/dist/adapters/nextjs.mjs.map +1 -1
  20. package/dist/adapters/sdk.d.mts +1 -1
  21. package/dist/adapters/sdk.d.ts +1 -1
  22. package/dist/adapters/sdk.js +5 -1
  23. package/dist/adapters/sdk.js.map +1 -1
  24. package/dist/adapters/sdk.mjs +5 -1
  25. package/dist/adapters/sdk.mjs.map +1 -1
  26. package/dist/agent/index.js +1 -1
  27. package/dist/agent/index.js.map +1 -1
  28. package/dist/agent/index.mjs +1 -1
  29. package/dist/agent/index.mjs.map +1 -1
  30. package/dist/bin/astrasync-claude-hook.js +5 -1
  31. package/dist/bin/astrasync-codex-hook.js +5 -1
  32. package/dist/bin/astrasync-guard.js +447 -25
  33. package/dist/bin/astrasync.js +470 -28
  34. package/dist/browser/background.js +5 -1
  35. package/dist/browser/background.js.map +1 -1
  36. package/dist/browser/background.mjs +5 -1
  37. package/dist/browser/background.mjs.map +1 -1
  38. package/dist/cli/index.js +1 -1
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cli/index.mjs +1 -1
  41. package/dist/cli/index.mjs.map +1 -1
  42. package/dist/codex/index.js +5 -1
  43. package/dist/codex/index.js.map +1 -1
  44. package/dist/codex/index.mjs +5 -1
  45. package/dist/codex/index.mjs.map +1 -1
  46. package/dist/cursor/extension.js +5 -1
  47. package/dist/cursor/extension.js.map +1 -1
  48. package/dist/cursor/extension.mjs +5 -1
  49. package/dist/cursor/extension.mjs.map +1 -1
  50. package/dist/edge-config.d.mts +1 -1
  51. package/dist/edge-config.d.ts +1 -1
  52. package/dist/edge-config.js +1 -1
  53. package/dist/edge-config.js.map +1 -1
  54. package/dist/edge-config.mjs +1 -1
  55. package/dist/edge-config.mjs.map +1 -1
  56. package/dist/edge-core/index.d.mts +1 -1
  57. package/dist/edge-core/index.d.ts +1 -1
  58. package/dist/edge-core/index.js +5 -1
  59. package/dist/edge-core/index.js.map +1 -1
  60. package/dist/edge-core/index.mjs +5 -1
  61. package/dist/edge-core/index.mjs.map +1 -1
  62. package/dist/gateway/gateway.js +5 -1
  63. package/dist/gateway/gateway.js.map +1 -1
  64. package/dist/gateway/gateway.mjs +5 -1
  65. package/dist/gateway/gateway.mjs.map +1 -1
  66. package/dist/git-trigger/git-hooks.d.mts +1 -1
  67. package/dist/git-trigger/git-hooks.d.ts +1 -1
  68. package/dist/index.d.mts +16 -2
  69. package/dist/index.d.ts +16 -2
  70. package/dist/index.js +33 -5
  71. package/dist/index.js.map +1 -1
  72. package/dist/index.mjs +33 -5
  73. package/dist/index.mjs.map +1 -1
  74. package/dist/registration/index.js +1 -1
  75. package/dist/registration/index.js.map +1 -1
  76. package/dist/registration/index.mjs +1 -1
  77. package/dist/registration/index.mjs.map +1 -1
  78. package/dist/transport/index.js +1 -1
  79. package/dist/transport/index.js.map +1 -1
  80. package/dist/transport/index.mjs +1 -1
  81. package/dist/transport/index.mjs.map +1 -1
  82. package/dist/{types-Bm1QvGqI.d.ts → types-DA8LzAOB.d.ts} +6 -0
  83. package/dist/{types-Blz520bR.d.mts → types-DRb_plOh.d.mts} +14 -0
  84. package/dist/{types-Blz520bR.d.ts → types-DRb_plOh.d.ts} +14 -0
  85. package/dist/{types-DBe4hgfT.d.mts → types-vNBvYD7Z.d.mts} +6 -0
  86. package/dist/ui/index.d.mts +1 -1
  87. package/dist/ui/index.d.ts +1 -1
  88. package/dist/verify.d.mts +1 -1
  89. package/dist/verify.d.ts +1 -1
  90. package/dist/verify.js +5 -1
  91. package/dist/verify.js.map +1 -1
  92. package/dist/verify.mjs +5 -1
  93. package/dist/verify.mjs.map +1 -1
  94. package/package.json +1 -1
@@ -3752,7 +3752,7 @@ function getTrustLevel(score) {
3752
3752
  }
3753
3753
 
3754
3754
  // src/version.ts
3755
- var SDK_VERSION = "5.16.0";
3755
+ var SDK_VERSION = "5.17.1";
3756
3756
 
3757
3757
  // src/http.ts
3758
3758
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -3891,6 +3891,7 @@ function getCacheKey(request, counterpartyId) {
3891
3891
  request.enableRuntimeChallenge ? "1" : "0",
3892
3892
  // 5.16.0: route policy changes the verdict (owners / challenge).
3893
3893
  request.routePolicy ? JSON.stringify(request.routePolicy) : "",
3894
+ request.approvalHeldByRoute ? "1" : "0",
3894
3895
  // callerMetadata fields contribute to risk model; include the ones
3895
3896
  // backend reads. sourceIp/userAgent/forwardedFor change per-request
3896
3897
  // so their inclusion effectively forces a re-check for any varying
@@ -4064,6 +4065,7 @@ async function callVerifyAccessAPI(config, request) {
4064
4065
  if (requestData.enableRuntimeChallenge)
4065
4066
  body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
4066
4067
  if (requestData.routePolicy) body.routePolicy = requestData.routePolicy;
4068
+ if (requestData.approvalHeldByRoute) body.approvalHeldByRoute = true;
4067
4069
  if (requestData.createSession) body.createSession = requestData.createSession;
4068
4070
  if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
4069
4071
  if (requestData.counterpartyType) body.counterpartyType = requestData.counterpartyType;
@@ -4395,6 +4397,8 @@ async function verify(config, request, options) {
4395
4397
  } else if (result.recommendation === "step_up_required" && result.identityVerified) {
4396
4398
  result.requiresStepUp = true;
4397
4399
  result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
4400
+ const stepUpFailures = apiResponse.access?.failures;
4401
+ if (stepUpFailures?.length) result.failures = stepUpFailures;
4398
4402
  }
4399
4403
  const escalates = result.requiresStepUp === true || result.requiresApproval === true || result.recommendation === "step_up_required";
4400
4404
  if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny" && !escalates) {
@@ -3751,7 +3751,7 @@ function getTrustLevel(score) {
3751
3751
  }
3752
3752
 
3753
3753
  // src/version.ts
3754
- var SDK_VERSION = "5.16.0";
3754
+ var SDK_VERSION = "5.17.1";
3755
3755
 
3756
3756
  // src/http.ts
3757
3757
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -3890,6 +3890,7 @@ function getCacheKey(request, counterpartyId) {
3890
3890
  request.enableRuntimeChallenge ? "1" : "0",
3891
3891
  // 5.16.0: route policy changes the verdict (owners / challenge).
3892
3892
  request.routePolicy ? JSON.stringify(request.routePolicy) : "",
3893
+ request.approvalHeldByRoute ? "1" : "0",
3893
3894
  // callerMetadata fields contribute to risk model; include the ones
3894
3895
  // backend reads. sourceIp/userAgent/forwardedFor change per-request
3895
3896
  // so their inclusion effectively forces a re-check for any varying
@@ -4063,6 +4064,7 @@ async function callVerifyAccessAPI(config, request) {
4063
4064
  if (requestData.enableRuntimeChallenge)
4064
4065
  body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
4065
4066
  if (requestData.routePolicy) body.routePolicy = requestData.routePolicy;
4067
+ if (requestData.approvalHeldByRoute) body.approvalHeldByRoute = true;
4066
4068
  if (requestData.createSession) body.createSession = requestData.createSession;
4067
4069
  if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
4068
4070
  if (requestData.counterpartyType) body.counterpartyType = requestData.counterpartyType;
@@ -4394,6 +4396,8 @@ async function verify(config, request, options) {
4394
4396
  } else if (result.recommendation === "step_up_required" && result.identityVerified) {
4395
4397
  result.requiresStepUp = true;
4396
4398
  result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
4399
+ const stepUpFailures = apiResponse.access?.failures;
4400
+ if (stepUpFailures?.length) result.failures = stepUpFailures;
4397
4401
  }
4398
4402
  const escalates = result.requiresStepUp === true || result.requiresApproval === true || result.recommendation === "step_up_required";
4399
4403
  if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny" && !escalates) {
@@ -233,9 +233,9 @@ function requireType() {
233
233
  }
234
234
  function Type2(tag, options) {
235
235
  options = options || {};
236
- Object.keys(options).forEach(function(name) {
237
- if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) {
238
- throw new YAMLException2('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.');
236
+ Object.keys(options).forEach(function(name2) {
237
+ if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name2) === -1) {
238
+ throw new YAMLException2('Unknown option "' + name2 + '" is met in definition of "' + tag + '" YAML type.');
239
239
  }
240
240
  });
241
241
  this.options = options;
@@ -268,9 +268,9 @@ function requireSchema() {
268
268
  hasRequiredSchema = 1;
269
269
  const YAMLException2 = requireException();
270
270
  const Type2 = requireType();
271
- function compileList(schema2, name) {
271
+ function compileList(schema2, name2) {
272
272
  const result = [];
273
- schema2[name].forEach(function(currentType) {
273
+ schema2[name2].forEach(function(currentType) {
274
274
  let newIndex = result.length;
275
275
  result.forEach(function(previousType, previousIndex) {
276
276
  if (previousType.tag === currentType.tag && previousType.kind === currentType.kind && previousType.multi === currentType.multi) {
@@ -1179,18 +1179,18 @@ function requireLoader() {
1179
1179
  state.onWarning.call(null, generateError(state, message));
1180
1180
  }
1181
1181
  }
1182
- function storeAnchor(state, name, value) {
1182
+ function storeAnchor(state, name2, value) {
1183
1183
  const transactions = state.anchorMapTransactions;
1184
1184
  if (transactions.length !== 0) {
1185
1185
  const transaction = transactions[transactions.length - 1];
1186
- if (!_hasOwnProperty.call(transaction, name)) {
1187
- transaction[name] = {
1188
- existed: _hasOwnProperty.call(state.anchorMap, name),
1189
- value: state.anchorMap[name]
1186
+ if (!_hasOwnProperty.call(transaction, name2)) {
1187
+ transaction[name2] = {
1188
+ existed: _hasOwnProperty.call(state.anchorMap, name2),
1189
+ value: state.anchorMap[name2]
1190
1190
  };
1191
1191
  }
1192
1192
  }
1193
- state.anchorMap[name] = value;
1193
+ state.anchorMap[name2] = value;
1194
1194
  }
1195
1195
  function beginAnchorTransaction(state) {
1196
1196
  state.anchorMapTransactions.push(/* @__PURE__ */ Object.create(null));
@@ -1202,9 +1202,9 @@ function requireLoader() {
1202
1202
  const parent = transactions[transactions.length - 1];
1203
1203
  const names = Object.keys(transaction);
1204
1204
  for (let index = 0, length = names.length; index < length; index += 1) {
1205
- const name = names[index];
1206
- if (!_hasOwnProperty.call(parent, name)) {
1207
- parent[name] = transaction[name];
1205
+ const name2 = names[index];
1206
+ if (!_hasOwnProperty.call(parent, name2)) {
1207
+ parent[name2] = transaction[name2];
1208
1208
  }
1209
1209
  }
1210
1210
  }
@@ -1245,7 +1245,7 @@ function requireLoader() {
1245
1245
  state.result = snapshot.result;
1246
1246
  }
1247
1247
  const directiveHandlers = {
1248
- YAML: function handleYamlDirective(state, name, args) {
1248
+ YAML: function handleYamlDirective(state, name2, args) {
1249
1249
  if (state.version !== null) {
1250
1250
  throwError(state, "duplication of %YAML directive");
1251
1251
  }
@@ -1267,7 +1267,7 @@ function requireLoader() {
1267
1267
  throwWarning(state, "unsupported YAML version of the document");
1268
1268
  }
1269
1269
  },
1270
- TAG: function handleTagDirective(state, name, args) {
1270
+ TAG: function handleTagDirective(state, name2, args) {
1271
1271
  let prefix;
1272
1272
  if (args.length !== 2) {
1273
1273
  throwError(state, "TAG directive accepts exactly two arguments");
@@ -3573,10 +3573,10 @@ ${category.label}
3573
3573
  scope.blockedResources = additionalBlocked;
3574
3574
  }
3575
3575
  this.print("");
3576
- const name = (await this.ask("Policy name [default: local-guard]: ")).trim() || "local-guard";
3576
+ const name2 = (await this.ask("Policy name [default: local-guard]: ")).trim() || "local-guard";
3577
3577
  const policy = {
3578
3578
  version: "1.0",
3579
- name,
3579
+ name: name2,
3580
3580
  purposes,
3581
3581
  ...scope && { scope },
3582
3582
  ...limits && { limits },
@@ -3713,7 +3713,7 @@ Policy saved to ${outputPath}`);
3713
3713
  };
3714
3714
 
3715
3715
  // src/version.ts
3716
- var SDK_VERSION = "5.16.0";
3716
+ var SDK_VERSION = "5.17.1";
3717
3717
 
3718
3718
  // src/http.ts
3719
3719
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -3725,10 +3725,10 @@ function withUserAgent(initHeaders) {
3725
3725
  return copy;
3726
3726
  }
3727
3727
  if (Array.isArray(initHeaders)) {
3728
- const has2 = initHeaders.some(([name]) => name.toLowerCase() === "user-agent");
3728
+ const has2 = initHeaders.some(([name2]) => name2.toLowerCase() === "user-agent");
3729
3729
  return has2 ? initHeaders : [...initHeaders, ["user-agent", SDK_USER_AGENT]];
3730
3730
  }
3731
- const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === "user-agent");
3731
+ const has = Object.keys(initHeaders).some((name2) => name2.toLowerCase() === "user-agent");
3732
3732
  return has ? initHeaders : { "user-agent": SDK_USER_AGENT, ...initHeaders };
3733
3733
  }
3734
3734
  var sdkFetch = (input, init) => fetch(input, { ...init, headers: withUserAgent(init?.headers) });
@@ -4303,8 +4303,8 @@ function extractBacktickSubstitutions(text, collect) {
4303
4303
  }
4304
4304
  function basename(token) {
4305
4305
  const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
4306
- const name = slash === -1 ? token : token.slice(slash + 1);
4307
- return name.replace(/\.(exe|cmd|bat|ps1)$/i, "");
4306
+ const name2 = slash === -1 ? token : token.slice(slash + 1);
4307
+ return name2.replace(/\.(exe|cmd|bat|ps1)$/i, "");
4308
4308
  }
4309
4309
  function matchSegment(segment, list) {
4310
4310
  const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
@@ -4756,6 +4756,7 @@ function getCacheKey(request2, counterpartyId) {
4756
4756
  request2.enableRuntimeChallenge ? "1" : "0",
4757
4757
  // 5.16.0: route policy changes the verdict (owners / challenge).
4758
4758
  request2.routePolicy ? JSON.stringify(request2.routePolicy) : "",
4759
+ request2.approvalHeldByRoute ? "1" : "0",
4759
4760
  // callerMetadata fields contribute to risk model; include the ones
4760
4761
  // backend reads. sourceIp/userAgent/forwardedFor change per-request
4761
4762
  // so their inclusion effectively forces a re-check for any varying
@@ -4929,6 +4930,7 @@ async function callVerifyAccessAPI(config, request2) {
4929
4930
  if (requestData.enableRuntimeChallenge)
4930
4931
  body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
4931
4932
  if (requestData.routePolicy) body.routePolicy = requestData.routePolicy;
4933
+ if (requestData.approvalHeldByRoute) body.approvalHeldByRoute = true;
4932
4934
  if (requestData.createSession) body.createSession = requestData.createSession;
4933
4935
  if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
4934
4936
  if (requestData.counterpartyType) body.counterpartyType = requestData.counterpartyType;
@@ -5260,6 +5262,8 @@ async function verify(config, request2, options) {
5260
5262
  } else if (result.recommendation === "step_up_required" && result.identityVerified) {
5261
5263
  result.requiresStepUp = true;
5262
5264
  result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
5265
+ const stepUpFailures = apiResponse.access?.failures;
5266
+ if (stepUpFailures?.length) result.failures = stepUpFailures;
5263
5267
  }
5264
5268
  const escalates = result.requiresStepUp === true || result.requiresApproval === true || result.recommendation === "step_up_required";
5265
5269
  if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny" && !escalates) {
@@ -6429,7 +6433,7 @@ function ensureGuardSelfProtection(policyPath) {
6429
6433
  (0, import_fs5.writeFileSync)(policyPath, toYaml(parsed.policy), "utf-8");
6430
6434
  return true;
6431
6435
  }
6432
- function buildDefaultClaudeCodePolicy(name = "claude-code-local-guard") {
6436
+ function buildDefaultClaudeCodePolicy(name2 = "claude-code-local-guard") {
6433
6437
  const recommendedCommands = DEFAULT_BLOCKED_COMMANDS.filter((c) => c.recommended).map(
6434
6438
  (c) => c.pattern
6435
6439
  );
@@ -6437,7 +6441,7 @@ function buildDefaultClaudeCodePolicy(name = "claude-code-local-guard") {
6437
6441
  const permissive = RISK_PRESETS.find((p) => p.name === "permissive");
6438
6442
  return {
6439
6443
  version: "1.0",
6440
- name,
6444
+ name: name2,
6441
6445
  description: "AstraSync Local Guard for Claude Code \u2014 default coding-agent policy. Edit freely; the hook re-reads this file on every tool call.",
6442
6446
  purposes: [
6443
6447
  {
@@ -7066,6 +7070,393 @@ function runGitCredential(action, input, git = realGit) {
7066
7070
  });
7067
7071
  }
7068
7072
 
7073
+ // src/fleet/browse-proxy.ts
7074
+ var import_node_http = __toESM(require("http"));
7075
+ var import_node_https = __toESM(require("https"));
7076
+ var import_node_net = __toESM(require("net"));
7077
+ var import_node_tls = __toESM(require("tls"));
7078
+
7079
+ // src/fleet/local-cert.ts
7080
+ var import_node_crypto5 = __toESM(require("crypto"));
7081
+ function len(n) {
7082
+ if (n < 128) return Buffer.from([n]);
7083
+ const bytes = [];
7084
+ while (n > 0) {
7085
+ bytes.unshift(n & 255);
7086
+ n >>= 8;
7087
+ }
7088
+ return Buffer.from([128 | bytes.length, ...bytes]);
7089
+ }
7090
+ var tlv = (tag, body) => Buffer.concat([Buffer.from([tag]), len(body.length), body]);
7091
+ var seq2 = (...parts) => tlv(48, Buffer.concat(parts));
7092
+ var set2 = (...parts) => tlv(49, Buffer.concat(parts));
7093
+ var ctx = (n, body) => tlv(160 + n, body);
7094
+ function oid(dotted) {
7095
+ const parts = dotted.split(".").map(Number);
7096
+ const out = [parts[0] * 40 + parts[1]];
7097
+ for (const p of parts.slice(2)) {
7098
+ const enc = [p & 127];
7099
+ let v = p >> 7;
7100
+ while (v > 0) {
7101
+ enc.unshift(v & 127 | 128);
7102
+ v >>= 7;
7103
+ }
7104
+ out.push(...enc);
7105
+ }
7106
+ return tlv(6, Buffer.from(out));
7107
+ }
7108
+ function integer(buf) {
7109
+ const b = buf[0] & 128 ? Buffer.concat([Buffer.from([0]), buf]) : buf;
7110
+ return tlv(2, b);
7111
+ }
7112
+ function utcTime(d) {
7113
+ const s = d.toISOString().replace(/[-:T]/g, "").slice(2, 14) + "Z";
7114
+ return tlv(23, Buffer.from(s, "ascii"));
7115
+ }
7116
+ var ECDSA_SHA256 = seq2(oid("1.2.840.10045.4.3.2"));
7117
+ function name(cn) {
7118
+ return seq2(set2(seq2(oid("2.5.4.3"), tlv(12, Buffer.from(cn, "utf8")))));
7119
+ }
7120
+ function createLocalCert(hosts, days = 30) {
7121
+ const { privateKey, publicKey } = import_node_crypto5.default.generateKeyPairSync("ec", { namedCurve: "P-256" });
7122
+ const spki = publicKey.export({ type: "spki", format: "der" });
7123
+ const now = Date.now();
7124
+ const san = seq2(...hosts.map((h) => tlv(130, Buffer.from(h, "ascii"))));
7125
+ const extensions = ctx(
7126
+ 3,
7127
+ seq2(
7128
+ seq2(oid("2.5.29.17"), tlv(4, san)),
7129
+ // subjectAltName
7130
+ seq2(oid("2.5.29.19"), tlv(4, seq2()))
7131
+ // basicConstraints: not a CA
7132
+ )
7133
+ );
7134
+ const subject = name("AstraSync Local Guard (browse)");
7135
+ const tbs = seq2(
7136
+ ctx(0, integer(Buffer.from([2]))),
7137
+ // v3
7138
+ integer(import_node_crypto5.default.randomBytes(16)),
7139
+ ECDSA_SHA256,
7140
+ subject,
7141
+ seq2(utcTime(new Date(now - 6e4)), utcTime(new Date(now + days * 864e5))),
7142
+ subject,
7143
+ spki,
7144
+ extensions
7145
+ );
7146
+ const signature = import_node_crypto5.default.sign("sha256", tbs, privateKey);
7147
+ const der = seq2(tbs, ECDSA_SHA256, tlv(3, Buffer.concat([Buffer.from([0]), signature])));
7148
+ const b64 = der.toString("base64").match(/.{1,64}/g).join("\n");
7149
+ return {
7150
+ cert: `-----BEGIN CERTIFICATE-----
7151
+ ${b64}
7152
+ -----END CERTIFICATE-----
7153
+ `,
7154
+ key: privateKey.export({ type: "pkcs8", format: "pem" }).toString()
7155
+ };
7156
+ }
7157
+
7158
+ // src/fleet/browse-proxy.ts
7159
+ var HOP_BY_HOP = /* @__PURE__ */ new Set([
7160
+ "connection",
7161
+ "proxy-connection",
7162
+ "keep-alive",
7163
+ "transfer-encoding",
7164
+ "upgrade",
7165
+ "te",
7166
+ "trailer",
7167
+ "proxy-authorization"
7168
+ ]);
7169
+ function cleanHeaders(h) {
7170
+ const out = {};
7171
+ for (const [k, v] of Object.entries(h)) if (!HOP_BY_HOP.has(k) && v !== void 0) out[k] = v;
7172
+ return out;
7173
+ }
7174
+ async function startBrowseProxy(opts) {
7175
+ const hosts = new Set(opts.hosts.map((h) => h.toLowerCase()));
7176
+ const deviceHeader = `Device ${opts.deviceCredential}`;
7177
+ const { cert, key } = createLocalCert([...hosts]);
7178
+ function forward(req, res, target) {
7179
+ const headers = cleanHeaders(req.headers);
7180
+ headers["x-astra-device"] = deviceHeader;
7181
+ const mod = target.protocol === "https:" ? import_node_https.default : import_node_http.default;
7182
+ const upstream = mod.request(
7183
+ {
7184
+ host: target.host,
7185
+ port: target.port,
7186
+ method: req.method,
7187
+ path: target.path,
7188
+ headers,
7189
+ servername: target.protocol === "https:" ? String(req.headers.host ?? target.host).split(":")[0] : void 0,
7190
+ rejectUnauthorized: target.rejectUnauthorized ?? true
7191
+ },
7192
+ (up) => {
7193
+ res.writeHead(up.statusCode ?? 502, cleanHeaders(up.headers));
7194
+ up.pipe(res);
7195
+ }
7196
+ );
7197
+ upstream.on("error", () => {
7198
+ if (!res.headersSent) res.writeHead(502, { "content-type": "text/plain" });
7199
+ res.end("AstraSync browse proxy: upstream unreachable");
7200
+ });
7201
+ req.pipe(upstream);
7202
+ }
7203
+ function forwardUpgrade(req, socket, head, target) {
7204
+ const headers = { ...req.headers };
7205
+ delete headers["proxy-connection"];
7206
+ delete headers["proxy-authorization"];
7207
+ delete headers["x-astra-device"];
7208
+ if (target.addDevice) headers["x-astra-device"] = deviceHeader;
7209
+ const lines = [`${req.method} ${target.path} HTTP/1.1`];
7210
+ for (const [k, v] of Object.entries(headers)) {
7211
+ if (v === void 0) continue;
7212
+ for (const one of Array.isArray(v) ? v : [v]) lines.push(`${k}: ${one}`);
7213
+ }
7214
+ const onConnect = () => {
7215
+ upstream.write(lines.join("\r\n") + "\r\n\r\n");
7216
+ if (head.length) upstream.write(head);
7217
+ upstream.pipe(socket);
7218
+ socket.pipe(upstream);
7219
+ };
7220
+ const upstream = target.protocol === "https:" ? import_node_tls.default.connect(
7221
+ {
7222
+ host: target.host,
7223
+ port: target.port,
7224
+ servername: String(req.headers.host ?? target.host).split(":")[0],
7225
+ rejectUnauthorized: target.rejectUnauthorized ?? true
7226
+ },
7227
+ onConnect
7228
+ ) : import_node_net.default.connect(target.port, target.host, onConnect);
7229
+ upstream.on("error", () => socket.destroy());
7230
+ socket.on("error", () => upstream.destroy());
7231
+ }
7232
+ const inner = import_node_http.default.createServer((req, res) => {
7233
+ const hostHeader = String(req.headers.host ?? "");
7234
+ const [h, p] = hostHeader.split(":");
7235
+ const port2 = Number(p) || 443;
7236
+ const up = opts.upstreamFor?.(h, port2) ?? { host: h, port: port2 };
7237
+ forward(req, res, {
7238
+ protocol: "https:",
7239
+ host: up.host,
7240
+ port: up.port,
7241
+ path: req.url ?? "/",
7242
+ rejectUnauthorized: up.rejectUnauthorized
7243
+ });
7244
+ });
7245
+ const plainInner = import_node_http.default.createServer((req, res) => {
7246
+ const [h, p] = String(req.headers.host ?? "").split(":");
7247
+ forward(req, res, { protocol: "http:", host: h, port: Number(p) || 80, path: req.url ?? "/" });
7248
+ });
7249
+ plainInner.on("upgrade", (req, socket, head) => {
7250
+ const [h, p] = String(req.headers.host ?? "").split(":");
7251
+ forwardUpgrade(req, socket, head, {
7252
+ protocol: "http:",
7253
+ host: h,
7254
+ port: Number(p) || 80,
7255
+ path: req.url ?? "/",
7256
+ addDevice: true
7257
+ });
7258
+ });
7259
+ const server = import_node_http.default.createServer((req, res) => {
7260
+ let url;
7261
+ try {
7262
+ url = new URL(req.url ?? "");
7263
+ } catch {
7264
+ res.writeHead(400).end("absolute URL required");
7265
+ return;
7266
+ }
7267
+ if (hosts.has(url.hostname.toLowerCase())) {
7268
+ forward(req, res, {
7269
+ protocol: "http:",
7270
+ host: url.hostname,
7271
+ port: Number(url.port) || 80,
7272
+ path: `${url.pathname}${url.search}`
7273
+ });
7274
+ return;
7275
+ }
7276
+ const upstream = import_node_http.default.request(
7277
+ {
7278
+ host: url.hostname,
7279
+ port: Number(url.port) || 80,
7280
+ method: req.method,
7281
+ path: `${url.pathname}${url.search}`,
7282
+ headers: cleanHeaders(req.headers)
7283
+ },
7284
+ (up) => {
7285
+ res.writeHead(up.statusCode ?? 502, cleanHeaders(up.headers));
7286
+ up.pipe(res);
7287
+ }
7288
+ );
7289
+ upstream.on("error", () => res.writeHead(502).end());
7290
+ req.pipe(upstream);
7291
+ });
7292
+ inner.on("upgrade", (req, socket, head) => {
7293
+ const [h, p] = String(req.headers.host ?? "").split(":");
7294
+ const port2 = Number(p) || 443;
7295
+ const up = opts.upstreamFor?.(h, port2) ?? { host: h, port: port2 };
7296
+ forwardUpgrade(req, socket, head, {
7297
+ protocol: "https:",
7298
+ host: up.host,
7299
+ port: up.port,
7300
+ path: req.url ?? "/",
7301
+ addDevice: true,
7302
+ rejectUnauthorized: up.rejectUnauthorized
7303
+ });
7304
+ });
7305
+ server.on("upgrade", (req, socket, head) => {
7306
+ let url;
7307
+ try {
7308
+ url = new URL(req.url ?? "");
7309
+ } catch {
7310
+ socket.destroy();
7311
+ return;
7312
+ }
7313
+ forwardUpgrade(req, socket, head, {
7314
+ protocol: "http:",
7315
+ host: url.hostname,
7316
+ port: Number(url.port) || 80,
7317
+ path: `${url.pathname}${url.search}`,
7318
+ addDevice: hosts.has(url.hostname.toLowerCase())
7319
+ });
7320
+ });
7321
+ server.on("connect", (req, socket, head) => {
7322
+ const [host, portStr] = String(req.url ?? "").split(":");
7323
+ const port2 = Number(portStr) || 443;
7324
+ if (hosts.has(host.toLowerCase())) {
7325
+ socket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
7326
+ const start = (first) => {
7327
+ const s = socket;
7328
+ s.pause();
7329
+ s.unshift(first);
7330
+ if (first[0] === 22) {
7331
+ const tlsSocket = new import_node_tls.default.TLSSocket(s, {
7332
+ isServer: true,
7333
+ secureContext: import_node_tls.default.createSecureContext({ cert, key })
7334
+ });
7335
+ inner.emit("connection", tlsSocket);
7336
+ tlsSocket.on("error", () => socket.destroy());
7337
+ } else {
7338
+ plainInner.emit("connection", s);
7339
+ s.resume();
7340
+ }
7341
+ };
7342
+ if (head.length) start(head);
7343
+ else socket.once("data", (chunk) => start(chunk));
7344
+ return;
7345
+ }
7346
+ const upstream = import_node_net.default.connect(port2, host, () => {
7347
+ socket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
7348
+ if (head.length) upstream.write(head);
7349
+ upstream.pipe(socket);
7350
+ socket.pipe(upstream);
7351
+ });
7352
+ upstream.on("error", () => socket.destroy());
7353
+ socket.on("error", () => upstream.destroy());
7354
+ });
7355
+ await new Promise((resolve) => server.listen(opts.port ?? 0, "127.0.0.1", resolve));
7356
+ const port = server.address().port;
7357
+ return {
7358
+ port,
7359
+ close: () => new Promise((resolve) => {
7360
+ inner.close();
7361
+ plainInner.close();
7362
+ server.close(() => resolve());
7363
+ server.closeAllConnections?.();
7364
+ })
7365
+ };
7366
+ }
7367
+
7368
+ // src/fleet/browse.ts
7369
+ function refusal(body) {
7370
+ const e = body.error;
7371
+ if (e && typeof e === "object")
7372
+ return { code: e.code ?? body.code, message: e.message ?? body.message };
7373
+ return { code: body.code, message: body.message ?? (typeof e === "string" ? e : void 0) };
7374
+ }
7375
+ async function runBrowse(paths, opts) {
7376
+ const log = opts.log ?? ((l) => console.log(l));
7377
+ const f = opts.fetchImpl ?? fetch;
7378
+ const loaded = loadEnrolment(paths);
7379
+ if (!loaded || !("enrolment" in loaded)) {
7380
+ throw new Error("This machine is not enrolled. Run: astrasync guard enroll <token>");
7381
+ }
7382
+ const { credential, apiBaseUrl } = loaded.enrolment;
7383
+ if (!opts.checkpoint || !opts.slug) throw new Error("--checkpoint and --slug are required");
7384
+ const appOrigin = new URL(apiBaseUrl).origin;
7385
+ const hosts = [new URL(apiBaseUrl).hostname, ...opts.extraHosts ?? []];
7386
+ const device = `Device ${credential}`;
7387
+ const proxy = await startBrowseProxy({ hosts, deviceCredential: credential, port: opts.port });
7388
+ try {
7389
+ const base2 = opts.checkpoint.replace(/\/+$/, "");
7390
+ const signin = await f(
7391
+ `${base2}/a/${encodeURIComponent(opts.slug)}/h/${encodeURIComponent(opts.upstream ?? "astrasync-api")}/agent-sessions`,
7392
+ {
7393
+ method: "POST",
7394
+ headers: { authorization: device, "content-type": "application/json" },
7395
+ body: "{}"
7396
+ }
7397
+ );
7398
+ const body = await signin.json().catch(() => ({}));
7399
+ if (signin.status !== 201 && signin.status !== 202) {
7400
+ const r = refusal(body);
7401
+ throw new Error(
7402
+ `Sign-in refused (${signin.status}${r.code ? ` ${r.code}` : ""}): ${r.message ?? "no detail"}`
7403
+ );
7404
+ }
7405
+ const sessionId = String(body.data?.sessionId ?? "");
7406
+ let code = body.data?.code;
7407
+ if (!code) {
7408
+ log("Waiting for the owner to approve this sign-in with their passkey\u2026");
7409
+ const deadline = Date.now() + (opts.waitMs ?? 10 * 6e4);
7410
+ let failures = 0;
7411
+ while (!code) {
7412
+ if (Date.now() > deadline) throw new Error("The owner did not approve in time.");
7413
+ await new Promise((r) => setTimeout(r, opts.pollIntervalMs ?? 3e3));
7414
+ let poll;
7415
+ try {
7416
+ poll = await f(`${apiBaseUrl.replace(/\/+$/, "")}/agent-sessions/${sessionId}/status`, {
7417
+ headers: { "x-astra-device": device }
7418
+ });
7419
+ } catch (err) {
7420
+ if (++failures >= 3)
7421
+ throw new Error(`Couldn't check the sign-in status: ${err.message}`);
7422
+ continue;
7423
+ }
7424
+ const p = await poll.json().catch(() => ({}));
7425
+ if (!poll.ok) {
7426
+ const r = refusal(p);
7427
+ const why = `(${poll.status}${r.code ? ` ${r.code}` : ""}): ${r.message ?? "no detail"}`;
7428
+ if (poll.status === 401 || poll.status === 403 || poll.status === 404)
7429
+ throw new Error(`Sign-in status check refused ${why}`);
7430
+ if (++failures >= 3) throw new Error(`Couldn't check the sign-in status ${why}`);
7431
+ continue;
7432
+ }
7433
+ failures = 0;
7434
+ const status = p.data?.status;
7435
+ if (status === "approved") code = p.data?.code;
7436
+ else if (status === "denied" || status === "ended")
7437
+ throw new Error(`Sign-in ${status === "denied" ? "was not approved" : "ended"}.`);
7438
+ }
7439
+ }
7440
+ const openUrl = `${appOrigin}/agent-session#code=${code}`;
7441
+ log("");
7442
+ log(`Signed in. Open this once (within 2 minutes), through the proxy:`);
7443
+ log(` ${openUrl}`);
7444
+ log("");
7445
+ log(
7446
+ `Device proxy: http://127.0.0.1:${proxy.port} (adds this machine's credential for ${hosts.join(", ")})`
7447
+ );
7448
+ log("Playwright:");
7449
+ log(
7450
+ ` chromium.launch({ proxy: { server: 'http://127.0.0.1:${proxy.port}' } }) + newContext({ ignoreHTTPSErrors: true })`
7451
+ );
7452
+ log("Requests that skip the proxy end the session. Ctrl-C stops the proxy.");
7453
+ return { proxy, openUrl, sessionId };
7454
+ } catch (err) {
7455
+ await proxy.close();
7456
+ throw err;
7457
+ }
7458
+ }
7459
+
7069
7460
  // src/cli/guard.ts
7070
7461
  var DEFAULT_POLICY_PATH = "./local-policy.yaml";
7071
7462
  var DEFAULT_CONFIG_DIR = ".astrasync";
@@ -7601,6 +7992,28 @@ async function cmdFleetStatus() {
7601
7992
  const status = await getFleetStatus(fleetPaths());
7602
7993
  for (const line of formatFleetStatus(status)) console.log(line);
7603
7994
  }
7995
+ async function cmdBrowse() {
7996
+ try {
7997
+ const port = flagValue("--port");
7998
+ const { proxy } = await runBrowse(fleetPaths(), {
7999
+ checkpoint: flagValue("--checkpoint") ?? "",
8000
+ slug: flagValue("--slug") ?? "",
8001
+ upstream: flagValue("--upstream"),
8002
+ port: port ? Number(port) : void 0,
8003
+ extraHosts: flagValue("--host") ? [flagValue("--host")] : []
8004
+ });
8005
+ const stop = async () => {
8006
+ await proxy.close();
8007
+ process.exit(0);
8008
+ };
8009
+ process.on("SIGINT", stop);
8010
+ process.on("SIGTERM", stop);
8011
+ await new Promise(() => void 0);
8012
+ } catch (err) {
8013
+ console.error(`browse failed: ${err instanceof Error ? err.message : String(err)}`);
8014
+ process.exit(1);
8015
+ }
8016
+ }
7604
8017
  async function cmdGitRoute() {
7605
8018
  try {
7606
8019
  if (process.argv.includes("--undo")) {
@@ -7698,6 +8111,9 @@ async function main() {
7698
8111
  case "git-route":
7699
8112
  await cmdGitRoute();
7700
8113
  break;
8114
+ case "browse":
8115
+ await cmdBrowse();
8116
+ break;
7701
8117
  case "git-credential":
7702
8118
  await cmdGitCredential();
7703
8119
  break;
@@ -7721,6 +8137,12 @@ async function main() {
7721
8137
  console.log(" install-codex Install the Local Guard hook for OpenAI Codex CLI");
7722
8138
  console.log(" (same flags; policy shared with Claude Code)");
7723
8139
  console.log(" uninstall-codex Remove the Codex hook (--project for project scope)");
8140
+ console.log(
8141
+ " browse Sign an agent in to your dashboard from this machine and"
8142
+ );
8143
+ console.log(
8144
+ " run its device proxy (--checkpoint <url> --slug <agent>)"
8145
+ );
7724
8146
  console.log(" enroll <token> Enrol this machine in your organisation fleet");
7725
8147
  console.log(" (--api-base-url <url>, --system for machine scope)");
7726
8148
  console.log(" fleet-status Show fleet enrolment, signed policy and heartbeat");