@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
@@ -49,7 +49,9 @@ var HIGH_RISK_COMMANDS = [
49
49
  "git push",
50
50
  "npm publish",
51
51
  "docker",
52
- "kubectl"
52
+ "kubectl",
53
+ "gh pr create",
54
+ "gh pr merge"
53
55
  ];
54
56
  var SENSITIVE_PATHS = [
55
57
  ".ssh",
@@ -68,6 +70,164 @@ var SENSITIVE_PATHS = [
68
70
  ".pypirc"
69
71
  ];
70
72
 
73
+ // src/local-evaluator/command-matcher.ts
74
+ var WRAPPERS = /* @__PURE__ */ new Set([
75
+ "sudo",
76
+ "doas",
77
+ "env",
78
+ "nohup",
79
+ "time",
80
+ "timeout",
81
+ "nice",
82
+ "ionice",
83
+ "stdbuf",
84
+ "xargs",
85
+ "command",
86
+ "exec",
87
+ "builtin"
88
+ ]);
89
+ var ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;
90
+ var MAX_WRAPPER_HOPS = 4;
91
+ var MAX_RECURSION_DEPTH = 3;
92
+ var MAX_COMMAND_LENGTH = 1e5;
93
+ var LOCAL_HOST_RE = /^(localhost|127\.0\.0\.1|::1|[a-z0-9.-]*\.localhost)$/;
94
+ function buildRiskList(entries) {
95
+ const singleWord = /* @__PURE__ */ new Set();
96
+ const multiWord = [];
97
+ for (const entry of entries) {
98
+ const words = entry.toLowerCase().trim().split(/\s+/);
99
+ if (words.length === 1) singleWord.add(words[0]);
100
+ else multiWord.push(words);
101
+ }
102
+ multiWord.sort((a, b) => b.length - a.length);
103
+ return { singleWord, multiWord };
104
+ }
105
+ function stripHeredocBodies(text) {
106
+ const lines = text.split("\n");
107
+ const kept = [];
108
+ let terminator = null;
109
+ for (const line of lines) {
110
+ if (terminator !== null) {
111
+ if (line.trim() === terminator) terminator = null;
112
+ continue;
113
+ }
114
+ kept.push(line);
115
+ const heredoc = line.match(/<<-?\s*['"]?(\w+)['"]?/);
116
+ if (heredoc) terminator = heredoc[1];
117
+ }
118
+ return kept.join("\n");
119
+ }
120
+ function extractDollarSubstitutions(text, collect) {
121
+ let result = text;
122
+ let start = result.indexOf("$(");
123
+ let iterations = 0;
124
+ while (start !== -1 && iterations < 50) {
125
+ iterations++;
126
+ let depth = 0;
127
+ let end = result.length;
128
+ for (let i = start + 1; i < result.length; i++) {
129
+ if (result[i] === "(") depth++;
130
+ else if (result[i] === ")") {
131
+ depth--;
132
+ if (depth === 0) {
133
+ end = i;
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ collect(result.slice(start + 2, end));
139
+ result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;
140
+ start = result.indexOf("$(");
141
+ }
142
+ return result;
143
+ }
144
+ function extractBacktickSubstitutions(text, collect) {
145
+ if (!text.includes("`")) return text;
146
+ const chunks = text.split("`");
147
+ const kept = [];
148
+ for (let i = 0; i < chunks.length; i++) {
149
+ if (i % 2 === 1) collect(chunks[i]);
150
+ else kept.push(chunks[i]);
151
+ }
152
+ return kept.join(" ");
153
+ }
154
+ function basename(token) {
155
+ const slash = token.lastIndexOf("/");
156
+ return slash === -1 ? token : token.slice(slash + 1);
157
+ }
158
+ function matchSegment(segment, list) {
159
+ const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
160
+ let i = 0;
161
+ let hops = 0;
162
+ while (i < tokens.length) {
163
+ while (i < tokens.length && (tokens[i] === "export" || ASSIGN_RE.test(tokens[i]))) i++;
164
+ if (i >= tokens.length) return null;
165
+ const cmd = basename(tokens[i]);
166
+ const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith("-"))];
167
+ for (const entry of list.multiWord) {
168
+ if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {
169
+ return entry.join(" ");
170
+ }
171
+ }
172
+ if (list.singleWord.has(cmd)) return cmd;
173
+ if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {
174
+ hops++;
175
+ i++;
176
+ while (i < tokens.length && (tokens[i].startsWith("-") || ASSIGN_RE.test(tokens[i]))) i++;
177
+ continue;
178
+ }
179
+ return null;
180
+ }
181
+ return null;
182
+ }
183
+ function collectMatches(command, list, matches, depth) {
184
+ if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;
185
+ let text = command.toLowerCase();
186
+ text = stripHeredocBodies(text);
187
+ const recurse = (inner) => collectMatches(inner, list, matches, depth + 1);
188
+ text = extractDollarSubstitutions(text, recurse);
189
+ text = extractBacktickSubstitutions(text, recurse);
190
+ text = text.replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
191
+ text = text.replace(/(^|\s)#[^\n]*/g, " ");
192
+ const segments = text.split(/\|\||&&|[;|&\n]|[({]/);
193
+ for (const segment of segments) {
194
+ const matched = matchSegment(segment, list);
195
+ if (matched) matches.add(matched);
196
+ }
197
+ }
198
+ function matchHighRiskCommands(command, highRiskCommands = HIGH_RISK_COMMANDS) {
199
+ if (!command) return [];
200
+ const matches = /* @__PURE__ */ new Set();
201
+ collectMatches(command, buildRiskList(highRiskCommands), matches, 0);
202
+ return [...matches];
203
+ }
204
+ function isLocalhostOnlyNetworkCommand(command) {
205
+ const text = command.toLowerCase();
206
+ const tokens = text.split(/\s+/).map((t) => t.replace(/^["'`]+|["'`,;)]+$/g, ""));
207
+ let localCount = 0;
208
+ for (const token of tokens) {
209
+ let host = null;
210
+ if (token.includes("://")) {
211
+ let rest = token.slice(token.indexOf("://") + 3);
212
+ const at = rest.indexOf("@");
213
+ if (at !== -1) rest = rest.slice(at + 1);
214
+ host = rest.split(/[/?#]/)[0];
215
+ } else if (/^[a-z0-9.[\]:-]+:\d+(\/.*)?$/.test(token)) {
216
+ host = token.split("/")[0];
217
+ } else if (/^(localhost|127\.0\.0\.1|\[?::1\]?)(\/.*)?$/.test(token)) {
218
+ host = token.split("/")[0];
219
+ }
220
+ if (host === null) continue;
221
+ const bare = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
222
+ if (LOCAL_HOST_RE.test(bare)) {
223
+ localCount++;
224
+ } else {
225
+ return false;
226
+ }
227
+ }
228
+ return localCount > 0;
229
+ }
230
+
71
231
  // src/local-evaluator/evaluator.ts
72
232
  var LocalEvaluator = class {
73
233
  constructor(policy) {
@@ -218,12 +378,10 @@ var LocalEvaluator = class {
218
378
  return score;
219
379
  }
220
380
  if (context.purpose === "shell.exec" && context.target) {
221
- const targetLower = context.target.toLowerCase();
222
- for (const cmd of HIGH_RISK_COMMANDS) {
223
- if (targetLower.startsWith(cmd) || targetLower.includes(` ${cmd} `) || targetLower.includes(` ${cmd}`)) {
224
- score = Math.max(score, 80);
225
- break;
226
- }
381
+ const matched = matchHighRiskCommands(context.target);
382
+ const localProbeOnly = matched.length > 0 && matched.every((cmd) => cmd === "curl" || cmd === "wget") && isLocalhostOnlyNetworkCommand(context.target);
383
+ if (matched.length > 0 && !localProbeOnly) {
384
+ score = Math.max(score, 80);
227
385
  }
228
386
  }
229
387
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
@@ -3534,7 +3692,7 @@ function getTrustLevel(score) {
3534
3692
  }
3535
3693
 
3536
3694
  // src/version.ts
3537
- var SDK_VERSION = "3.10.0";
3695
+ var SDK_VERSION = "3.10.1";
3538
3696
 
3539
3697
  // src/well-known.ts
3540
3698
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -4092,8 +4250,75 @@ async function verifyOnline(config, context, astraId) {
4092
4250
  // src/gateway/modes/hybrid.ts
4093
4251
  var import_fs2 = require("fs");
4094
4252
  var import_path = require("path");
4253
+
4254
+ // src/gateway/redact.ts
4255
+ var marker = (type2) => `[REDACTED:${type2}]`;
4256
+ var REDACTION_PATTERNS = [
4257
+ // Multi-line blocks first so later single-line patterns don't shred them.
4258
+ {
4259
+ type: "private-key",
4260
+ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g
4261
+ },
4262
+ // URL userinfo credentials (https://user:pass@, x-access-token:ghp_…@).
4263
+ {
4264
+ type: "url-credentials",
4265
+ re: /([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+:[^\s/@]+@/gi,
4266
+ replacement: `$1${marker("url-credentials")}@`
4267
+ },
4268
+ // Known token formats.
4269
+ { type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,255}\b/g },
4270
+ { type: "github-token", re: /\bgithub_pat_[A-Za-z0-9_]{20,255}\b/g },
4271
+ { type: "astrasync-key", re: /\bkya_[A-Fa-f0-9]{32,128}\b/g },
4272
+ { type: "openai-key", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g },
4273
+ { type: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/g },
4274
+ { type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
4275
+ // Bearer/token headers.
4276
+ {
4277
+ type: "bearer-token",
4278
+ re: /\b(bearer\s+)[a-z0-9._~+/=-]{16,}/gi,
4279
+ replacement: `$1${marker("bearer-token")}`
4280
+ },
4281
+ // Generic KEY=value / key: value assignments with secret-ish names.
4282
+ {
4283
+ type: "assigned-secret",
4284
+ re: /\b((?:password|passwd|secret|token|api[_-]?key|apikey|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[=:]\s*)(['"]?)[^\s'"&;]{8,}\2/gi,
4285
+ replacement: `$1$2${marker("assigned-secret")}$2`
4286
+ }
4287
+ ];
4288
+ function redactSecrets(text) {
4289
+ let result = text;
4290
+ for (const { type: type2, re, replacement } of REDACTION_PATTERNS) {
4291
+ result = result.replace(re, replacement ?? marker(type2));
4292
+ }
4293
+ return result;
4294
+ }
4295
+ function redactDeep(value) {
4296
+ if (typeof value === "string") {
4297
+ return redactSecrets(value);
4298
+ }
4299
+ if (Array.isArray(value)) {
4300
+ return value.map((item) => redactDeep(item));
4301
+ }
4302
+ if (value !== null && typeof value === "object" && !(value instanceof Date)) {
4303
+ const out = {};
4304
+ for (const [k, v] of Object.entries(value)) {
4305
+ out[k] = redactDeep(v);
4306
+ }
4307
+ return out;
4308
+ }
4309
+ return value;
4310
+ }
4311
+
4312
+ // src/gateway/modes/hybrid.ts
4095
4313
  var MAX_QUEUE_SIZE = 1e3;
4096
4314
  var MAX_RETRIES = 3;
4315
+ var FLUSH_TIMEOUT = /* @__PURE__ */ Symbol("flush-timeout");
4316
+ function delay(ms) {
4317
+ return new Promise((resolve) => {
4318
+ const timer = setTimeout(() => resolve(FLUSH_TIMEOUT), ms);
4319
+ if (typeof timer.unref === "function") timer.unref();
4320
+ });
4321
+ }
4097
4322
  var HybridSyncQueue = class {
4098
4323
  constructor(config, persistPath) {
4099
4324
  this.queue = [];
@@ -4114,12 +4339,12 @@ var HybridSyncQueue = class {
4114
4339
  /**
4115
4340
  * Stop periodic sync and flush remaining entries.
4116
4341
  */
4117
- async stop() {
4342
+ async stop(flushOptions = {}) {
4118
4343
  if (this.timer) {
4119
4344
  clearInterval(this.timer);
4120
4345
  this.timer = null;
4121
4346
  }
4122
- await this.flush();
4347
+ await this.flush(flushOptions);
4123
4348
  this.saveToDisk();
4124
4349
  }
4125
4350
  /**
@@ -4131,8 +4356,8 @@ var HybridSyncQueue = class {
4131
4356
  }
4132
4357
  this.queue.push({
4133
4358
  id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
4134
- context,
4135
- decision,
4359
+ context: redactDeep(context),
4360
+ decision: redactDeep(decision),
4136
4361
  timestamp: /* @__PURE__ */ new Date(),
4137
4362
  retryCount: 0,
4138
4363
  status: "pending"
@@ -4142,16 +4367,21 @@ var HybridSyncQueue = class {
4142
4367
  /**
4143
4368
  * Flush pending entries to the API.
4144
4369
  */
4145
- async flush() {
4370
+ async flush(options = {}) {
4146
4371
  if (this.processing) return;
4147
4372
  this.processing = true;
4148
4373
  try {
4149
4374
  const pending = this.queue.filter((e) => e.status === "pending");
4150
- for (const entry of pending) {
4151
- try {
4152
- await verifyOnline(this.config, entry.context);
4375
+ const batch = typeof options.limit === "number" ? pending.slice(0, options.limit) : pending;
4376
+ for (const entry of batch) {
4377
+ const attempt = verifyOnline(this.config, entry.context, this.config.agentId).then(() => "synced").catch(() => "error");
4378
+ const result = options.perEntryTimeoutMs ? await Promise.race([attempt, delay(options.perEntryTimeoutMs)]) : await attempt;
4379
+ if (result === FLUSH_TIMEOUT) {
4380
+ break;
4381
+ }
4382
+ if (result === "synced") {
4153
4383
  entry.status = "synced";
4154
- } catch {
4384
+ } else {
4155
4385
  entry.retryCount++;
4156
4386
  if (entry.retryCount >= MAX_RETRIES) {
4157
4387
  entry.status = "failed";
@@ -4228,8 +4458,8 @@ var TraceLogger = class {
4228
4458
  id: this.generateId(),
4229
4459
  timestamp: /* @__PURE__ */ new Date(),
4230
4460
  type: "evaluation",
4231
- context,
4232
- decision
4461
+ context: redactDeep(context),
4462
+ decision: redactDeep(decision)
4233
4463
  });
4234
4464
  }
4235
4465
  /**
@@ -4346,10 +4576,12 @@ var AstraSyncGateway = class {
4346
4576
  }
4347
4577
  /**
4348
4578
  * Graceful shutdown. Flushes sync queue and cleans up resources.
4579
+ * Stateless callers (hooks) pass bounded flush options so sync never
4580
+ * sits on a tool call's critical path.
4349
4581
  */
4350
- async shutdown() {
4582
+ async shutdown(options) {
4351
4583
  if (this.syncQueue) {
4352
- await this.syncQueue.stop();
4584
+ await this.syncQueue.stop(options?.syncFlush ?? {});
4353
4585
  this.syncQueue = null;
4354
4586
  }
4355
4587
  this.evaluator = null;
@@ -4372,7 +4604,7 @@ var AstraSyncGateway = class {
4372
4604
  let decision;
4373
4605
  switch (this.config.mode) {
4374
4606
  case "online":
4375
- decision = await verifyOnline(this.config, context, astraId);
4607
+ decision = await verifyOnline(this.config, context, astraId ?? this.config.agentId);
4376
4608
  break;
4377
4609
  case "local":
4378
4610
  if (!this.evaluator) throw new Error("Local evaluator not initialized");
@@ -4677,6 +4909,8 @@ var ClaudeCodeAdapter = class {
4677
4909
  // src/claude-code/hook.ts
4678
4910
  var MAX_STDIN_BYTES = 1024 * 1024;
4679
4911
  var STDIN_TIMEOUT_MS = 5e3;
4912
+ var HYBRID_FLUSH_LIMIT = 3;
4913
+ var HYBRID_FLUSH_TIMEOUT_MS = 1500;
4680
4914
  function resolveGuardConfig(env, payloadCwd, home = (0, import_os.homedir)()) {
4681
4915
  const candidateRoots = [env.CLAUDE_PROJECT_DIR, payloadCwd, home].filter(
4682
4916
  (root) => typeof root === "string" && root.length > 0
@@ -4729,13 +4963,25 @@ async function processHookPayload(payloadJson, options = {}) {
4729
4963
  }
4730
4964
  const failOpen = envFailOpen || resolved.settings.failOpen === true;
4731
4965
  try {
4966
+ let mode = resolved.settings.mode === "hybrid" ? "hybrid" : "local";
4967
+ if (mode === "hybrid" && !(resolved.settings.apiBaseUrl && resolved.settings.apiKey)) {
4968
+ console.error(
4969
+ "AstraSync Local Guard: guard.json requests hybrid mode but apiBaseUrl/apiKey are missing \u2014 running local-only"
4970
+ );
4971
+ mode = "local";
4972
+ }
4732
4973
  const gatewayConfig = {
4733
- mode: "local",
4974
+ mode,
4734
4975
  policyFile: resolved.policyFile,
4735
4976
  posture: resolved.settings.posture ?? "active",
4736
4977
  traceEnabled: true,
4737
4978
  tracePath: (0, import_path3.join)(resolved.guardDir, "traces"),
4738
- debug: false
4979
+ debug: false,
4980
+ ...mode === "hybrid" && {
4981
+ apiBaseUrl: resolved.settings.apiBaseUrl,
4982
+ apiKey: resolved.settings.apiKey,
4983
+ agentId: resolved.settings.agentId
4984
+ }
4739
4985
  };
4740
4986
  const gateway = options.gatewayFactory ? options.gatewayFactory(gatewayConfig) : new AstraSyncGateway(gatewayConfig);
4741
4987
  await gateway.initialize();
@@ -4748,7 +4994,9 @@ async function processHookPayload(payloadJson, options = {}) {
4748
4994
  await adapter.initialize({ gateway, adapterOptions: {} });
4749
4995
  const output = await adapter.evaluatePayload(payload);
4750
4996
  await adapter.shutdown();
4751
- await gateway.shutdown();
4997
+ await gateway.shutdown({
4998
+ syncFlush: { limit: HYBRID_FLUSH_LIMIT, perEntryTimeoutMs: HYBRID_FLUSH_TIMEOUT_MS }
4999
+ });
4752
5000
  return { output, exitCode: 0 };
4753
5001
  } catch (err) {
4754
5002
  if (failOpen) return { output: null, exitCode: 0 };