@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
@@ -261,7 +261,9 @@ var HIGH_RISK_COMMANDS = [
261
261
  "git push",
262
262
  "npm publish",
263
263
  "docker",
264
- "kubectl"
264
+ "kubectl",
265
+ "gh pr create",
266
+ "gh pr merge"
265
267
  ];
266
268
  var SENSITIVE_PATHS = [
267
269
  ".ssh",
@@ -280,6 +282,164 @@ var SENSITIVE_PATHS = [
280
282
  ".pypirc"
281
283
  ];
282
284
 
285
+ // src/local-evaluator/command-matcher.ts
286
+ var WRAPPERS = /* @__PURE__ */ new Set([
287
+ "sudo",
288
+ "doas",
289
+ "env",
290
+ "nohup",
291
+ "time",
292
+ "timeout",
293
+ "nice",
294
+ "ionice",
295
+ "stdbuf",
296
+ "xargs",
297
+ "command",
298
+ "exec",
299
+ "builtin"
300
+ ]);
301
+ var ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;
302
+ var MAX_WRAPPER_HOPS = 4;
303
+ var MAX_RECURSION_DEPTH = 3;
304
+ var MAX_COMMAND_LENGTH = 1e5;
305
+ var LOCAL_HOST_RE = /^(localhost|127\.0\.0\.1|::1|[a-z0-9.-]*\.localhost)$/;
306
+ function buildRiskList(entries) {
307
+ const singleWord = /* @__PURE__ */ new Set();
308
+ const multiWord = [];
309
+ for (const entry of entries) {
310
+ const words = entry.toLowerCase().trim().split(/\s+/);
311
+ if (words.length === 1) singleWord.add(words[0]);
312
+ else multiWord.push(words);
313
+ }
314
+ multiWord.sort((a, b) => b.length - a.length);
315
+ return { singleWord, multiWord };
316
+ }
317
+ function stripHeredocBodies(text) {
318
+ const lines = text.split("\n");
319
+ const kept = [];
320
+ let terminator = null;
321
+ for (const line of lines) {
322
+ if (terminator !== null) {
323
+ if (line.trim() === terminator) terminator = null;
324
+ continue;
325
+ }
326
+ kept.push(line);
327
+ const heredoc = line.match(/<<-?\s*['"]?(\w+)['"]?/);
328
+ if (heredoc) terminator = heredoc[1];
329
+ }
330
+ return kept.join("\n");
331
+ }
332
+ function extractDollarSubstitutions(text, collect) {
333
+ let result = text;
334
+ let start = result.indexOf("$(");
335
+ let iterations = 0;
336
+ while (start !== -1 && iterations < 50) {
337
+ iterations++;
338
+ let depth = 0;
339
+ let end = result.length;
340
+ for (let i = start + 1; i < result.length; i++) {
341
+ if (result[i] === "(") depth++;
342
+ else if (result[i] === ")") {
343
+ depth--;
344
+ if (depth === 0) {
345
+ end = i;
346
+ break;
347
+ }
348
+ }
349
+ }
350
+ collect(result.slice(start + 2, end));
351
+ result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;
352
+ start = result.indexOf("$(");
353
+ }
354
+ return result;
355
+ }
356
+ function extractBacktickSubstitutions(text, collect) {
357
+ if (!text.includes("`")) return text;
358
+ const chunks = text.split("`");
359
+ const kept = [];
360
+ for (let i = 0; i < chunks.length; i++) {
361
+ if (i % 2 === 1) collect(chunks[i]);
362
+ else kept.push(chunks[i]);
363
+ }
364
+ return kept.join(" ");
365
+ }
366
+ function basename(token) {
367
+ const slash = token.lastIndexOf("/");
368
+ return slash === -1 ? token : token.slice(slash + 1);
369
+ }
370
+ function matchSegment(segment, list) {
371
+ const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
372
+ let i = 0;
373
+ let hops = 0;
374
+ while (i < tokens.length) {
375
+ while (i < tokens.length && (tokens[i] === "export" || ASSIGN_RE.test(tokens[i]))) i++;
376
+ if (i >= tokens.length) return null;
377
+ const cmd = basename(tokens[i]);
378
+ const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith("-"))];
379
+ for (const entry of list.multiWord) {
380
+ if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {
381
+ return entry.join(" ");
382
+ }
383
+ }
384
+ if (list.singleWord.has(cmd)) return cmd;
385
+ if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {
386
+ hops++;
387
+ i++;
388
+ while (i < tokens.length && (tokens[i].startsWith("-") || ASSIGN_RE.test(tokens[i]))) i++;
389
+ continue;
390
+ }
391
+ return null;
392
+ }
393
+ return null;
394
+ }
395
+ function collectMatches(command, list, matches, depth) {
396
+ if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;
397
+ let text = command.toLowerCase();
398
+ text = stripHeredocBodies(text);
399
+ const recurse = (inner) => collectMatches(inner, list, matches, depth + 1);
400
+ text = extractDollarSubstitutions(text, recurse);
401
+ text = extractBacktickSubstitutions(text, recurse);
402
+ text = text.replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
403
+ text = text.replace(/(^|\s)#[^\n]*/g, " ");
404
+ const segments = text.split(/\|\||&&|[;|&\n]|[({]/);
405
+ for (const segment of segments) {
406
+ const matched = matchSegment(segment, list);
407
+ if (matched) matches.add(matched);
408
+ }
409
+ }
410
+ function matchHighRiskCommands(command, highRiskCommands = HIGH_RISK_COMMANDS) {
411
+ if (!command) return [];
412
+ const matches = /* @__PURE__ */ new Set();
413
+ collectMatches(command, buildRiskList(highRiskCommands), matches, 0);
414
+ return [...matches];
415
+ }
416
+ function isLocalhostOnlyNetworkCommand(command) {
417
+ const text = command.toLowerCase();
418
+ const tokens = text.split(/\s+/).map((t) => t.replace(/^["'`]+|["'`,;)]+$/g, ""));
419
+ let localCount = 0;
420
+ for (const token of tokens) {
421
+ let host = null;
422
+ if (token.includes("://")) {
423
+ let rest = token.slice(token.indexOf("://") + 3);
424
+ const at = rest.indexOf("@");
425
+ if (at !== -1) rest = rest.slice(at + 1);
426
+ host = rest.split(/[/?#]/)[0];
427
+ } else if (/^[a-z0-9.[\]:-]+:\d+(\/.*)?$/.test(token)) {
428
+ host = token.split("/")[0];
429
+ } else if (/^(localhost|127\.0\.0\.1|\[?::1\]?)(\/.*)?$/.test(token)) {
430
+ host = token.split("/")[0];
431
+ }
432
+ if (host === null) continue;
433
+ const bare = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
434
+ if (LOCAL_HOST_RE.test(bare)) {
435
+ localCount++;
436
+ } else {
437
+ return false;
438
+ }
439
+ }
440
+ return localCount > 0;
441
+ }
442
+
283
443
  // src/local-evaluator/evaluator.ts
284
444
  var LocalEvaluator = class {
285
445
  constructor(policy) {
@@ -430,12 +590,10 @@ var LocalEvaluator = class {
430
590
  return score;
431
591
  }
432
592
  if (context.purpose === "shell.exec" && context.target) {
433
- const targetLower = context.target.toLowerCase();
434
- for (const cmd of HIGH_RISK_COMMANDS) {
435
- if (targetLower.startsWith(cmd) || targetLower.includes(` ${cmd} `) || targetLower.includes(` ${cmd}`)) {
436
- score = Math.max(score, 80);
437
- break;
438
- }
593
+ const matched = matchHighRiskCommands(context.target);
594
+ const localProbeOnly = matched.length > 0 && matched.every((cmd) => cmd === "curl" || cmd === "wget") && isLocalhostOnlyNetworkCommand(context.target);
595
+ if (matched.length > 0 && !localProbeOnly) {
596
+ score = Math.max(score, 80);
439
597
  }
440
598
  }
441
599
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
@@ -3746,7 +3904,7 @@ function getTrustLevel(score) {
3746
3904
  }
3747
3905
 
3748
3906
  // src/version.ts
3749
- var SDK_VERSION = "3.10.0";
3907
+ var SDK_VERSION = "3.10.1";
3750
3908
 
3751
3909
  // src/well-known.ts
3752
3910
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -4307,8 +4465,75 @@ async function verifyOnline(config, context, astraId) {
4307
4465
  // src/gateway/modes/hybrid.ts
4308
4466
  import { existsSync, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
4309
4467
  import { dirname } from "path";
4468
+
4469
+ // src/gateway/redact.ts
4470
+ var marker = (type2) => `[REDACTED:${type2}]`;
4471
+ var REDACTION_PATTERNS = [
4472
+ // Multi-line blocks first so later single-line patterns don't shred them.
4473
+ {
4474
+ type: "private-key",
4475
+ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g
4476
+ },
4477
+ // URL userinfo credentials (https://user:pass@, x-access-token:ghp_…@).
4478
+ {
4479
+ type: "url-credentials",
4480
+ re: /([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+:[^\s/@]+@/gi,
4481
+ replacement: `$1${marker("url-credentials")}@`
4482
+ },
4483
+ // Known token formats.
4484
+ { type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,255}\b/g },
4485
+ { type: "github-token", re: /\bgithub_pat_[A-Za-z0-9_]{20,255}\b/g },
4486
+ { type: "astrasync-key", re: /\bkya_[A-Fa-f0-9]{32,128}\b/g },
4487
+ { type: "openai-key", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g },
4488
+ { type: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/g },
4489
+ { type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
4490
+ // Bearer/token headers.
4491
+ {
4492
+ type: "bearer-token",
4493
+ re: /\b(bearer\s+)[a-z0-9._~+/=-]{16,}/gi,
4494
+ replacement: `$1${marker("bearer-token")}`
4495
+ },
4496
+ // Generic KEY=value / key: value assignments with secret-ish names.
4497
+ {
4498
+ type: "assigned-secret",
4499
+ re: /\b((?:password|passwd|secret|token|api[_-]?key|apikey|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[=:]\s*)(['"]?)[^\s'"&;]{8,}\2/gi,
4500
+ replacement: `$1$2${marker("assigned-secret")}$2`
4501
+ }
4502
+ ];
4503
+ function redactSecrets(text) {
4504
+ let result = text;
4505
+ for (const { type: type2, re, replacement } of REDACTION_PATTERNS) {
4506
+ result = result.replace(re, replacement ?? marker(type2));
4507
+ }
4508
+ return result;
4509
+ }
4510
+ function redactDeep(value) {
4511
+ if (typeof value === "string") {
4512
+ return redactSecrets(value);
4513
+ }
4514
+ if (Array.isArray(value)) {
4515
+ return value.map((item) => redactDeep(item));
4516
+ }
4517
+ if (value !== null && typeof value === "object" && !(value instanceof Date)) {
4518
+ const out = {};
4519
+ for (const [k, v] of Object.entries(value)) {
4520
+ out[k] = redactDeep(v);
4521
+ }
4522
+ return out;
4523
+ }
4524
+ return value;
4525
+ }
4526
+
4527
+ // src/gateway/modes/hybrid.ts
4310
4528
  var MAX_QUEUE_SIZE = 1e3;
4311
4529
  var MAX_RETRIES = 3;
4530
+ var FLUSH_TIMEOUT = /* @__PURE__ */ Symbol("flush-timeout");
4531
+ function delay(ms) {
4532
+ return new Promise((resolve) => {
4533
+ const timer = setTimeout(() => resolve(FLUSH_TIMEOUT), ms);
4534
+ if (typeof timer.unref === "function") timer.unref();
4535
+ });
4536
+ }
4312
4537
  var HybridSyncQueue = class {
4313
4538
  constructor(config, persistPath) {
4314
4539
  this.queue = [];
@@ -4329,12 +4554,12 @@ var HybridSyncQueue = class {
4329
4554
  /**
4330
4555
  * Stop periodic sync and flush remaining entries.
4331
4556
  */
4332
- async stop() {
4557
+ async stop(flushOptions = {}) {
4333
4558
  if (this.timer) {
4334
4559
  clearInterval(this.timer);
4335
4560
  this.timer = null;
4336
4561
  }
4337
- await this.flush();
4562
+ await this.flush(flushOptions);
4338
4563
  this.saveToDisk();
4339
4564
  }
4340
4565
  /**
@@ -4346,8 +4571,8 @@ var HybridSyncQueue = class {
4346
4571
  }
4347
4572
  this.queue.push({
4348
4573
  id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
4349
- context,
4350
- decision,
4574
+ context: redactDeep(context),
4575
+ decision: redactDeep(decision),
4351
4576
  timestamp: /* @__PURE__ */ new Date(),
4352
4577
  retryCount: 0,
4353
4578
  status: "pending"
@@ -4357,16 +4582,21 @@ var HybridSyncQueue = class {
4357
4582
  /**
4358
4583
  * Flush pending entries to the API.
4359
4584
  */
4360
- async flush() {
4585
+ async flush(options = {}) {
4361
4586
  if (this.processing) return;
4362
4587
  this.processing = true;
4363
4588
  try {
4364
4589
  const pending = this.queue.filter((e) => e.status === "pending");
4365
- for (const entry of pending) {
4366
- try {
4367
- await verifyOnline(this.config, entry.context);
4590
+ const batch = typeof options.limit === "number" ? pending.slice(0, options.limit) : pending;
4591
+ for (const entry of batch) {
4592
+ const attempt = verifyOnline(this.config, entry.context, this.config.agentId).then(() => "synced").catch(() => "error");
4593
+ const result = options.perEntryTimeoutMs ? await Promise.race([attempt, delay(options.perEntryTimeoutMs)]) : await attempt;
4594
+ if (result === FLUSH_TIMEOUT) {
4595
+ break;
4596
+ }
4597
+ if (result === "synced") {
4368
4598
  entry.status = "synced";
4369
- } catch {
4599
+ } else {
4370
4600
  entry.retryCount++;
4371
4601
  if (entry.retryCount >= MAX_RETRIES) {
4372
4602
  entry.status = "failed";
@@ -4443,8 +4673,8 @@ var TraceLogger = class {
4443
4673
  id: this.generateId(),
4444
4674
  timestamp: /* @__PURE__ */ new Date(),
4445
4675
  type: "evaluation",
4446
- context,
4447
- decision
4676
+ context: redactDeep(context),
4677
+ decision: redactDeep(decision)
4448
4678
  });
4449
4679
  }
4450
4680
  /**
@@ -4561,10 +4791,12 @@ var AstraSyncGateway = class {
4561
4791
  }
4562
4792
  /**
4563
4793
  * Graceful shutdown. Flushes sync queue and cleans up resources.
4794
+ * Stateless callers (hooks) pass bounded flush options so sync never
4795
+ * sits on a tool call's critical path.
4564
4796
  */
4565
- async shutdown() {
4797
+ async shutdown(options) {
4566
4798
  if (this.syncQueue) {
4567
- await this.syncQueue.stop();
4799
+ await this.syncQueue.stop(options?.syncFlush ?? {});
4568
4800
  this.syncQueue = null;
4569
4801
  }
4570
4802
  this.evaluator = null;
@@ -4587,7 +4819,7 @@ var AstraSyncGateway = class {
4587
4819
  let decision;
4588
4820
  switch (this.config.mode) {
4589
4821
  case "online":
4590
- decision = await verifyOnline(this.config, context, astraId);
4822
+ decision = await verifyOnline(this.config, context, astraId ?? this.config.agentId);
4591
4823
  break;
4592
4824
  case "local":
4593
4825
  if (!this.evaluator) throw new Error("Local evaluator not initialized");