@astrasyncai/verification-gateway 3.10.0 → 3.10.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 (66) 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 +273 -25
  20. package/dist/bin/astrasync-guard.js +255 -23
  21. package/dist/bin/astrasync.js +270 -24
  22. package/dist/browser/background.js +254 -22
  23. package/dist/browser/background.js.map +1 -1
  24. package/dist/browser/background.mjs +254 -22
  25. package/dist/browser/background.mjs.map +1 -1
  26. package/dist/browser/browser-adapter.d.mts +1 -1
  27. package/dist/browser/browser-adapter.d.ts +1 -1
  28. package/dist/claude-code/claude-code-adapter.d.mts +1 -1
  29. package/dist/claude-code/claude-code-adapter.d.ts +1 -1
  30. package/dist/cli/index.d.mts +1 -1
  31. package/dist/cli/index.d.ts +1 -1
  32. package/dist/cursor/cursor-adapter.d.mts +1 -1
  33. package/dist/cursor/cursor-adapter.d.ts +1 -1
  34. package/dist/cursor/extension.d.mts +1 -1
  35. package/dist/cursor/extension.d.ts +1 -1
  36. package/dist/cursor/extension.js +254 -22
  37. package/dist/cursor/extension.js.map +1 -1
  38. package/dist/cursor/extension.mjs +254 -22
  39. package/dist/cursor/extension.mjs.map +1 -1
  40. package/dist/gateway/gateway.d.mts +27 -2
  41. package/dist/gateway/gateway.d.ts +27 -2
  42. package/dist/gateway/gateway.js +254 -22
  43. package/dist/gateway/gateway.js.map +1 -1
  44. package/dist/gateway/gateway.mjs +254 -22
  45. package/dist/gateway/gateway.mjs.map +1 -1
  46. package/dist/git-trigger/git-hooks.d.mts +1 -1
  47. package/dist/git-trigger/git-hooks.d.ts +1 -1
  48. package/dist/index.d.mts +1 -1
  49. package/dist/index.d.ts +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/index.mjs +1 -1
  53. package/dist/index.mjs.map +1 -1
  54. package/dist/local-evaluator/evaluator.d.mts +1 -1
  55. package/dist/local-evaluator/evaluator.d.ts +1 -1
  56. package/dist/local-evaluator/evaluator.js +165 -7
  57. package/dist/local-evaluator/evaluator.js.map +1 -1
  58. package/dist/local-evaluator/evaluator.mjs +165 -7
  59. package/dist/local-evaluator/evaluator.mjs.map +1 -1
  60. package/dist/{types-D0lCtFQ7.d.mts → types-BV6abBnF.d.mts} +6 -0
  61. package/dist/{types-YbiXYKx1.d.ts → types-C23_uaY8.d.ts} +6 -0
  62. package/dist/verify.js +1 -1
  63. package/dist/verify.js.map +1 -1
  64. package/dist/verify.mjs +1 -1
  65. package/dist/verify.mjs.map +1 -1
  66. package/package.json +1 -1
@@ -284,7 +284,9 @@ var HIGH_RISK_COMMANDS = [
284
284
  "git push",
285
285
  "npm publish",
286
286
  "docker",
287
- "kubectl"
287
+ "kubectl",
288
+ "gh pr create",
289
+ "gh pr merge"
288
290
  ];
289
291
  var SENSITIVE_PATHS = [
290
292
  ".ssh",
@@ -303,6 +305,164 @@ var SENSITIVE_PATHS = [
303
305
  ".pypirc"
304
306
  ];
305
307
 
308
+ // src/local-evaluator/command-matcher.ts
309
+ var WRAPPERS = /* @__PURE__ */ new Set([
310
+ "sudo",
311
+ "doas",
312
+ "env",
313
+ "nohup",
314
+ "time",
315
+ "timeout",
316
+ "nice",
317
+ "ionice",
318
+ "stdbuf",
319
+ "xargs",
320
+ "command",
321
+ "exec",
322
+ "builtin"
323
+ ]);
324
+ var ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;
325
+ var MAX_WRAPPER_HOPS = 4;
326
+ var MAX_RECURSION_DEPTH = 3;
327
+ var MAX_COMMAND_LENGTH = 1e5;
328
+ var LOCAL_HOST_RE = /^(localhost|127\.0\.0\.1|::1|[a-z0-9.-]*\.localhost)$/;
329
+ function buildRiskList(entries) {
330
+ const singleWord = /* @__PURE__ */ new Set();
331
+ const multiWord = [];
332
+ for (const entry of entries) {
333
+ const words = entry.toLowerCase().trim().split(/\s+/);
334
+ if (words.length === 1) singleWord.add(words[0]);
335
+ else multiWord.push(words);
336
+ }
337
+ multiWord.sort((a, b) => b.length - a.length);
338
+ return { singleWord, multiWord };
339
+ }
340
+ function stripHeredocBodies(text) {
341
+ const lines = text.split("\n");
342
+ const kept = [];
343
+ let terminator = null;
344
+ for (const line of lines) {
345
+ if (terminator !== null) {
346
+ if (line.trim() === terminator) terminator = null;
347
+ continue;
348
+ }
349
+ kept.push(line);
350
+ const heredoc = line.match(/<<-?\s*['"]?(\w+)['"]?/);
351
+ if (heredoc) terminator = heredoc[1];
352
+ }
353
+ return kept.join("\n");
354
+ }
355
+ function extractDollarSubstitutions(text, collect) {
356
+ let result = text;
357
+ let start = result.indexOf("$(");
358
+ let iterations = 0;
359
+ while (start !== -1 && iterations < 50) {
360
+ iterations++;
361
+ let depth = 0;
362
+ let end = result.length;
363
+ for (let i = start + 1; i < result.length; i++) {
364
+ if (result[i] === "(") depth++;
365
+ else if (result[i] === ")") {
366
+ depth--;
367
+ if (depth === 0) {
368
+ end = i;
369
+ break;
370
+ }
371
+ }
372
+ }
373
+ collect(result.slice(start + 2, end));
374
+ result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;
375
+ start = result.indexOf("$(");
376
+ }
377
+ return result;
378
+ }
379
+ function extractBacktickSubstitutions(text, collect) {
380
+ if (!text.includes("`")) return text;
381
+ const chunks = text.split("`");
382
+ const kept = [];
383
+ for (let i = 0; i < chunks.length; i++) {
384
+ if (i % 2 === 1) collect(chunks[i]);
385
+ else kept.push(chunks[i]);
386
+ }
387
+ return kept.join(" ");
388
+ }
389
+ function basename(token) {
390
+ const slash = token.lastIndexOf("/");
391
+ return slash === -1 ? token : token.slice(slash + 1);
392
+ }
393
+ function matchSegment(segment, list) {
394
+ const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
395
+ let i = 0;
396
+ let hops = 0;
397
+ while (i < tokens.length) {
398
+ while (i < tokens.length && (tokens[i] === "export" || ASSIGN_RE.test(tokens[i]))) i++;
399
+ if (i >= tokens.length) return null;
400
+ const cmd = basename(tokens[i]);
401
+ const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith("-"))];
402
+ for (const entry of list.multiWord) {
403
+ if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {
404
+ return entry.join(" ");
405
+ }
406
+ }
407
+ if (list.singleWord.has(cmd)) return cmd;
408
+ if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {
409
+ hops++;
410
+ i++;
411
+ while (i < tokens.length && (tokens[i].startsWith("-") || ASSIGN_RE.test(tokens[i]))) i++;
412
+ continue;
413
+ }
414
+ return null;
415
+ }
416
+ return null;
417
+ }
418
+ function collectMatches(command, list, matches, depth) {
419
+ if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;
420
+ let text = command.toLowerCase();
421
+ text = stripHeredocBodies(text);
422
+ const recurse = (inner) => collectMatches(inner, list, matches, depth + 1);
423
+ text = extractDollarSubstitutions(text, recurse);
424
+ text = extractBacktickSubstitutions(text, recurse);
425
+ text = text.replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
426
+ text = text.replace(/(^|\s)#[^\n]*/g, " ");
427
+ const segments = text.split(/\|\||&&|[;|&\n]|[({]/);
428
+ for (const segment of segments) {
429
+ const matched = matchSegment(segment, list);
430
+ if (matched) matches.add(matched);
431
+ }
432
+ }
433
+ function matchHighRiskCommands(command, highRiskCommands = HIGH_RISK_COMMANDS) {
434
+ if (!command) return [];
435
+ const matches = /* @__PURE__ */ new Set();
436
+ collectMatches(command, buildRiskList(highRiskCommands), matches, 0);
437
+ return [...matches];
438
+ }
439
+ function isLocalhostOnlyNetworkCommand(command) {
440
+ const text = command.toLowerCase();
441
+ const tokens = text.split(/\s+/).map((t) => t.replace(/^["'`]+|["'`,;)]+$/g, ""));
442
+ let localCount = 0;
443
+ for (const token of tokens) {
444
+ let host = null;
445
+ if (token.includes("://")) {
446
+ let rest = token.slice(token.indexOf("://") + 3);
447
+ const at = rest.indexOf("@");
448
+ if (at !== -1) rest = rest.slice(at + 1);
449
+ host = rest.split(/[/?#]/)[0];
450
+ } else if (/^[a-z0-9.[\]:-]+:\d+(\/.*)?$/.test(token)) {
451
+ host = token.split("/")[0];
452
+ } else if (/^(localhost|127\.0\.0\.1|\[?::1\]?)(\/.*)?$/.test(token)) {
453
+ host = token.split("/")[0];
454
+ }
455
+ if (host === null) continue;
456
+ const bare = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
457
+ if (LOCAL_HOST_RE.test(bare)) {
458
+ localCount++;
459
+ } else {
460
+ return false;
461
+ }
462
+ }
463
+ return localCount > 0;
464
+ }
465
+
306
466
  // src/local-evaluator/evaluator.ts
307
467
  var LocalEvaluator = class {
308
468
  constructor(policy) {
@@ -453,12 +613,10 @@ var LocalEvaluator = class {
453
613
  return score;
454
614
  }
455
615
  if (context.purpose === "shell.exec" && context.target) {
456
- const targetLower = context.target.toLowerCase();
457
- for (const cmd of HIGH_RISK_COMMANDS) {
458
- if (targetLower.startsWith(cmd) || targetLower.includes(` ${cmd} `) || targetLower.includes(` ${cmd}`)) {
459
- score = Math.max(score, 80);
460
- break;
461
- }
616
+ const matched = matchHighRiskCommands(context.target);
617
+ const localProbeOnly = matched.length > 0 && matched.every((cmd) => cmd === "curl" || cmd === "wget") && isLocalhostOnlyNetworkCommand(context.target);
618
+ if (matched.length > 0 && !localProbeOnly) {
619
+ score = Math.max(score, 80);
462
620
  }
463
621
  }
464
622
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
@@ -3769,7 +3927,7 @@ function getTrustLevel(score) {
3769
3927
  }
3770
3928
 
3771
3929
  // src/version.ts
3772
- var SDK_VERSION = "3.10.0";
3930
+ var SDK_VERSION = "3.10.1";
3773
3931
 
3774
3932
  // src/well-known.ts
3775
3933
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -4327,8 +4485,75 @@ async function verifyOnline(config, context, astraId) {
4327
4485
  // src/gateway/modes/hybrid.ts
4328
4486
  var import_fs2 = require("fs");
4329
4487
  var import_path = require("path");
4488
+
4489
+ // src/gateway/redact.ts
4490
+ var marker = (type2) => `[REDACTED:${type2}]`;
4491
+ var REDACTION_PATTERNS = [
4492
+ // Multi-line blocks first so later single-line patterns don't shred them.
4493
+ {
4494
+ type: "private-key",
4495
+ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g
4496
+ },
4497
+ // URL userinfo credentials (https://user:pass@, x-access-token:ghp_…@).
4498
+ {
4499
+ type: "url-credentials",
4500
+ re: /([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+:[^\s/@]+@/gi,
4501
+ replacement: `$1${marker("url-credentials")}@`
4502
+ },
4503
+ // Known token formats.
4504
+ { type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,255}\b/g },
4505
+ { type: "github-token", re: /\bgithub_pat_[A-Za-z0-9_]{20,255}\b/g },
4506
+ { type: "astrasync-key", re: /\bkya_[A-Fa-f0-9]{32,128}\b/g },
4507
+ { type: "openai-key", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g },
4508
+ { type: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/g },
4509
+ { type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
4510
+ // Bearer/token headers.
4511
+ {
4512
+ type: "bearer-token",
4513
+ re: /\b(bearer\s+)[a-z0-9._~+/=-]{16,}/gi,
4514
+ replacement: `$1${marker("bearer-token")}`
4515
+ },
4516
+ // Generic KEY=value / key: value assignments with secret-ish names.
4517
+ {
4518
+ type: "assigned-secret",
4519
+ re: /\b((?:password|passwd|secret|token|api[_-]?key|apikey|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[=:]\s*)(['"]?)[^\s'"&;]{8,}\2/gi,
4520
+ replacement: `$1$2${marker("assigned-secret")}$2`
4521
+ }
4522
+ ];
4523
+ function redactSecrets(text) {
4524
+ let result = text;
4525
+ for (const { type: type2, re, replacement } of REDACTION_PATTERNS) {
4526
+ result = result.replace(re, replacement ?? marker(type2));
4527
+ }
4528
+ return result;
4529
+ }
4530
+ function redactDeep(value) {
4531
+ if (typeof value === "string") {
4532
+ return redactSecrets(value);
4533
+ }
4534
+ if (Array.isArray(value)) {
4535
+ return value.map((item) => redactDeep(item));
4536
+ }
4537
+ if (value !== null && typeof value === "object" && !(value instanceof Date)) {
4538
+ const out = {};
4539
+ for (const [k, v] of Object.entries(value)) {
4540
+ out[k] = redactDeep(v);
4541
+ }
4542
+ return out;
4543
+ }
4544
+ return value;
4545
+ }
4546
+
4547
+ // src/gateway/modes/hybrid.ts
4330
4548
  var MAX_QUEUE_SIZE = 1e3;
4331
4549
  var MAX_RETRIES = 3;
4550
+ var FLUSH_TIMEOUT = /* @__PURE__ */ Symbol("flush-timeout");
4551
+ function delay(ms) {
4552
+ return new Promise((resolve) => {
4553
+ const timer = setTimeout(() => resolve(FLUSH_TIMEOUT), ms);
4554
+ if (typeof timer.unref === "function") timer.unref();
4555
+ });
4556
+ }
4332
4557
  var HybridSyncQueue = class {
4333
4558
  constructor(config, persistPath) {
4334
4559
  this.queue = [];
@@ -4349,12 +4574,12 @@ var HybridSyncQueue = class {
4349
4574
  /**
4350
4575
  * Stop periodic sync and flush remaining entries.
4351
4576
  */
4352
- async stop() {
4577
+ async stop(flushOptions = {}) {
4353
4578
  if (this.timer) {
4354
4579
  clearInterval(this.timer);
4355
4580
  this.timer = null;
4356
4581
  }
4357
- await this.flush();
4582
+ await this.flush(flushOptions);
4358
4583
  this.saveToDisk();
4359
4584
  }
4360
4585
  /**
@@ -4366,8 +4591,8 @@ var HybridSyncQueue = class {
4366
4591
  }
4367
4592
  this.queue.push({
4368
4593
  id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
4369
- context,
4370
- decision,
4594
+ context: redactDeep(context),
4595
+ decision: redactDeep(decision),
4371
4596
  timestamp: /* @__PURE__ */ new Date(),
4372
4597
  retryCount: 0,
4373
4598
  status: "pending"
@@ -4377,16 +4602,21 @@ var HybridSyncQueue = class {
4377
4602
  /**
4378
4603
  * Flush pending entries to the API.
4379
4604
  */
4380
- async flush() {
4605
+ async flush(options = {}) {
4381
4606
  if (this.processing) return;
4382
4607
  this.processing = true;
4383
4608
  try {
4384
4609
  const pending = this.queue.filter((e) => e.status === "pending");
4385
- for (const entry of pending) {
4386
- try {
4387
- await verifyOnline(this.config, entry.context);
4610
+ const batch = typeof options.limit === "number" ? pending.slice(0, options.limit) : pending;
4611
+ for (const entry of batch) {
4612
+ const attempt = verifyOnline(this.config, entry.context, this.config.agentId).then(() => "synced").catch(() => "error");
4613
+ const result = options.perEntryTimeoutMs ? await Promise.race([attempt, delay(options.perEntryTimeoutMs)]) : await attempt;
4614
+ if (result === FLUSH_TIMEOUT) {
4615
+ break;
4616
+ }
4617
+ if (result === "synced") {
4388
4618
  entry.status = "synced";
4389
- } catch {
4619
+ } else {
4390
4620
  entry.retryCount++;
4391
4621
  if (entry.retryCount >= MAX_RETRIES) {
4392
4622
  entry.status = "failed";
@@ -4463,8 +4693,8 @@ var TraceLogger = class {
4463
4693
  id: this.generateId(),
4464
4694
  timestamp: /* @__PURE__ */ new Date(),
4465
4695
  type: "evaluation",
4466
- context,
4467
- decision
4696
+ context: redactDeep(context),
4697
+ decision: redactDeep(decision)
4468
4698
  });
4469
4699
  }
4470
4700
  /**
@@ -4581,10 +4811,12 @@ var AstraSyncGateway = class {
4581
4811
  }
4582
4812
  /**
4583
4813
  * Graceful shutdown. Flushes sync queue and cleans up resources.
4814
+ * Stateless callers (hooks) pass bounded flush options so sync never
4815
+ * sits on a tool call's critical path.
4584
4816
  */
4585
- async shutdown() {
4817
+ async shutdown(options) {
4586
4818
  if (this.syncQueue) {
4587
- await this.syncQueue.stop();
4819
+ await this.syncQueue.stop(options?.syncFlush ?? {});
4588
4820
  this.syncQueue = null;
4589
4821
  }
4590
4822
  this.evaluator = null;
@@ -4607,7 +4839,7 @@ var AstraSyncGateway = class {
4607
4839
  let decision;
4608
4840
  switch (this.config.mode) {
4609
4841
  case "online":
4610
- decision = await verifyOnline(this.config, context, astraId);
4842
+ decision = await verifyOnline(this.config, context, astraId ?? this.config.agentId);
4611
4843
  break;
4612
4844
  case "local":
4613
4845
  if (!this.evaluator) throw new Error("Local evaluator not initialized");