@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
@@ -4185,7 +4185,9 @@ var HIGH_RISK_COMMANDS = [
4185
4185
  "git push",
4186
4186
  "npm publish",
4187
4187
  "docker",
4188
- "kubectl"
4188
+ "kubectl",
4189
+ "gh pr create",
4190
+ "gh pr merge"
4189
4191
  ];
4190
4192
  var SENSITIVE_PATHS = [
4191
4193
  ".ssh",
@@ -4204,6 +4206,164 @@ var SENSITIVE_PATHS = [
4204
4206
  ".pypirc"
4205
4207
  ];
4206
4208
 
4209
+ // src/local-evaluator/command-matcher.ts
4210
+ var WRAPPERS = /* @__PURE__ */ new Set([
4211
+ "sudo",
4212
+ "doas",
4213
+ "env",
4214
+ "nohup",
4215
+ "time",
4216
+ "timeout",
4217
+ "nice",
4218
+ "ionice",
4219
+ "stdbuf",
4220
+ "xargs",
4221
+ "command",
4222
+ "exec",
4223
+ "builtin"
4224
+ ]);
4225
+ var ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;
4226
+ var MAX_WRAPPER_HOPS = 4;
4227
+ var MAX_RECURSION_DEPTH = 3;
4228
+ var MAX_COMMAND_LENGTH = 1e5;
4229
+ var LOCAL_HOST_RE = /^(localhost|127\.0\.0\.1|::1|[a-z0-9.-]*\.localhost)$/;
4230
+ function buildRiskList(entries) {
4231
+ const singleWord = /* @__PURE__ */ new Set();
4232
+ const multiWord = [];
4233
+ for (const entry of entries) {
4234
+ const words = entry.toLowerCase().trim().split(/\s+/);
4235
+ if (words.length === 1) singleWord.add(words[0]);
4236
+ else multiWord.push(words);
4237
+ }
4238
+ multiWord.sort((a, b) => b.length - a.length);
4239
+ return { singleWord, multiWord };
4240
+ }
4241
+ function stripHeredocBodies(text) {
4242
+ const lines = text.split("\n");
4243
+ const kept = [];
4244
+ let terminator = null;
4245
+ for (const line of lines) {
4246
+ if (terminator !== null) {
4247
+ if (line.trim() === terminator) terminator = null;
4248
+ continue;
4249
+ }
4250
+ kept.push(line);
4251
+ const heredoc = line.match(/<<-?\s*['"]?(\w+)['"]?/);
4252
+ if (heredoc) terminator = heredoc[1];
4253
+ }
4254
+ return kept.join("\n");
4255
+ }
4256
+ function extractDollarSubstitutions(text, collect) {
4257
+ let result = text;
4258
+ let start = result.indexOf("$(");
4259
+ let iterations = 0;
4260
+ while (start !== -1 && iterations < 50) {
4261
+ iterations++;
4262
+ let depth = 0;
4263
+ let end = result.length;
4264
+ for (let i = start + 1; i < result.length; i++) {
4265
+ if (result[i] === "(") depth++;
4266
+ else if (result[i] === ")") {
4267
+ depth--;
4268
+ if (depth === 0) {
4269
+ end = i;
4270
+ break;
4271
+ }
4272
+ }
4273
+ }
4274
+ collect(result.slice(start + 2, end));
4275
+ result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;
4276
+ start = result.indexOf("$(");
4277
+ }
4278
+ return result;
4279
+ }
4280
+ function extractBacktickSubstitutions(text, collect) {
4281
+ if (!text.includes("`")) return text;
4282
+ const chunks = text.split("`");
4283
+ const kept = [];
4284
+ for (let i = 0; i < chunks.length; i++) {
4285
+ if (i % 2 === 1) collect(chunks[i]);
4286
+ else kept.push(chunks[i]);
4287
+ }
4288
+ return kept.join(" ");
4289
+ }
4290
+ function basename(token) {
4291
+ const slash = token.lastIndexOf("/");
4292
+ return slash === -1 ? token : token.slice(slash + 1);
4293
+ }
4294
+ function matchSegment(segment, list) {
4295
+ const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
4296
+ let i = 0;
4297
+ let hops = 0;
4298
+ while (i < tokens.length) {
4299
+ while (i < tokens.length && (tokens[i] === "export" || ASSIGN_RE.test(tokens[i]))) i++;
4300
+ if (i >= tokens.length) return null;
4301
+ const cmd = basename(tokens[i]);
4302
+ const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith("-"))];
4303
+ for (const entry of list.multiWord) {
4304
+ if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {
4305
+ return entry.join(" ");
4306
+ }
4307
+ }
4308
+ if (list.singleWord.has(cmd)) return cmd;
4309
+ if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {
4310
+ hops++;
4311
+ i++;
4312
+ while (i < tokens.length && (tokens[i].startsWith("-") || ASSIGN_RE.test(tokens[i]))) i++;
4313
+ continue;
4314
+ }
4315
+ return null;
4316
+ }
4317
+ return null;
4318
+ }
4319
+ function collectMatches(command, list, matches, depth) {
4320
+ if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;
4321
+ let text = command.toLowerCase();
4322
+ text = stripHeredocBodies(text);
4323
+ const recurse = (inner) => collectMatches(inner, list, matches, depth + 1);
4324
+ text = extractDollarSubstitutions(text, recurse);
4325
+ text = extractBacktickSubstitutions(text, recurse);
4326
+ text = text.replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
4327
+ text = text.replace(/(^|\s)#[^\n]*/g, " ");
4328
+ const segments = text.split(/\|\||&&|[;|&\n]|[({]/);
4329
+ for (const segment of segments) {
4330
+ const matched = matchSegment(segment, list);
4331
+ if (matched) matches.add(matched);
4332
+ }
4333
+ }
4334
+ function matchHighRiskCommands(command, highRiskCommands = HIGH_RISK_COMMANDS) {
4335
+ if (!command) return [];
4336
+ const matches = /* @__PURE__ */ new Set();
4337
+ collectMatches(command, buildRiskList(highRiskCommands), matches, 0);
4338
+ return [...matches];
4339
+ }
4340
+ function isLocalhostOnlyNetworkCommand(command) {
4341
+ const text = command.toLowerCase();
4342
+ const tokens = text.split(/\s+/).map((t) => t.replace(/^["'`]+|["'`,;)]+$/g, ""));
4343
+ let localCount = 0;
4344
+ for (const token of tokens) {
4345
+ let host = null;
4346
+ if (token.includes("://")) {
4347
+ let rest = token.slice(token.indexOf("://") + 3);
4348
+ const at = rest.indexOf("@");
4349
+ if (at !== -1) rest = rest.slice(at + 1);
4350
+ host = rest.split(/[/?#]/)[0];
4351
+ } else if (/^[a-z0-9.[\]:-]+:\d+(\/.*)?$/.test(token)) {
4352
+ host = token.split("/")[0];
4353
+ } else if (/^(localhost|127\.0\.0\.1|\[?::1\]?)(\/.*)?$/.test(token)) {
4354
+ host = token.split("/")[0];
4355
+ }
4356
+ if (host === null) continue;
4357
+ const bare = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
4358
+ if (LOCAL_HOST_RE.test(bare)) {
4359
+ localCount++;
4360
+ } else {
4361
+ return false;
4362
+ }
4363
+ }
4364
+ return localCount > 0;
4365
+ }
4366
+
4207
4367
  // src/local-evaluator/evaluator.ts
4208
4368
  var LocalEvaluator = class {
4209
4369
  constructor(policy) {
@@ -4354,12 +4514,10 @@ var LocalEvaluator = class {
4354
4514
  return score;
4355
4515
  }
4356
4516
  if (context.purpose === "shell.exec" && context.target) {
4357
- const targetLower = context.target.toLowerCase();
4358
- for (const cmd of HIGH_RISK_COMMANDS) {
4359
- if (targetLower.startsWith(cmd) || targetLower.includes(` ${cmd} `) || targetLower.includes(` ${cmd}`)) {
4360
- score = Math.max(score, 80);
4361
- break;
4362
- }
4517
+ const matched = matchHighRiskCommands(context.target);
4518
+ const localProbeOnly = matched.length > 0 && matched.every((cmd) => cmd === "curl" || cmd === "wget") && isLocalhostOnlyNetworkCommand(context.target);
4519
+ if (matched.length > 0 && !localProbeOnly) {
4520
+ score = Math.max(score, 80);
4363
4521
  }
4364
4522
  }
4365
4523
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
@@ -4440,7 +4598,7 @@ function getTrustLevel(score) {
4440
4598
  }
4441
4599
 
4442
4600
  // src/version.ts
4443
- var SDK_VERSION = "3.10.0";
4601
+ var SDK_VERSION = "3.10.1";
4444
4602
 
4445
4603
  // src/well-known.ts
4446
4604
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -4998,8 +5156,75 @@ async function verifyOnline(config, context, astraId) {
4998
5156
  // src/gateway/modes/hybrid.ts
4999
5157
  var import_fs2 = require("fs");
5000
5158
  var import_path = require("path");
5159
+
5160
+ // src/gateway/redact.ts
5161
+ var marker = (type2) => `[REDACTED:${type2}]`;
5162
+ var REDACTION_PATTERNS = [
5163
+ // Multi-line blocks first so later single-line patterns don't shred them.
5164
+ {
5165
+ type: "private-key",
5166
+ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g
5167
+ },
5168
+ // URL userinfo credentials (https://user:pass@, x-access-token:ghp_…@).
5169
+ {
5170
+ type: "url-credentials",
5171
+ re: /([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+:[^\s/@]+@/gi,
5172
+ replacement: `$1${marker("url-credentials")}@`
5173
+ },
5174
+ // Known token formats.
5175
+ { type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,255}\b/g },
5176
+ { type: "github-token", re: /\bgithub_pat_[A-Za-z0-9_]{20,255}\b/g },
5177
+ { type: "astrasync-key", re: /\bkya_[A-Fa-f0-9]{32,128}\b/g },
5178
+ { type: "openai-key", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g },
5179
+ { type: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/g },
5180
+ { type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
5181
+ // Bearer/token headers.
5182
+ {
5183
+ type: "bearer-token",
5184
+ re: /\b(bearer\s+)[a-z0-9._~+/=-]{16,}/gi,
5185
+ replacement: `$1${marker("bearer-token")}`
5186
+ },
5187
+ // Generic KEY=value / key: value assignments with secret-ish names.
5188
+ {
5189
+ type: "assigned-secret",
5190
+ re: /\b((?:password|passwd|secret|token|api[_-]?key|apikey|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[=:]\s*)(['"]?)[^\s'"&;]{8,}\2/gi,
5191
+ replacement: `$1$2${marker("assigned-secret")}$2`
5192
+ }
5193
+ ];
5194
+ function redactSecrets(text) {
5195
+ let result = text;
5196
+ for (const { type: type2, re, replacement } of REDACTION_PATTERNS) {
5197
+ result = result.replace(re, replacement ?? marker(type2));
5198
+ }
5199
+ return result;
5200
+ }
5201
+ function redactDeep(value) {
5202
+ if (typeof value === "string") {
5203
+ return redactSecrets(value);
5204
+ }
5205
+ if (Array.isArray(value)) {
5206
+ return value.map((item) => redactDeep(item));
5207
+ }
5208
+ if (value !== null && typeof value === "object" && !(value instanceof Date)) {
5209
+ const out = {};
5210
+ for (const [k, v] of Object.entries(value)) {
5211
+ out[k] = redactDeep(v);
5212
+ }
5213
+ return out;
5214
+ }
5215
+ return value;
5216
+ }
5217
+
5218
+ // src/gateway/modes/hybrid.ts
5001
5219
  var MAX_QUEUE_SIZE = 1e3;
5002
5220
  var MAX_RETRIES = 3;
5221
+ var FLUSH_TIMEOUT = /* @__PURE__ */ Symbol("flush-timeout");
5222
+ function delay(ms) {
5223
+ return new Promise((resolve) => {
5224
+ const timer = setTimeout(() => resolve(FLUSH_TIMEOUT), ms);
5225
+ if (typeof timer.unref === "function") timer.unref();
5226
+ });
5227
+ }
5003
5228
  var HybridSyncQueue = class {
5004
5229
  constructor(config, persistPath) {
5005
5230
  this.queue = [];
@@ -5020,12 +5245,12 @@ var HybridSyncQueue = class {
5020
5245
  /**
5021
5246
  * Stop periodic sync and flush remaining entries.
5022
5247
  */
5023
- async stop() {
5248
+ async stop(flushOptions = {}) {
5024
5249
  if (this.timer) {
5025
5250
  clearInterval(this.timer);
5026
5251
  this.timer = null;
5027
5252
  }
5028
- await this.flush();
5253
+ await this.flush(flushOptions);
5029
5254
  this.saveToDisk();
5030
5255
  }
5031
5256
  /**
@@ -5037,8 +5262,8 @@ var HybridSyncQueue = class {
5037
5262
  }
5038
5263
  this.queue.push({
5039
5264
  id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
5040
- context,
5041
- decision,
5265
+ context: redactDeep(context),
5266
+ decision: redactDeep(decision),
5042
5267
  timestamp: /* @__PURE__ */ new Date(),
5043
5268
  retryCount: 0,
5044
5269
  status: "pending"
@@ -5048,16 +5273,21 @@ var HybridSyncQueue = class {
5048
5273
  /**
5049
5274
  * Flush pending entries to the API.
5050
5275
  */
5051
- async flush() {
5276
+ async flush(options = {}) {
5052
5277
  if (this.processing) return;
5053
5278
  this.processing = true;
5054
5279
  try {
5055
5280
  const pending = this.queue.filter((e) => e.status === "pending");
5056
- for (const entry of pending) {
5057
- try {
5058
- await verifyOnline(this.config, entry.context);
5281
+ const batch = typeof options.limit === "number" ? pending.slice(0, options.limit) : pending;
5282
+ for (const entry of batch) {
5283
+ const attempt = verifyOnline(this.config, entry.context, this.config.agentId).then(() => "synced").catch(() => "error");
5284
+ const result = options.perEntryTimeoutMs ? await Promise.race([attempt, delay(options.perEntryTimeoutMs)]) : await attempt;
5285
+ if (result === FLUSH_TIMEOUT) {
5286
+ break;
5287
+ }
5288
+ if (result === "synced") {
5059
5289
  entry.status = "synced";
5060
- } catch {
5290
+ } else {
5061
5291
  entry.retryCount++;
5062
5292
  if (entry.retryCount >= MAX_RETRIES) {
5063
5293
  entry.status = "failed";
@@ -5134,8 +5364,8 @@ var TraceLogger = class {
5134
5364
  id: this.generateId(),
5135
5365
  timestamp: /* @__PURE__ */ new Date(),
5136
5366
  type: "evaluation",
5137
- context,
5138
- decision
5367
+ context: redactDeep(context),
5368
+ decision: redactDeep(decision)
5139
5369
  });
5140
5370
  }
5141
5371
  /**
@@ -5252,10 +5482,12 @@ var AstraSyncGateway = class {
5252
5482
  }
5253
5483
  /**
5254
5484
  * Graceful shutdown. Flushes sync queue and cleans up resources.
5485
+ * Stateless callers (hooks) pass bounded flush options so sync never
5486
+ * sits on a tool call's critical path.
5255
5487
  */
5256
- async shutdown() {
5488
+ async shutdown(options) {
5257
5489
  if (this.syncQueue) {
5258
- await this.syncQueue.stop();
5490
+ await this.syncQueue.stop(options?.syncFlush ?? {});
5259
5491
  this.syncQueue = null;
5260
5492
  }
5261
5493
  this.evaluator = null;
@@ -5278,7 +5510,7 @@ var AstraSyncGateway = class {
5278
5510
  let decision;
5279
5511
  switch (this.config.mode) {
5280
5512
  case "online":
5281
- decision = await verifyOnline(this.config, context, astraId);
5513
+ decision = await verifyOnline(this.config, context, astraId ?? this.config.agentId);
5282
5514
  break;
5283
5515
  case "local":
5284
5516
  if (!this.evaluator) throw new Error("Local evaluator not initialized");
@@ -5830,7 +6062,7 @@ async function cmdUninstallHooks() {
5830
6062
  function resolveClaudeHookCommand() {
5831
6063
  const sibling = path.join(__dirname, "astrasync-claude-hook.js");
5832
6064
  if (fs.existsSync(sibling)) {
5833
- return `node "${sibling}"`;
6065
+ return `"${process.execPath}" "${sibling}"`;
5834
6066
  }
5835
6067
  return "astrasync-claude-hook";
5836
6068
  }