@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
@@ -18,7 +18,9 @@ var HIGH_RISK_COMMANDS = [
18
18
  "git push",
19
19
  "npm publish",
20
20
  "docker",
21
- "kubectl"
21
+ "kubectl",
22
+ "gh pr create",
23
+ "gh pr merge"
22
24
  ];
23
25
  var SENSITIVE_PATHS = [
24
26
  ".ssh",
@@ -37,6 +39,164 @@ var SENSITIVE_PATHS = [
37
39
  ".pypirc"
38
40
  ];
39
41
 
42
+ // src/local-evaluator/command-matcher.ts
43
+ var WRAPPERS = /* @__PURE__ */ new Set([
44
+ "sudo",
45
+ "doas",
46
+ "env",
47
+ "nohup",
48
+ "time",
49
+ "timeout",
50
+ "nice",
51
+ "ionice",
52
+ "stdbuf",
53
+ "xargs",
54
+ "command",
55
+ "exec",
56
+ "builtin"
57
+ ]);
58
+ var ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;
59
+ var MAX_WRAPPER_HOPS = 4;
60
+ var MAX_RECURSION_DEPTH = 3;
61
+ var MAX_COMMAND_LENGTH = 1e5;
62
+ var LOCAL_HOST_RE = /^(localhost|127\.0\.0\.1|::1|[a-z0-9.-]*\.localhost)$/;
63
+ function buildRiskList(entries) {
64
+ const singleWord = /* @__PURE__ */ new Set();
65
+ const multiWord = [];
66
+ for (const entry of entries) {
67
+ const words = entry.toLowerCase().trim().split(/\s+/);
68
+ if (words.length === 1) singleWord.add(words[0]);
69
+ else multiWord.push(words);
70
+ }
71
+ multiWord.sort((a, b) => b.length - a.length);
72
+ return { singleWord, multiWord };
73
+ }
74
+ function stripHeredocBodies(text) {
75
+ const lines = text.split("\n");
76
+ const kept = [];
77
+ let terminator = null;
78
+ for (const line of lines) {
79
+ if (terminator !== null) {
80
+ if (line.trim() === terminator) terminator = null;
81
+ continue;
82
+ }
83
+ kept.push(line);
84
+ const heredoc = line.match(/<<-?\s*['"]?(\w+)['"]?/);
85
+ if (heredoc) terminator = heredoc[1];
86
+ }
87
+ return kept.join("\n");
88
+ }
89
+ function extractDollarSubstitutions(text, collect) {
90
+ let result = text;
91
+ let start = result.indexOf("$(");
92
+ let iterations = 0;
93
+ while (start !== -1 && iterations < 50) {
94
+ iterations++;
95
+ let depth = 0;
96
+ let end = result.length;
97
+ for (let i = start + 1; i < result.length; i++) {
98
+ if (result[i] === "(") depth++;
99
+ else if (result[i] === ")") {
100
+ depth--;
101
+ if (depth === 0) {
102
+ end = i;
103
+ break;
104
+ }
105
+ }
106
+ }
107
+ collect(result.slice(start + 2, end));
108
+ result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;
109
+ start = result.indexOf("$(");
110
+ }
111
+ return result;
112
+ }
113
+ function extractBacktickSubstitutions(text, collect) {
114
+ if (!text.includes("`")) return text;
115
+ const chunks = text.split("`");
116
+ const kept = [];
117
+ for (let i = 0; i < chunks.length; i++) {
118
+ if (i % 2 === 1) collect(chunks[i]);
119
+ else kept.push(chunks[i]);
120
+ }
121
+ return kept.join(" ");
122
+ }
123
+ function basename(token) {
124
+ const slash = token.lastIndexOf("/");
125
+ return slash === -1 ? token : token.slice(slash + 1);
126
+ }
127
+ function matchSegment(segment, list) {
128
+ const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
129
+ let i = 0;
130
+ let hops = 0;
131
+ while (i < tokens.length) {
132
+ while (i < tokens.length && (tokens[i] === "export" || ASSIGN_RE.test(tokens[i]))) i++;
133
+ if (i >= tokens.length) return null;
134
+ const cmd = basename(tokens[i]);
135
+ const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith("-"))];
136
+ for (const entry of list.multiWord) {
137
+ if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {
138
+ return entry.join(" ");
139
+ }
140
+ }
141
+ if (list.singleWord.has(cmd)) return cmd;
142
+ if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {
143
+ hops++;
144
+ i++;
145
+ while (i < tokens.length && (tokens[i].startsWith("-") || ASSIGN_RE.test(tokens[i]))) i++;
146
+ continue;
147
+ }
148
+ return null;
149
+ }
150
+ return null;
151
+ }
152
+ function collectMatches(command, list, matches, depth) {
153
+ if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;
154
+ let text = command.toLowerCase();
155
+ text = stripHeredocBodies(text);
156
+ const recurse = (inner) => collectMatches(inner, list, matches, depth + 1);
157
+ text = extractDollarSubstitutions(text, recurse);
158
+ text = extractBacktickSubstitutions(text, recurse);
159
+ text = text.replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
160
+ text = text.replace(/(^|\s)#[^\n]*/g, " ");
161
+ const segments = text.split(/\|\||&&|[;|&\n]|[({]/);
162
+ for (const segment of segments) {
163
+ const matched = matchSegment(segment, list);
164
+ if (matched) matches.add(matched);
165
+ }
166
+ }
167
+ function matchHighRiskCommands(command, highRiskCommands = HIGH_RISK_COMMANDS) {
168
+ if (!command) return [];
169
+ const matches = /* @__PURE__ */ new Set();
170
+ collectMatches(command, buildRiskList(highRiskCommands), matches, 0);
171
+ return [...matches];
172
+ }
173
+ function isLocalhostOnlyNetworkCommand(command) {
174
+ const text = command.toLowerCase();
175
+ const tokens = text.split(/\s+/).map((t) => t.replace(/^["'`]+|["'`,;)]+$/g, ""));
176
+ let localCount = 0;
177
+ for (const token of tokens) {
178
+ let host = null;
179
+ if (token.includes("://")) {
180
+ let rest = token.slice(token.indexOf("://") + 3);
181
+ const at = rest.indexOf("@");
182
+ if (at !== -1) rest = rest.slice(at + 1);
183
+ host = rest.split(/[/?#]/)[0];
184
+ } else if (/^[a-z0-9.[\]:-]+:\d+(\/.*)?$/.test(token)) {
185
+ host = token.split("/")[0];
186
+ } else if (/^(localhost|127\.0\.0\.1|\[?::1\]?)(\/.*)?$/.test(token)) {
187
+ host = token.split("/")[0];
188
+ }
189
+ if (host === null) continue;
190
+ const bare = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
191
+ if (LOCAL_HOST_RE.test(bare)) {
192
+ localCount++;
193
+ } else {
194
+ return false;
195
+ }
196
+ }
197
+ return localCount > 0;
198
+ }
199
+
40
200
  // src/local-evaluator/evaluator.ts
41
201
  var LocalEvaluator = class {
42
202
  constructor(policy) {
@@ -187,12 +347,10 @@ var LocalEvaluator = class {
187
347
  return score;
188
348
  }
189
349
  if (context.purpose === "shell.exec" && context.target) {
190
- const targetLower = context.target.toLowerCase();
191
- for (const cmd of HIGH_RISK_COMMANDS) {
192
- if (targetLower.startsWith(cmd) || targetLower.includes(` ${cmd} `) || targetLower.includes(` ${cmd}`)) {
193
- score = Math.max(score, 80);
194
- break;
195
- }
350
+ const matched = matchHighRiskCommands(context.target);
351
+ const localProbeOnly = matched.length > 0 && matched.every((cmd) => cmd === "curl" || cmd === "wget") && isLocalhostOnlyNetworkCommand(context.target);
352
+ if (matched.length > 0 && !localProbeOnly) {
353
+ score = Math.max(score, 80);
196
354
  }
197
355
  }
198
356
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
@@ -3503,7 +3661,7 @@ function getTrustLevel(score) {
3503
3661
  }
3504
3662
 
3505
3663
  // src/version.ts
3506
- var SDK_VERSION = "3.10.0";
3664
+ var SDK_VERSION = "3.10.1";
3507
3665
 
3508
3666
  // src/well-known.ts
3509
3667
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -4064,8 +4222,75 @@ async function verifyOnline(config, context, astraId) {
4064
4222
  // src/gateway/modes/hybrid.ts
4065
4223
  import { existsSync, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
4066
4224
  import { dirname } from "path";
4225
+
4226
+ // src/gateway/redact.ts
4227
+ var marker = (type2) => `[REDACTED:${type2}]`;
4228
+ var REDACTION_PATTERNS = [
4229
+ // Multi-line blocks first so later single-line patterns don't shred them.
4230
+ {
4231
+ type: "private-key",
4232
+ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g
4233
+ },
4234
+ // URL userinfo credentials (https://user:pass@, x-access-token:ghp_…@).
4235
+ {
4236
+ type: "url-credentials",
4237
+ re: /([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+:[^\s/@]+@/gi,
4238
+ replacement: `$1${marker("url-credentials")}@`
4239
+ },
4240
+ // Known token formats.
4241
+ { type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,255}\b/g },
4242
+ { type: "github-token", re: /\bgithub_pat_[A-Za-z0-9_]{20,255}\b/g },
4243
+ { type: "astrasync-key", re: /\bkya_[A-Fa-f0-9]{32,128}\b/g },
4244
+ { type: "openai-key", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g },
4245
+ { type: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/g },
4246
+ { type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
4247
+ // Bearer/token headers.
4248
+ {
4249
+ type: "bearer-token",
4250
+ re: /\b(bearer\s+)[a-z0-9._~+/=-]{16,}/gi,
4251
+ replacement: `$1${marker("bearer-token")}`
4252
+ },
4253
+ // Generic KEY=value / key: value assignments with secret-ish names.
4254
+ {
4255
+ type: "assigned-secret",
4256
+ re: /\b((?:password|passwd|secret|token|api[_-]?key|apikey|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[=:]\s*)(['"]?)[^\s'"&;]{8,}\2/gi,
4257
+ replacement: `$1$2${marker("assigned-secret")}$2`
4258
+ }
4259
+ ];
4260
+ function redactSecrets(text) {
4261
+ let result = text;
4262
+ for (const { type: type2, re, replacement } of REDACTION_PATTERNS) {
4263
+ result = result.replace(re, replacement ?? marker(type2));
4264
+ }
4265
+ return result;
4266
+ }
4267
+ function redactDeep(value) {
4268
+ if (typeof value === "string") {
4269
+ return redactSecrets(value);
4270
+ }
4271
+ if (Array.isArray(value)) {
4272
+ return value.map((item) => redactDeep(item));
4273
+ }
4274
+ if (value !== null && typeof value === "object" && !(value instanceof Date)) {
4275
+ const out = {};
4276
+ for (const [k, v] of Object.entries(value)) {
4277
+ out[k] = redactDeep(v);
4278
+ }
4279
+ return out;
4280
+ }
4281
+ return value;
4282
+ }
4283
+
4284
+ // src/gateway/modes/hybrid.ts
4067
4285
  var MAX_QUEUE_SIZE = 1e3;
4068
4286
  var MAX_RETRIES = 3;
4287
+ var FLUSH_TIMEOUT = /* @__PURE__ */ Symbol("flush-timeout");
4288
+ function delay(ms) {
4289
+ return new Promise((resolve) => {
4290
+ const timer = setTimeout(() => resolve(FLUSH_TIMEOUT), ms);
4291
+ if (typeof timer.unref === "function") timer.unref();
4292
+ });
4293
+ }
4069
4294
  var HybridSyncQueue = class {
4070
4295
  constructor(config, persistPath) {
4071
4296
  this.queue = [];
@@ -4086,12 +4311,12 @@ var HybridSyncQueue = class {
4086
4311
  /**
4087
4312
  * Stop periodic sync and flush remaining entries.
4088
4313
  */
4089
- async stop() {
4314
+ async stop(flushOptions = {}) {
4090
4315
  if (this.timer) {
4091
4316
  clearInterval(this.timer);
4092
4317
  this.timer = null;
4093
4318
  }
4094
- await this.flush();
4319
+ await this.flush(flushOptions);
4095
4320
  this.saveToDisk();
4096
4321
  }
4097
4322
  /**
@@ -4103,8 +4328,8 @@ var HybridSyncQueue = class {
4103
4328
  }
4104
4329
  this.queue.push({
4105
4330
  id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
4106
- context,
4107
- decision,
4331
+ context: redactDeep(context),
4332
+ decision: redactDeep(decision),
4108
4333
  timestamp: /* @__PURE__ */ new Date(),
4109
4334
  retryCount: 0,
4110
4335
  status: "pending"
@@ -4114,16 +4339,21 @@ var HybridSyncQueue = class {
4114
4339
  /**
4115
4340
  * Flush pending entries to the API.
4116
4341
  */
4117
- async flush() {
4342
+ async flush(options = {}) {
4118
4343
  if (this.processing) return;
4119
4344
  this.processing = true;
4120
4345
  try {
4121
4346
  const pending = this.queue.filter((e) => e.status === "pending");
4122
- for (const entry of pending) {
4123
- try {
4124
- await verifyOnline(this.config, entry.context);
4347
+ const batch = typeof options.limit === "number" ? pending.slice(0, options.limit) : pending;
4348
+ for (const entry of batch) {
4349
+ const attempt = verifyOnline(this.config, entry.context, this.config.agentId).then(() => "synced").catch(() => "error");
4350
+ const result = options.perEntryTimeoutMs ? await Promise.race([attempt, delay(options.perEntryTimeoutMs)]) : await attempt;
4351
+ if (result === FLUSH_TIMEOUT) {
4352
+ break;
4353
+ }
4354
+ if (result === "synced") {
4125
4355
  entry.status = "synced";
4126
- } catch {
4356
+ } else {
4127
4357
  entry.retryCount++;
4128
4358
  if (entry.retryCount >= MAX_RETRIES) {
4129
4359
  entry.status = "failed";
@@ -4200,8 +4430,8 @@ var TraceLogger = class {
4200
4430
  id: this.generateId(),
4201
4431
  timestamp: /* @__PURE__ */ new Date(),
4202
4432
  type: "evaluation",
4203
- context,
4204
- decision
4433
+ context: redactDeep(context),
4434
+ decision: redactDeep(decision)
4205
4435
  });
4206
4436
  }
4207
4437
  /**
@@ -4318,10 +4548,12 @@ var AstraSyncGateway = class {
4318
4548
  }
4319
4549
  /**
4320
4550
  * Graceful shutdown. Flushes sync queue and cleans up resources.
4551
+ * Stateless callers (hooks) pass bounded flush options so sync never
4552
+ * sits on a tool call's critical path.
4321
4553
  */
4322
- async shutdown() {
4554
+ async shutdown(options) {
4323
4555
  if (this.syncQueue) {
4324
- await this.syncQueue.stop();
4556
+ await this.syncQueue.stop(options?.syncFlush ?? {});
4325
4557
  this.syncQueue = null;
4326
4558
  }
4327
4559
  this.evaluator = null;
@@ -4344,7 +4576,7 @@ var AstraSyncGateway = class {
4344
4576
  let decision;
4345
4577
  switch (this.config.mode) {
4346
4578
  case "online":
4347
- decision = await verifyOnline(this.config, context, astraId);
4579
+ decision = await verifyOnline(this.config, context, astraId ?? this.config.agentId);
4348
4580
  break;
4349
4581
  case "local":
4350
4582
  if (!this.evaluator) throw new Error("Local evaluator not initialized");