@astrasyncai/verification-gateway 3.10.1 → 3.11.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 (77) 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 +105 -27
  20. package/dist/bin/astrasync-codex-hook.js +5151 -0
  21. package/dist/bin/astrasync-guard.js +234 -32
  22. package/dist/bin/astrasync.js +242 -32
  23. package/dist/browser/background.js +84 -17
  24. package/dist/browser/background.js.map +1 -1
  25. package/dist/browser/background.mjs +84 -17
  26. package/dist/browser/background.mjs.map +1 -1
  27. package/dist/browser/browser-adapter.d.mts +1 -1
  28. package/dist/browser/browser-adapter.d.ts +1 -1
  29. package/dist/claude-code/claude-code-adapter.d.mts +1 -1
  30. package/dist/claude-code/claude-code-adapter.d.ts +1 -1
  31. package/dist/claude-code/claude-code-adapter.js +9 -1
  32. package/dist/claude-code/claude-code-adapter.js.map +1 -1
  33. package/dist/claude-code/claude-code-adapter.mjs +9 -1
  34. package/dist/claude-code/claude-code-adapter.mjs.map +1 -1
  35. package/dist/cli/index.d.mts +1 -1
  36. package/dist/cli/index.d.ts +1 -1
  37. package/dist/codex/index.d.mts +230 -0
  38. package/dist/codex/index.d.ts +230 -0
  39. package/dist/codex/index.js +5269 -0
  40. package/dist/codex/index.js.map +1 -0
  41. package/dist/codex/index.mjs +5241 -0
  42. package/dist/codex/index.mjs.map +1 -0
  43. package/dist/cursor/cursor-adapter.d.mts +1 -1
  44. package/dist/cursor/cursor-adapter.d.ts +1 -1
  45. package/dist/cursor/extension.d.mts +1 -1
  46. package/dist/cursor/extension.d.ts +1 -1
  47. package/dist/cursor/extension.js +84 -17
  48. package/dist/cursor/extension.js.map +1 -1
  49. package/dist/cursor/extension.mjs +84 -17
  50. package/dist/cursor/extension.mjs.map +1 -1
  51. package/dist/gateway/gateway.d.mts +6 -1
  52. package/dist/gateway/gateway.d.ts +6 -1
  53. package/dist/gateway/gateway.js +84 -17
  54. package/dist/gateway/gateway.js.map +1 -1
  55. package/dist/gateway/gateway.mjs +84 -17
  56. package/dist/gateway/gateway.mjs.map +1 -1
  57. package/dist/git-trigger/git-hooks.d.mts +1 -1
  58. package/dist/git-trigger/git-hooks.d.ts +1 -1
  59. package/dist/index.d.mts +1 -1
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.js +1 -1
  62. package/dist/index.js.map +1 -1
  63. package/dist/index.mjs +1 -1
  64. package/dist/index.mjs.map +1 -1
  65. package/dist/local-evaluator/evaluator.d.mts +10 -1
  66. package/dist/local-evaluator/evaluator.d.ts +10 -1
  67. package/dist/local-evaluator/evaluator.js +20 -9
  68. package/dist/local-evaluator/evaluator.js.map +1 -1
  69. package/dist/local-evaluator/evaluator.mjs +20 -9
  70. package/dist/local-evaluator/evaluator.mjs.map +1 -1
  71. package/dist/{types-C23_uaY8.d.ts → types-DhFPiZ3t.d.ts} +6 -1
  72. package/dist/{types-BV6abBnF.d.mts → types-cQVCMeNV.d.mts} +6 -1
  73. package/dist/verify.js +1 -1
  74. package/dist/verify.js.map +1 -1
  75. package/dist/verify.mjs +1 -1
  76. package/dist/verify.mjs.map +1 -1
  77. package/package.json +8 -2
@@ -4628,8 +4628,9 @@ function extractBacktickSubstitutions(text, collect) {
4628
4628
  return kept.join(" ");
4629
4629
  }
4630
4630
  function basename(token) {
4631
- const slash = token.lastIndexOf("/");
4632
- return slash === -1 ? token : token.slice(slash + 1);
4631
+ const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
4632
+ const name = slash === -1 ? token : token.slice(slash + 1);
4633
+ return name.replace(/\.(exe|cmd|bat|ps1)$/i, "");
4633
4634
  }
4634
4635
  function matchSegment(segment, list) {
4635
4636
  const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
@@ -4795,7 +4796,9 @@ var init_evaluator = __esm({
4795
4796
  }
4796
4797
  return {
4797
4798
  recommendation: "ALLOW",
4798
- reason: "All PDLSS checks passed",
4799
+ // "evaluated": dimensions absent from the policy are skipped, not
4800
+ // failed — evaluatedDimensions below records which ones ran.
4801
+ reason: "All evaluated PDLSS checks passed",
4799
4802
  evaluatedDimensions: {
4800
4803
  purpose: true,
4801
4804
  scope: !!this.policy.scope,
@@ -4894,7 +4897,7 @@ var init_evaluator = __esm({
4894
4897
  }
4895
4898
  }
4896
4899
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
4897
- const targetLower = context.target.toLowerCase();
4900
+ const targetLower = context.target.toLowerCase().replace(/\\/g, "/");
4898
4901
  for (const sensitivePath of SENSITIVE_PATHS) {
4899
4902
  if (targetLower.includes(sensitivePath.toLowerCase())) {
4900
4903
  score = Math.max(score, 50);
@@ -4923,12 +4926,20 @@ var init_evaluator = __esm({
4923
4926
  matchesAnyPattern(value, patterns) {
4924
4927
  return patterns.some((pattern) => this.matchGlob(value, pattern));
4925
4928
  }
4926
- /**
4927
- * Simple glob pattern matching.
4928
- * Supports * (matches any characters including / and spaces) and ? (single char).
4929
- * Uses the same approach as the backend's matchGlobPattern.
4930
- */
4929
+ homePrefix() {
4930
+ if (this.cachedHomePrefix !== void 0) return this.cachedHomePrefix;
4931
+ const env = typeof process !== "undefined" ? process.env : void 0;
4932
+ const home = env?.HOME || env?.USERPROFILE;
4933
+ this.cachedHomePrefix = home ? home.replace(/\\/g, "/") : null;
4934
+ return this.cachedHomePrefix;
4935
+ }
4931
4936
  matchGlob(value, pattern) {
4937
+ value = value.replace(/\\/g, "/");
4938
+ pattern = pattern.replace(/\\/g, "/");
4939
+ if (pattern === "~" || pattern.startsWith("~/")) {
4940
+ const home = this.homePrefix();
4941
+ if (home) pattern = home + pattern.slice(1);
4942
+ }
4932
4943
  if (pattern === value) return true;
4933
4944
  const starCount = (pattern.match(/\*/g) ?? []).length;
4934
4945
  if (starCount > 8) {
@@ -4989,7 +5000,7 @@ var SDK_VERSION;
4989
5000
  var init_version = __esm({
4990
5001
  "src/version.ts"() {
4991
5002
  "use strict";
4992
- SDK_VERSION = "3.10.1";
5003
+ SDK_VERSION = "3.11.1";
4993
5004
  }
4994
5005
  });
4995
5006
 
@@ -5959,10 +5970,12 @@ function toGatewayConfig(config) {
5959
5970
  };
5960
5971
  }
5961
5972
  function toVerificationRequest(context, astraId) {
5973
+ const dotIndex = context.purpose.indexOf(".");
5974
+ const dotted = dotIndex > 0;
5962
5975
  return {
5963
5976
  credentials: { astraId },
5964
- purpose: context.purpose,
5965
- action: context.action,
5977
+ purpose: dotted ? context.purpose.slice(0, dotIndex) : context.purpose,
5978
+ action: dotted ? context.purpose : context.action,
5966
5979
  resourceType: context.resourceType,
5967
5980
  resource: context.target,
5968
5981
  transactionValue: context.transactionValue,
@@ -6228,13 +6241,43 @@ var init_trace_logger = __esm({
6228
6241
  * Log an evaluation event.
6229
6242
  */
6230
6243
  logEvaluation(context, decision) {
6244
+ if (!this.enabled) return;
6245
+ let event;
6246
+ try {
6247
+ event = {
6248
+ id: this.generateId(),
6249
+ timestamp: /* @__PURE__ */ new Date(),
6250
+ type: "evaluation",
6251
+ context: redactDeep(context),
6252
+ decision: redactDeep(decision)
6253
+ };
6254
+ } catch (err) {
6255
+ event = {
6256
+ id: this.generateId(),
6257
+ timestamp: /* @__PURE__ */ new Date(),
6258
+ type: "error",
6259
+ metadata: {
6260
+ error: `trace redaction failed: ${err instanceof Error ? err.message : String(err)}`,
6261
+ recommendation: decision.recommendation,
6262
+ purpose: context.purpose,
6263
+ action: context.action
6264
+ }
6265
+ };
6266
+ }
6267
+ this.write(event);
6268
+ }
6269
+ /**
6270
+ * Log a tool call the guard saw but did not govern. One line per skip
6271
+ * keeps coverage holes (unmapped platform tools) visible in the traces
6272
+ * instead of silently indistinguishable from ALLOW.
6273
+ */
6274
+ logSkipped(toolName, reason) {
6231
6275
  if (!this.enabled) return;
6232
6276
  this.write({
6233
6277
  id: this.generateId(),
6234
6278
  timestamp: /* @__PURE__ */ new Date(),
6235
- type: "evaluation",
6236
- context: redactDeep(context),
6237
- decision: redactDeep(decision)
6279
+ type: "skipped",
6280
+ metadata: { toolName, reason }
6238
6281
  });
6239
6282
  }
6240
6283
  /**
@@ -6263,17 +6306,34 @@ var init_trace_logger = __esm({
6263
6306
  });
6264
6307
  }
6265
6308
  write(event) {
6309
+ let line;
6310
+ try {
6311
+ line = JSON.stringify(event) + "\n";
6312
+ } catch {
6313
+ return;
6314
+ }
6266
6315
  try {
6267
6316
  const filePath = this.getCurrentFilePath();
6268
- const line = JSON.stringify(event) + "\n";
6269
6317
  if ((0, import_fs3.existsSync)(filePath)) {
6270
6318
  const stats = (0, import_fs3.statSync)(filePath);
6271
6319
  if (stats.size > MAX_FILE_SIZE) {
6272
6320
  this.rotate();
6273
6321
  }
6274
6322
  }
6275
- (0, import_fs3.appendFileSync)(filePath, line);
6323
+ for (let attempt = 0; attempt < 3; attempt++) {
6324
+ try {
6325
+ (0, import_fs3.appendFileSync)(filePath, line);
6326
+ return;
6327
+ } catch {
6328
+ }
6329
+ }
6330
+ const fallbackPath = filePath.replace(/\.jsonl$/, `-r${process.pid}.jsonl`);
6331
+ (0, import_fs3.appendFileSync)(fallbackPath, line);
6276
6332
  } catch {
6333
+ try {
6334
+ console.error(`[AstraSync TraceLogger] failed to persist trace line: ${line.trim()}`);
6335
+ } catch {
6336
+ }
6277
6337
  }
6278
6338
  }
6279
6339
  getCurrentFilePath() {
@@ -6445,6 +6505,13 @@ var init_gateway = __esm({
6445
6505
  getSyncStatus() {
6446
6506
  return this.syncQueue?.getStatus() || null;
6447
6507
  }
6508
+ /**
6509
+ * Record a tool call the calling adapter saw but did not govern
6510
+ * (unmapped tool, skipTools, MCP with governMcp off). Trace-only.
6511
+ */
6512
+ logSkippedTool(toolName, reason) {
6513
+ this.traceLogger?.logSkipped(toolName, reason);
6514
+ }
6448
6515
  };
6449
6516
  }
6450
6517
  });
@@ -6579,9 +6646,9 @@ var init_git_hooks = __esm({
6579
6646
  });
6580
6647
 
6581
6648
  // src/claude-code/settings-installer.ts
6582
- function isGuardEntry(entry) {
6649
+ function isGuardEntry(entry, marker2) {
6583
6650
  return (entry.hooks ?? []).some(
6584
- (h) => typeof h.command === "string" && h.command.includes(ASTRASYNC_HOOK_MARKER)
6651
+ (h) => typeof h.command === "string" && h.command.includes(marker2)
6585
6652
  );
6586
6653
  }
6587
6654
  function readSettings(settingsPath) {
@@ -6595,24 +6662,26 @@ function writeSettings(settingsPath, settings) {
6595
6662
  (0, import_fs5.writeFileSync)(settingsPath, `${JSON.stringify(settings, null, 2)}
6596
6663
  `, "utf-8");
6597
6664
  }
6598
- function buildHookEntry(hookCommand) {
6665
+ function buildHookEntry(hookCommand, matcher = HOOK_MATCHER, extraHookFields = {}) {
6599
6666
  return {
6600
- matcher: HOOK_MATCHER,
6601
- hooks: [{ type: "command", command: hookCommand, timeout: HOOK_TIMEOUT_SECONDS }]
6667
+ matcher,
6668
+ hooks: [
6669
+ { type: "command", command: hookCommand, timeout: HOOK_TIMEOUT_SECONDS, ...extraHookFields }
6670
+ ]
6602
6671
  };
6603
6672
  }
6604
- function installClaudeHooks(settingsPath, hookCommand) {
6605
- if (!hookCommand.includes(ASTRASYNC_HOOK_MARKER)) {
6673
+ function installClaudeHooks(settingsPath, hookCommand, marker2 = ASTRASYNC_HOOK_MARKER, matcher = HOOK_MATCHER, extraHookFields = {}) {
6674
+ if (!hookCommand.includes(marker2)) {
6606
6675
  throw new Error(
6607
- `Hook command must reference ${ASTRASYNC_HOOK_MARKER} (got: ${hookCommand}) \u2014 the marker is how install/uninstall identify the guard entry.`
6676
+ `Hook command must reference ${marker2} (got: ${hookCommand}) \u2014 the marker is how install/uninstall identify the guard entry.`
6608
6677
  );
6609
6678
  }
6610
6679
  const settings = readSettings(settingsPath);
6611
6680
  settings.hooks = settings.hooks ?? {};
6612
6681
  const preToolUse = settings.hooks.PreToolUse = settings.hooks.PreToolUse ?? [];
6613
- const existing = preToolUse.find(isGuardEntry);
6682
+ const existing = preToolUse.find((entry) => isGuardEntry(entry, marker2));
6614
6683
  if (existing) {
6615
- const fresh = buildHookEntry(hookCommand);
6684
+ const fresh = buildHookEntry(hookCommand, matcher, extraHookFields);
6616
6685
  const changed = JSON.stringify(existing) !== JSON.stringify(fresh);
6617
6686
  if (changed) {
6618
6687
  preToolUse[preToolUse.indexOf(existing)] = fresh;
@@ -6620,11 +6689,11 @@ function installClaudeHooks(settingsPath, hookCommand) {
6620
6689
  }
6621
6690
  return { installed: false, alreadyPresent: true, updated: changed };
6622
6691
  }
6623
- preToolUse.push(buildHookEntry(hookCommand));
6692
+ preToolUse.push(buildHookEntry(hookCommand, matcher, extraHookFields));
6624
6693
  writeSettings(settingsPath, settings);
6625
6694
  return { installed: true, alreadyPresent: false, updated: false };
6626
6695
  }
6627
- function uninstallClaudeHooks(settingsPath) {
6696
+ function uninstallClaudeHooks(settingsPath, marker2 = ASTRASYNC_HOOK_MARKER) {
6628
6697
  if (!(0, import_fs5.existsSync)(settingsPath)) return false;
6629
6698
  let settings;
6630
6699
  try {
@@ -6634,7 +6703,7 @@ function uninstallClaudeHooks(settingsPath) {
6634
6703
  }
6635
6704
  const preToolUse = settings.hooks?.PreToolUse;
6636
6705
  if (!preToolUse) return false;
6637
- const remaining = preToolUse.filter((entry) => !isGuardEntry(entry));
6706
+ const remaining = preToolUse.filter((entry) => !isGuardEntry(entry, marker2));
6638
6707
  if (remaining.length === preToolUse.length) return false;
6639
6708
  if (remaining.length > 0) {
6640
6709
  settings.hooks.PreToolUse = remaining;
@@ -6698,16 +6767,44 @@ var init_settings_installer = __esm({
6698
6767
  init_wizard();
6699
6768
  init_pdlss_parser();
6700
6769
  ASTRASYNC_HOOK_MARKER = "astrasync-claude-hook";
6701
- HOOK_MATCHER = "Bash|Write|Edit|MultiEdit|NotebookEdit|Read|Glob|Grep|WebFetch|WebSearch|Task|Agent|mcp__.*";
6770
+ HOOK_MATCHER = "Bash|PowerShell|Write|Edit|MultiEdit|NotebookEdit|Read|Glob|Grep|WebFetch|WebSearch|Task|Agent|mcp__.*";
6702
6771
  HOOK_TIMEOUT_SECONDS = 10;
6703
6772
  GUARD_SELF_PROTECTION_PATTERNS = [
6704
6773
  "**/.claude/settings.json",
6705
6774
  "**/.claude/settings.local.json",
6706
- "**/.astrasync/**"
6775
+ "**/.astrasync/**",
6776
+ "**/.codex/hooks.json",
6777
+ "**/.codex/config.toml"
6707
6778
  ];
6708
6779
  }
6709
6780
  });
6710
6781
 
6782
+ // src/codex/settings-installer.ts
6783
+ function installCodexHooks(hooksPath, hookCommand) {
6784
+ return installClaudeHooks(
6785
+ hooksPath,
6786
+ hookCommand,
6787
+ ASTRASYNC_CODEX_HOOK_MARKER,
6788
+ CODEX_HOOK_MATCHER,
6789
+ {
6790
+ statusMessage: CODEX_STATUS_MESSAGE
6791
+ }
6792
+ );
6793
+ }
6794
+ function uninstallCodexHooks(hooksPath) {
6795
+ return uninstallClaudeHooks(hooksPath, ASTRASYNC_CODEX_HOOK_MARKER);
6796
+ }
6797
+ var ASTRASYNC_CODEX_HOOK_MARKER, CODEX_HOOK_MATCHER, CODEX_STATUS_MESSAGE;
6798
+ var init_settings_installer2 = __esm({
6799
+ "src/codex/settings-installer.ts"() {
6800
+ "use strict";
6801
+ init_settings_installer();
6802
+ ASTRASYNC_CODEX_HOOK_MARKER = "astrasync-codex-hook";
6803
+ CODEX_HOOK_MATCHER = "Bash|apply_patch|Edit|Write|web_search|mcp__.*";
6804
+ CODEX_STATUS_MESSAGE = "AstraSync Local Guard";
6805
+ }
6806
+ });
6807
+
6711
6808
  // src/cli/guard.ts
6712
6809
  var guard_exports = {};
6713
6810
  function createPrompt() {
@@ -6872,6 +6969,7 @@ async function cmdStatus() {
6872
6969
  console.log(' Policy: not found \u2014 run "astrasync guard setup"');
6873
6970
  }
6874
6971
  printClaudeCodeStatus();
6972
+ printCodexStatus();
6875
6973
  console.log("");
6876
6974
  }
6877
6975
  async function cmdCheck() {
@@ -7050,6 +7148,108 @@ async function cmdUninstallClaudeCode() {
7050
7148
  `[AstraSync] Policy and traces left in place at ${astrasyncDir} \u2014 delete manually if unwanted.`
7051
7149
  );
7052
7150
  }
7151
+ function resolveCodexHookCommand() {
7152
+ const sibling = path.join(__dirname, "astrasync-codex-hook.js");
7153
+ if (fs.existsSync(sibling)) {
7154
+ return `"${process.execPath}" "${sibling}"`;
7155
+ }
7156
+ return "astrasync-codex-hook";
7157
+ }
7158
+ function codexPaths(project) {
7159
+ const root = project ? process.cwd() : os.homedir();
7160
+ return {
7161
+ hooksPath: path.join(root, ".codex", "hooks.json"),
7162
+ astrasyncDir: path.join(root, ".astrasync")
7163
+ };
7164
+ }
7165
+ async function cmdInstallCodex() {
7166
+ const flags = parseClaudeCodeFlags();
7167
+ const { hooksPath, astrasyncDir } = codexPaths(flags.project);
7168
+ const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
7169
+ const guardJsonPath = path.join(astrasyncDir, "guard.json");
7170
+ console.log("\nAstraSync Local Guard for Codex CLI \u2014 install\n");
7171
+ if (fs.existsSync(policyPath)) {
7172
+ console.log(` Policy: using existing ${policyPath} (shared across adapters)`);
7173
+ if (ensureGuardSelfProtection(policyPath)) {
7174
+ console.log(" added guard self-protection patterns to file.write");
7175
+ }
7176
+ } else if (flags.wizard) {
7177
+ const prompt = createPrompt();
7178
+ try {
7179
+ fs.mkdirSync(path.dirname(policyPath), { recursive: true });
7180
+ const wizard = new SetupWizard(prompt);
7181
+ await wizard.runAndSave(policyPath);
7182
+ } finally {
7183
+ prompt.close();
7184
+ }
7185
+ ensureGuardSelfProtection(policyPath);
7186
+ console.log(` Policy: created via wizard at ${policyPath}`);
7187
+ } else {
7188
+ fs.mkdirSync(path.dirname(policyPath), { recursive: true });
7189
+ fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
7190
+ console.log(` Policy: default coding-agent policy written to ${policyPath}`);
7191
+ console.log(" (re-run with --wizard for the interactive setup instead)");
7192
+ }
7193
+ if (!fs.existsSync(guardJsonPath)) {
7194
+ const guardSettings = {
7195
+ posture: flags.active ? "active" : "passive",
7196
+ governMcp: false
7197
+ };
7198
+ fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
7199
+ `, "utf-8");
7200
+ console.log(
7201
+ ` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
7202
+ );
7203
+ } else {
7204
+ console.log(` Guard: using existing ${guardJsonPath}`);
7205
+ }
7206
+ const hookCommand = resolveCodexHookCommand();
7207
+ const result = installCodexHooks(hooksPath, hookCommand);
7208
+ if (result.installed) {
7209
+ console.log(` Hook: installed in ${hooksPath}`);
7210
+ } else if (result.updated) {
7211
+ console.log(` Hook: updated in ${hooksPath}`);
7212
+ } else {
7213
+ console.log(` Hook: already present in ${hooksPath}`);
7214
+ }
7215
+ console.log("\n Next steps:");
7216
+ console.log(" 1. Start a NEW Codex session (hooks load at startup; /hooks to inspect).");
7217
+ console.log(` 2. Watch decisions: tail -f ${path.join(astrasyncDir, "traces")}/*.jsonl`);
7218
+ console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}.`);
7219
+ console.log(" Denies block; approvals fall through to Codex's own prompt \u2014");
7220
+ console.log(" except in dontAsk/bypassPermissions modes, where they deny.");
7221
+ console.log(" 4. Note: Codex fails OPEN if the hook itself times out; the guard");
7222
+ console.log(" fails closed on every failure it can see.\n");
7223
+ }
7224
+ async function cmdUninstallCodex() {
7225
+ const flags = parseClaudeCodeFlags();
7226
+ const { hooksPath, astrasyncDir } = codexPaths(flags.project);
7227
+ const removed = uninstallCodexHooks(hooksPath);
7228
+ if (removed) {
7229
+ console.log(`[AstraSync] Removed Codex hook from ${hooksPath}`);
7230
+ } else {
7231
+ console.log(`[AstraSync] No AstraSync hook found in ${hooksPath}`);
7232
+ }
7233
+ console.log(
7234
+ `[AstraSync] Policy and traces left in place at ${astrasyncDir} \u2014 delete manually if unwanted.`
7235
+ );
7236
+ }
7237
+ function printCodexStatus() {
7238
+ for (const scope of ["user", "project"]) {
7239
+ const { hooksPath, astrasyncDir } = codexPaths(scope === "project");
7240
+ let hookInstalled = false;
7241
+ try {
7242
+ const content = fs.readFileSync(hooksPath, "utf-8");
7243
+ hookInstalled = content.includes(ASTRASYNC_CODEX_HOOK_MARKER);
7244
+ } catch {
7245
+ }
7246
+ if (hookInstalled) {
7247
+ const policyPath = path.join(astrasyncDir, "policy.yaml");
7248
+ const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
7249
+ console.log(` Codex CLI (${scope}): hook installed, ${policy}`);
7250
+ }
7251
+ }
7252
+ }
7053
7253
  function printClaudeCodeStatus() {
7054
7254
  for (const scope of ["user", "project"]) {
7055
7255
  const { settingsPath, astrasyncDir } = claudeCodePaths(scope === "project");
@@ -7107,6 +7307,12 @@ async function main() {
7107
7307
  case "uninstall-claude-code":
7108
7308
  await cmdUninstallClaudeCode();
7109
7309
  break;
7310
+ case "install-codex":
7311
+ await cmdInstallCodex();
7312
+ break;
7313
+ case "uninstall-codex":
7314
+ await cmdUninstallCodex();
7315
+ break;
7110
7316
  default:
7111
7317
  console.log("AstraSync Local Guard\n");
7112
7318
  console.log("Usage: astrasync guard <command>\n");
@@ -7124,6 +7330,9 @@ async function main() {
7124
7330
  console.log(
7125
7331
  " uninstall-claude-code Remove the Claude Code hook (--project for project scope)"
7126
7332
  );
7333
+ console.log(" install-codex Install the Local Guard hook for OpenAI Codex CLI");
7334
+ console.log(" (same flags; policy shared with Claude Code)");
7335
+ console.log(" uninstall-codex Remove the Codex hook (--project for project scope)");
7127
7336
  console.log("");
7128
7337
  process.exit(command ? 1 : 0);
7129
7338
  }
@@ -7143,6 +7352,7 @@ var init_guard = __esm({
7143
7352
  init_pdlss_parser();
7144
7353
  init_git_hooks();
7145
7354
  init_settings_installer();
7355
+ init_settings_installer2();
7146
7356
  DEFAULT_POLICY_PATH = "./local-policy.yaml";
7147
7357
  DEFAULT_CONFIG_DIR = ".astrasync";
7148
7358
  main().catch((err) => {
@@ -387,8 +387,9 @@ function extractBacktickSubstitutions(text, collect) {
387
387
  return kept.join(" ");
388
388
  }
389
389
  function basename(token) {
390
- const slash = token.lastIndexOf("/");
391
- return slash === -1 ? token : token.slice(slash + 1);
390
+ const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
391
+ const name = slash === -1 ? token : token.slice(slash + 1);
392
+ return name.replace(/\.(exe|cmd|bat|ps1)$/i, "");
392
393
  }
393
394
  function matchSegment(segment, list) {
394
395
  const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
@@ -521,7 +522,9 @@ var LocalEvaluator = class {
521
522
  }
522
523
  return {
523
524
  recommendation: "ALLOW",
524
- reason: "All PDLSS checks passed",
525
+ // "evaluated": dimensions absent from the policy are skipped, not
526
+ // failed — evaluatedDimensions below records which ones ran.
527
+ reason: "All evaluated PDLSS checks passed",
525
528
  evaluatedDimensions: {
526
529
  purpose: true,
527
530
  scope: !!this.policy.scope,
@@ -620,7 +623,7 @@ var LocalEvaluator = class {
620
623
  }
621
624
  }
622
625
  if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
623
- const targetLower = context.target.toLowerCase();
626
+ const targetLower = context.target.toLowerCase().replace(/\\/g, "/");
624
627
  for (const sensitivePath of SENSITIVE_PATHS) {
625
628
  if (targetLower.includes(sensitivePath.toLowerCase())) {
626
629
  score = Math.max(score, 50);
@@ -649,12 +652,20 @@ var LocalEvaluator = class {
649
652
  matchesAnyPattern(value, patterns) {
650
653
  return patterns.some((pattern) => this.matchGlob(value, pattern));
651
654
  }
652
- /**
653
- * Simple glob pattern matching.
654
- * Supports * (matches any characters including / and spaces) and ? (single char).
655
- * Uses the same approach as the backend's matchGlobPattern.
656
- */
655
+ homePrefix() {
656
+ if (this.cachedHomePrefix !== void 0) return this.cachedHomePrefix;
657
+ const env = typeof process !== "undefined" ? process.env : void 0;
658
+ const home = env?.HOME || env?.USERPROFILE;
659
+ this.cachedHomePrefix = home ? home.replace(/\\/g, "/") : null;
660
+ return this.cachedHomePrefix;
661
+ }
657
662
  matchGlob(value, pattern) {
663
+ value = value.replace(/\\/g, "/");
664
+ pattern = pattern.replace(/\\/g, "/");
665
+ if (pattern === "~" || pattern.startsWith("~/")) {
666
+ const home = this.homePrefix();
667
+ if (home) pattern = home + pattern.slice(1);
668
+ }
658
669
  if (pattern === value) return true;
659
670
  const starCount = (pattern.match(/\*/g) ?? []).length;
660
671
  if (starCount > 8) {
@@ -3927,7 +3938,7 @@ function getTrustLevel(score) {
3927
3938
  }
3928
3939
 
3929
3940
  // src/version.ts
3930
- var SDK_VERSION = "3.10.1";
3941
+ var SDK_VERSION = "3.11.1";
3931
3942
 
3932
3943
  // src/well-known.ts
3933
3944
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -4442,10 +4453,12 @@ function toGatewayConfig(config) {
4442
4453
  };
4443
4454
  }
4444
4455
  function toVerificationRequest(context, astraId) {
4456
+ const dotIndex = context.purpose.indexOf(".");
4457
+ const dotted = dotIndex > 0;
4445
4458
  return {
4446
4459
  credentials: { astraId },
4447
- purpose: context.purpose,
4448
- action: context.action,
4460
+ purpose: dotted ? context.purpose.slice(0, dotIndex) : context.purpose,
4461
+ action: dotted ? context.purpose : context.action,
4449
4462
  resourceType: context.resourceType,
4450
4463
  resource: context.target,
4451
4464
  transactionValue: context.transactionValue,
@@ -4688,13 +4701,43 @@ var TraceLogger = class {
4688
4701
  * Log an evaluation event.
4689
4702
  */
4690
4703
  logEvaluation(context, decision) {
4704
+ if (!this.enabled) return;
4705
+ let event;
4706
+ try {
4707
+ event = {
4708
+ id: this.generateId(),
4709
+ timestamp: /* @__PURE__ */ new Date(),
4710
+ type: "evaluation",
4711
+ context: redactDeep(context),
4712
+ decision: redactDeep(decision)
4713
+ };
4714
+ } catch (err) {
4715
+ event = {
4716
+ id: this.generateId(),
4717
+ timestamp: /* @__PURE__ */ new Date(),
4718
+ type: "error",
4719
+ metadata: {
4720
+ error: `trace redaction failed: ${err instanceof Error ? err.message : String(err)}`,
4721
+ recommendation: decision.recommendation,
4722
+ purpose: context.purpose,
4723
+ action: context.action
4724
+ }
4725
+ };
4726
+ }
4727
+ this.write(event);
4728
+ }
4729
+ /**
4730
+ * Log a tool call the guard saw but did not govern. One line per skip
4731
+ * keeps coverage holes (unmapped platform tools) visible in the traces
4732
+ * instead of silently indistinguishable from ALLOW.
4733
+ */
4734
+ logSkipped(toolName, reason) {
4691
4735
  if (!this.enabled) return;
4692
4736
  this.write({
4693
4737
  id: this.generateId(),
4694
4738
  timestamp: /* @__PURE__ */ new Date(),
4695
- type: "evaluation",
4696
- context: redactDeep(context),
4697
- decision: redactDeep(decision)
4739
+ type: "skipped",
4740
+ metadata: { toolName, reason }
4698
4741
  });
4699
4742
  }
4700
4743
  /**
@@ -4723,17 +4766,34 @@ var TraceLogger = class {
4723
4766
  });
4724
4767
  }
4725
4768
  write(event) {
4769
+ let line;
4770
+ try {
4771
+ line = JSON.stringify(event) + "\n";
4772
+ } catch {
4773
+ return;
4774
+ }
4726
4775
  try {
4727
4776
  const filePath = this.getCurrentFilePath();
4728
- const line = JSON.stringify(event) + "\n";
4729
4777
  if ((0, import_fs3.existsSync)(filePath)) {
4730
4778
  const stats = (0, import_fs3.statSync)(filePath);
4731
4779
  if (stats.size > MAX_FILE_SIZE) {
4732
4780
  this.rotate();
4733
4781
  }
4734
4782
  }
4735
- (0, import_fs3.appendFileSync)(filePath, line);
4783
+ for (let attempt = 0; attempt < 3; attempt++) {
4784
+ try {
4785
+ (0, import_fs3.appendFileSync)(filePath, line);
4786
+ return;
4787
+ } catch {
4788
+ }
4789
+ }
4790
+ const fallbackPath = filePath.replace(/\.jsonl$/, `-r${process.pid}.jsonl`);
4791
+ (0, import_fs3.appendFileSync)(fallbackPath, line);
4736
4792
  } catch {
4793
+ try {
4794
+ console.error(`[AstraSync TraceLogger] failed to persist trace line: ${line.trim()}`);
4795
+ } catch {
4796
+ }
4737
4797
  }
4738
4798
  }
4739
4799
  getCurrentFilePath() {
@@ -4894,6 +4954,13 @@ var AstraSyncGateway = class {
4894
4954
  getSyncStatus() {
4895
4955
  return this.syncQueue?.getStatus() || null;
4896
4956
  }
4957
+ /**
4958
+ * Record a tool call the calling adapter saw but did not govern
4959
+ * (unmapped tool, skipTools, MCP with governMcp off). Trace-only.
4960
+ */
4961
+ logSkippedTool(toolName, reason) {
4962
+ this.traceLogger?.logSkipped(toolName, reason);
4963
+ }
4897
4964
  };
4898
4965
 
4899
4966
  // src/browser/background.ts