@astrasyncai/verification-gateway 3.10.0 → 3.11.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.
Files changed (77) hide show
  1. package/dist/adapter-interface/interface.d.mts +1 -1
  2. package/dist/adapter-interface/interface.d.ts +1 -1
  3. package/dist/adapters/express.js +1 -1
  4. package/dist/adapters/express.js.map +1 -1
  5. package/dist/adapters/express.mjs +1 -1
  6. package/dist/adapters/express.mjs.map +1 -1
  7. package/dist/adapters/mcp.js +1 -1
  8. package/dist/adapters/mcp.js.map +1 -1
  9. package/dist/adapters/mcp.mjs +1 -1
  10. package/dist/adapters/mcp.mjs.map +1 -1
  11. package/dist/adapters/nextjs.js +1 -1
  12. package/dist/adapters/nextjs.js.map +1 -1
  13. package/dist/adapters/nextjs.mjs +1 -1
  14. package/dist/adapters/nextjs.mjs.map +1 -1
  15. package/dist/adapters/sdk.js +1 -1
  16. package/dist/adapters/sdk.js.map +1 -1
  17. package/dist/adapters/sdk.mjs +1 -1
  18. package/dist/adapters/sdk.mjs.map +1 -1
  19. package/dist/bin/astrasync-claude-hook.js +317 -35
  20. package/dist/bin/astrasync-codex-hook.js +5107 -0
  21. package/dist/bin/astrasync-guard.js +430 -40
  22. package/dist/bin/astrasync.js +453 -41
  23. package/dist/browser/background.js +279 -24
  24. package/dist/browser/background.js.map +1 -1
  25. package/dist/browser/background.mjs +279 -24
  26. package/dist/browser/background.mjs.map +1 -1
  27. package/dist/browser/browser-adapter.d.mts +1 -1
  28. package/dist/browser/browser-adapter.d.ts +1 -1
  29. package/dist/claude-code/claude-code-adapter.d.mts +1 -1
  30. package/dist/claude-code/claude-code-adapter.d.ts +1 -1
  31. package/dist/claude-code/claude-code-adapter.js +9 -1
  32. package/dist/claude-code/claude-code-adapter.js.map +1 -1
  33. package/dist/claude-code/claude-code-adapter.mjs +9 -1
  34. package/dist/claude-code/claude-code-adapter.mjs.map +1 -1
  35. package/dist/cli/index.d.mts +1 -1
  36. package/dist/cli/index.d.ts +1 -1
  37. package/dist/codex/index.d.mts +230 -0
  38. package/dist/codex/index.d.ts +230 -0
  39. package/dist/codex/index.js +5225 -0
  40. package/dist/codex/index.js.map +1 -0
  41. package/dist/codex/index.mjs +5197 -0
  42. package/dist/codex/index.mjs.map +1 -0
  43. package/dist/cursor/cursor-adapter.d.mts +1 -1
  44. package/dist/cursor/cursor-adapter.d.ts +1 -1
  45. package/dist/cursor/extension.d.mts +1 -1
  46. package/dist/cursor/extension.d.ts +1 -1
  47. package/dist/cursor/extension.js +279 -24
  48. package/dist/cursor/extension.js.map +1 -1
  49. package/dist/cursor/extension.mjs +279 -24
  50. package/dist/cursor/extension.mjs.map +1 -1
  51. package/dist/gateway/gateway.d.mts +32 -2
  52. package/dist/gateway/gateway.d.ts +32 -2
  53. package/dist/gateway/gateway.js +279 -24
  54. package/dist/gateway/gateway.js.map +1 -1
  55. package/dist/gateway/gateway.mjs +279 -24
  56. package/dist/gateway/gateway.mjs.map +1 -1
  57. package/dist/git-trigger/git-hooks.d.mts +1 -1
  58. package/dist/git-trigger/git-hooks.d.ts +1 -1
  59. package/dist/index.d.mts +1 -1
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.js +1 -1
  62. package/dist/index.js.map +1 -1
  63. package/dist/index.mjs +1 -1
  64. package/dist/index.mjs.map +1 -1
  65. package/dist/local-evaluator/evaluator.d.mts +1 -1
  66. package/dist/local-evaluator/evaluator.d.ts +1 -1
  67. package/dist/local-evaluator/evaluator.js +165 -7
  68. package/dist/local-evaluator/evaluator.js.map +1 -1
  69. package/dist/local-evaluator/evaluator.mjs +165 -7
  70. package/dist/local-evaluator/evaluator.mjs.map +1 -1
  71. package/dist/{types-D0lCtFQ7.d.mts → types-BV6abBnF.d.mts} +6 -0
  72. package/dist/{types-YbiXYKx1.d.ts → types-C23_uaY8.d.ts} +6 -0
  73. package/dist/verify.js +1 -1
  74. package/dist/verify.js.map +1 -1
  75. package/dist/verify.mjs +1 -1
  76. package/dist/verify.mjs.map +1 -1
  77. package/package.json +8 -2
@@ -282,7 +282,9 @@ var HIGH_RISK_COMMANDS = [
282
282
  "git push",
283
283
  "npm publish",
284
284
  "docker",
285
- "kubectl"
285
+ "kubectl",
286
+ "gh pr create",
287
+ "gh pr merge"
286
288
  ];
287
289
  var SENSITIVE_PATHS = [
288
290
  ".ssh",
@@ -301,6 +303,164 @@ var SENSITIVE_PATHS = [
301
303
  ".pypirc"
302
304
  ];
303
305
 
306
+ // src/local-evaluator/command-matcher.ts
307
+ var WRAPPERS = /* @__PURE__ */ new Set([
308
+ "sudo",
309
+ "doas",
310
+ "env",
311
+ "nohup",
312
+ "time",
313
+ "timeout",
314
+ "nice",
315
+ "ionice",
316
+ "stdbuf",
317
+ "xargs",
318
+ "command",
319
+ "exec",
320
+ "builtin"
321
+ ]);
322
+ var ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;
323
+ var MAX_WRAPPER_HOPS = 4;
324
+ var MAX_RECURSION_DEPTH = 3;
325
+ var MAX_COMMAND_LENGTH = 1e5;
326
+ var LOCAL_HOST_RE = /^(localhost|127\.0\.0\.1|::1|[a-z0-9.-]*\.localhost)$/;
327
+ function buildRiskList(entries) {
328
+ const singleWord = /* @__PURE__ */ new Set();
329
+ const multiWord = [];
330
+ for (const entry of entries) {
331
+ const words = entry.toLowerCase().trim().split(/\s+/);
332
+ if (words.length === 1) singleWord.add(words[0]);
333
+ else multiWord.push(words);
334
+ }
335
+ multiWord.sort((a, b) => b.length - a.length);
336
+ return { singleWord, multiWord };
337
+ }
338
+ function stripHeredocBodies(text) {
339
+ const lines = text.split("\n");
340
+ const kept = [];
341
+ let terminator = null;
342
+ for (const line of lines) {
343
+ if (terminator !== null) {
344
+ if (line.trim() === terminator) terminator = null;
345
+ continue;
346
+ }
347
+ kept.push(line);
348
+ const heredoc = line.match(/<<-?\s*['"]?(\w+)['"]?/);
349
+ if (heredoc) terminator = heredoc[1];
350
+ }
351
+ return kept.join("\n");
352
+ }
353
+ function extractDollarSubstitutions(text, collect) {
354
+ let result = text;
355
+ let start = result.indexOf("$(");
356
+ let iterations = 0;
357
+ while (start !== -1 && iterations < 50) {
358
+ iterations++;
359
+ let depth = 0;
360
+ let end = result.length;
361
+ for (let i = start + 1; i < result.length; i++) {
362
+ if (result[i] === "(") depth++;
363
+ else if (result[i] === ")") {
364
+ depth--;
365
+ if (depth === 0) {
366
+ end = i;
367
+ break;
368
+ }
369
+ }
370
+ }
371
+ collect(result.slice(start + 2, end));
372
+ result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;
373
+ start = result.indexOf("$(");
374
+ }
375
+ return result;
376
+ }
377
+ function extractBacktickSubstitutions(text, collect) {
378
+ if (!text.includes("`")) return text;
379
+ const chunks = text.split("`");
380
+ const kept = [];
381
+ for (let i = 0; i < chunks.length; i++) {
382
+ if (i % 2 === 1) collect(chunks[i]);
383
+ else kept.push(chunks[i]);
384
+ }
385
+ return kept.join(" ");
386
+ }
387
+ function basename(token) {
388
+ const slash = token.lastIndexOf("/");
389
+ return slash === -1 ? token : token.slice(slash + 1);
390
+ }
391
+ function matchSegment(segment, list) {
392
+ const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
393
+ let i = 0;
394
+ let hops = 0;
395
+ while (i < tokens.length) {
396
+ while (i < tokens.length && (tokens[i] === "export" || ASSIGN_RE.test(tokens[i]))) i++;
397
+ if (i >= tokens.length) return null;
398
+ const cmd = basename(tokens[i]);
399
+ const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith("-"))];
400
+ for (const entry of list.multiWord) {
401
+ if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {
402
+ return entry.join(" ");
403
+ }
404
+ }
405
+ if (list.singleWord.has(cmd)) return cmd;
406
+ if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {
407
+ hops++;
408
+ i++;
409
+ while (i < tokens.length && (tokens[i].startsWith("-") || ASSIGN_RE.test(tokens[i]))) i++;
410
+ continue;
411
+ }
412
+ return null;
413
+ }
414
+ return null;
415
+ }
416
+ function collectMatches(command, list, matches, depth) {
417
+ if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;
418
+ let text = command.toLowerCase();
419
+ text = stripHeredocBodies(text);
420
+ const recurse = (inner) => collectMatches(inner, list, matches, depth + 1);
421
+ text = extractDollarSubstitutions(text, recurse);
422
+ text = extractBacktickSubstitutions(text, recurse);
423
+ text = text.replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
424
+ text = text.replace(/(^|\s)#[^\n]*/g, " ");
425
+ const segments = text.split(/\|\||&&|[;|&\n]|[({]/);
426
+ for (const segment of segments) {
427
+ const matched = matchSegment(segment, list);
428
+ if (matched) matches.add(matched);
429
+ }
430
+ }
431
+ function matchHighRiskCommands(command, highRiskCommands = HIGH_RISK_COMMANDS) {
432
+ if (!command) return [];
433
+ const matches = /* @__PURE__ */ new Set();
434
+ collectMatches(command, buildRiskList(highRiskCommands), matches, 0);
435
+ return [...matches];
436
+ }
437
+ function isLocalhostOnlyNetworkCommand(command) {
438
+ const text = command.toLowerCase();
439
+ const tokens = text.split(/\s+/).map((t) => t.replace(/^["'`]+|["'`,;)]+$/g, ""));
440
+ let localCount = 0;
441
+ for (const token of tokens) {
442
+ let host = null;
443
+ if (token.includes("://")) {
444
+ let rest = token.slice(token.indexOf("://") + 3);
445
+ const at = rest.indexOf("@");
446
+ if (at !== -1) rest = rest.slice(at + 1);
447
+ host = rest.split(/[/?#]/)[0];
448
+ } else if (/^[a-z0-9.[\]:-]+:\d+(\/.*)?$/.test(token)) {
449
+ host = token.split("/")[0];
450
+ } else if (/^(localhost|127\.0\.0\.1|\[?::1\]?)(\/.*)?$/.test(token)) {
451
+ host = token.split("/")[0];
452
+ }
453
+ if (host === null) continue;
454
+ const bare = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
455
+ if (LOCAL_HOST_RE.test(bare)) {
456
+ localCount++;
457
+ } else {
458
+ return false;
459
+ }
460
+ }
461
+ return localCount > 0;
462
+ }
463
+
304
464
  // src/local-evaluator/evaluator.ts
305
465
  var LocalEvaluator = class {
306
466
  constructor(policy) {
@@ -451,12 +611,10 @@ var LocalEvaluator = class {
451
611
  return score;
452
612
  }
453
613
  if (context.purpose === "shell.exec" && context.target) {
454
- const targetLower = context.target.toLowerCase();
455
- for (const cmd of HIGH_RISK_COMMANDS) {
456
- if (targetLower.startsWith(cmd) || targetLower.includes(` ${cmd} `) || targetLower.includes(` ${cmd}`)) {
457
- score = Math.max(score, 80);
458
- break;
459
- }
614
+ const matched = matchHighRiskCommands(context.target);
615
+ const localProbeOnly = matched.length > 0 && matched.every((cmd) => cmd === "curl" || cmd === "wget") && isLocalhostOnlyNetworkCommand(context.target);
616
+ if (matched.length > 0 && !localProbeOnly) {
617
+ score = Math.max(score, 80);
460
618
  }
461
619
  }
462
620
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
@@ -3767,7 +3925,7 @@ function getTrustLevel(score) {
3767
3925
  }
3768
3926
 
3769
3927
  // src/version.ts
3770
- var SDK_VERSION = "3.10.0";
3928
+ var SDK_VERSION = "3.11.0";
3771
3929
 
3772
3930
  // src/well-known.ts
3773
3931
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -4285,10 +4443,12 @@ function toGatewayConfig(config) {
4285
4443
  };
4286
4444
  }
4287
4445
  function toVerificationRequest(context, astraId) {
4446
+ const dotIndex = context.purpose.indexOf(".");
4447
+ const dotted = dotIndex > 0;
4288
4448
  return {
4289
4449
  credentials: { astraId },
4290
- purpose: context.purpose,
4291
- action: context.action,
4450
+ purpose: dotted ? context.purpose.slice(0, dotIndex) : context.purpose,
4451
+ action: dotted ? context.purpose : context.action,
4292
4452
  resourceType: context.resourceType,
4293
4453
  resource: context.target,
4294
4454
  transactionValue: context.transactionValue,
@@ -4328,8 +4488,75 @@ async function verifyOnline(config, context, astraId) {
4328
4488
  // src/gateway/modes/hybrid.ts
4329
4489
  import { existsSync, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
4330
4490
  import { dirname } from "path";
4491
+
4492
+ // src/gateway/redact.ts
4493
+ var marker = (type2) => `[REDACTED:${type2}]`;
4494
+ var REDACTION_PATTERNS = [
4495
+ // Multi-line blocks first so later single-line patterns don't shred them.
4496
+ {
4497
+ type: "private-key",
4498
+ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g
4499
+ },
4500
+ // URL userinfo credentials (https://user:pass@, x-access-token:ghp_…@).
4501
+ {
4502
+ type: "url-credentials",
4503
+ re: /([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+:[^\s/@]+@/gi,
4504
+ replacement: `$1${marker("url-credentials")}@`
4505
+ },
4506
+ // Known token formats.
4507
+ { type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,255}\b/g },
4508
+ { type: "github-token", re: /\bgithub_pat_[A-Za-z0-9_]{20,255}\b/g },
4509
+ { type: "astrasync-key", re: /\bkya_[A-Fa-f0-9]{32,128}\b/g },
4510
+ { type: "openai-key", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g },
4511
+ { type: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/g },
4512
+ { type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
4513
+ // Bearer/token headers.
4514
+ {
4515
+ type: "bearer-token",
4516
+ re: /\b(bearer\s+)[a-z0-9._~+/=-]{16,}/gi,
4517
+ replacement: `$1${marker("bearer-token")}`
4518
+ },
4519
+ // Generic KEY=value / key: value assignments with secret-ish names.
4520
+ {
4521
+ type: "assigned-secret",
4522
+ re: /\b((?:password|passwd|secret|token|api[_-]?key|apikey|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[=:]\s*)(['"]?)[^\s'"&;]{8,}\2/gi,
4523
+ replacement: `$1$2${marker("assigned-secret")}$2`
4524
+ }
4525
+ ];
4526
+ function redactSecrets(text) {
4527
+ let result = text;
4528
+ for (const { type: type2, re, replacement } of REDACTION_PATTERNS) {
4529
+ result = result.replace(re, replacement ?? marker(type2));
4530
+ }
4531
+ return result;
4532
+ }
4533
+ function redactDeep(value) {
4534
+ if (typeof value === "string") {
4535
+ return redactSecrets(value);
4536
+ }
4537
+ if (Array.isArray(value)) {
4538
+ return value.map((item) => redactDeep(item));
4539
+ }
4540
+ if (value !== null && typeof value === "object" && !(value instanceof Date)) {
4541
+ const out = {};
4542
+ for (const [k, v] of Object.entries(value)) {
4543
+ out[k] = redactDeep(v);
4544
+ }
4545
+ return out;
4546
+ }
4547
+ return value;
4548
+ }
4549
+
4550
+ // src/gateway/modes/hybrid.ts
4331
4551
  var MAX_QUEUE_SIZE = 1e3;
4332
4552
  var MAX_RETRIES = 3;
4553
+ var FLUSH_TIMEOUT = /* @__PURE__ */ Symbol("flush-timeout");
4554
+ function delay(ms) {
4555
+ return new Promise((resolve) => {
4556
+ const timer = setTimeout(() => resolve(FLUSH_TIMEOUT), ms);
4557
+ if (typeof timer.unref === "function") timer.unref();
4558
+ });
4559
+ }
4333
4560
  var HybridSyncQueue = class {
4334
4561
  constructor(config, persistPath) {
4335
4562
  this.queue = [];
@@ -4350,12 +4577,12 @@ var HybridSyncQueue = class {
4350
4577
  /**
4351
4578
  * Stop periodic sync and flush remaining entries.
4352
4579
  */
4353
- async stop() {
4580
+ async stop(flushOptions = {}) {
4354
4581
  if (this.timer) {
4355
4582
  clearInterval(this.timer);
4356
4583
  this.timer = null;
4357
4584
  }
4358
- await this.flush();
4585
+ await this.flush(flushOptions);
4359
4586
  this.saveToDisk();
4360
4587
  }
4361
4588
  /**
@@ -4367,8 +4594,8 @@ var HybridSyncQueue = class {
4367
4594
  }
4368
4595
  this.queue.push({
4369
4596
  id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
4370
- context,
4371
- decision,
4597
+ context: redactDeep(context),
4598
+ decision: redactDeep(decision),
4372
4599
  timestamp: /* @__PURE__ */ new Date(),
4373
4600
  retryCount: 0,
4374
4601
  status: "pending"
@@ -4378,16 +4605,21 @@ var HybridSyncQueue = class {
4378
4605
  /**
4379
4606
  * Flush pending entries to the API.
4380
4607
  */
4381
- async flush() {
4608
+ async flush(options = {}) {
4382
4609
  if (this.processing) return;
4383
4610
  this.processing = true;
4384
4611
  try {
4385
4612
  const pending = this.queue.filter((e) => e.status === "pending");
4386
- for (const entry of pending) {
4387
- try {
4388
- await verifyOnline(this.config, entry.context);
4613
+ const batch = typeof options.limit === "number" ? pending.slice(0, options.limit) : pending;
4614
+ for (const entry of batch) {
4615
+ const attempt = verifyOnline(this.config, entry.context, this.config.agentId).then(() => "synced").catch(() => "error");
4616
+ const result = options.perEntryTimeoutMs ? await Promise.race([attempt, delay(options.perEntryTimeoutMs)]) : await attempt;
4617
+ if (result === FLUSH_TIMEOUT) {
4618
+ break;
4619
+ }
4620
+ if (result === "synced") {
4389
4621
  entry.status = "synced";
4390
- } catch {
4622
+ } else {
4391
4623
  entry.retryCount++;
4392
4624
  if (entry.retryCount >= MAX_RETRIES) {
4393
4625
  entry.status = "failed";
@@ -4464,8 +4696,22 @@ var TraceLogger = class {
4464
4696
  id: this.generateId(),
4465
4697
  timestamp: /* @__PURE__ */ new Date(),
4466
4698
  type: "evaluation",
4467
- context,
4468
- decision
4699
+ context: redactDeep(context),
4700
+ decision: redactDeep(decision)
4701
+ });
4702
+ }
4703
+ /**
4704
+ * Log a tool call the guard saw but did not govern. One line per skip
4705
+ * keeps coverage holes (unmapped platform tools) visible in the traces
4706
+ * instead of silently indistinguishable from ALLOW.
4707
+ */
4708
+ logSkipped(toolName, reason) {
4709
+ if (!this.enabled) return;
4710
+ this.write({
4711
+ id: this.generateId(),
4712
+ timestamp: /* @__PURE__ */ new Date(),
4713
+ type: "skipped",
4714
+ metadata: { toolName, reason }
4469
4715
  });
4470
4716
  }
4471
4717
  /**
@@ -4582,10 +4828,12 @@ var AstraSyncGateway = class {
4582
4828
  }
4583
4829
  /**
4584
4830
  * Graceful shutdown. Flushes sync queue and cleans up resources.
4831
+ * Stateless callers (hooks) pass bounded flush options so sync never
4832
+ * sits on a tool call's critical path.
4585
4833
  */
4586
- async shutdown() {
4834
+ async shutdown(options) {
4587
4835
  if (this.syncQueue) {
4588
- await this.syncQueue.stop();
4836
+ await this.syncQueue.stop(options?.syncFlush ?? {});
4589
4837
  this.syncQueue = null;
4590
4838
  }
4591
4839
  this.evaluator = null;
@@ -4608,7 +4856,7 @@ var AstraSyncGateway = class {
4608
4856
  let decision;
4609
4857
  switch (this.config.mode) {
4610
4858
  case "online":
4611
- decision = await verifyOnline(this.config, context, astraId);
4859
+ decision = await verifyOnline(this.config, context, astraId ?? this.config.agentId);
4612
4860
  break;
4613
4861
  case "local":
4614
4862
  if (!this.evaluator) throw new Error("Local evaluator not initialized");
@@ -4663,6 +4911,13 @@ var AstraSyncGateway = class {
4663
4911
  getSyncStatus() {
4664
4912
  return this.syncQueue?.getStatus() || null;
4665
4913
  }
4914
+ /**
4915
+ * Record a tool call the calling adapter saw but did not govern
4916
+ * (unmapped tool, skipTools, MCP with governMcp off). Trace-only.
4917
+ */
4918
+ logSkippedTool(toolName, reason) {
4919
+ this.traceLogger?.logSkipped(toolName, reason);
4920
+ }
4666
4921
  };
4667
4922
 
4668
4923
  // src/browser/background.ts