@seqyuan/annodex 0.1.53 → 0.1.55

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 (71) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +11 -11
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/required-server-files.js +3 -1
  6. package/.next/required-server-files.json +3 -1
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.html +1 -1
  16. package/.next/server/app/_not-found.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/.next/server/app/api/internal/runtime/route.js +1 -1
  24. package/.next/server/app/api/version/route.js +1 -1
  25. package/.next/server/app/docs/changelog.html +2 -2
  26. package/.next/server/app/docs/changelog.rsc +1 -1
  27. package/.next/server/app/docs/changelog.segments/_full.segment.rsc +1 -1
  28. package/.next/server/app/docs/changelog.segments/_head.segment.rsc +1 -1
  29. package/.next/server/app/docs/changelog.segments/_index.segment.rsc +1 -1
  30. package/.next/server/app/docs/changelog.segments/_tree.segment.rsc +1 -1
  31. package/.next/server/app/docs/changelog.segments/docs/changelog/__PAGE__.segment.rsc +1 -1
  32. package/.next/server/app/docs/changelog.segments/docs/changelog.segment.rsc +1 -1
  33. package/.next/server/app/docs/changelog.segments/docs.segment.rsc +1 -1
  34. package/.next/server/app/index.html +1 -1
  35. package/.next/server/app/index.rsc +1 -1
  36. package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  37. package/.next/server/app/index.segments/_full.segment.rsc +1 -1
  38. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  39. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  40. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  41. package/.next/server/app/login.html +1 -1
  42. package/.next/server/app/login.rsc +1 -1
  43. package/.next/server/app/login.segments/_full.segment.rsc +1 -1
  44. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  45. package/.next/server/app/login.segments/_index.segment.rsc +1 -1
  46. package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  47. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
  48. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  49. package/.next/server/app/workspace/page.js +3 -3
  50. package/.next/server/app/workspace/page_client-reference-manifest.js +1 -1
  51. package/.next/server/app/workspace.html +1 -1
  52. package/.next/server/app/workspace.rsc +2 -2
  53. package/.next/server/app/workspace.segments/_full.segment.rsc +2 -2
  54. package/.next/server/app/workspace.segments/_head.segment.rsc +1 -1
  55. package/.next/server/app/workspace.segments/_index.segment.rsc +1 -1
  56. package/.next/server/app/workspace.segments/_tree.segment.rsc +1 -1
  57. package/.next/server/app/workspace.segments/workspace/__PAGE__.segment.rsc +2 -2
  58. package/.next/server/app/workspace.segments/workspace.segment.rsc +1 -1
  59. package/.next/server/app-paths-manifest.json +11 -11
  60. package/.next/server/chunks/6983.js +12 -5
  61. package/.next/server/middleware-build-manifest.js +1 -1
  62. package/.next/server/pages/404.html +1 -1
  63. package/.next/server/pages/500.html +1 -1
  64. package/.next/server/server-reference-manifest.json +1 -1
  65. package/.next/static/chunks/app/workspace/{page-d1366e4f9b7a7a15.js → page-249489e08420f59d.js} +3 -3
  66. package/bin/annodex.js +299 -0
  67. package/lib/macos-codex-security.js +376 -0
  68. package/next.config.ts +1 -1
  69. package/package.json +2 -1
  70. /package/.next/static/{ZR0UxSLWFSPEwsYD3WfeI → vW2g3YlVaZErFy9-fsfCO}/_buildManifest.js +0 -0
  71. /package/.next/static/{ZR0UxSLWFSPEwsYD3WfeI → vW2g3YlVaZErFy9-fsfCO}/_ssgManifest.js +0 -0
@@ -0,0 +1,376 @@
1
+ "use strict";
2
+ /* eslint-disable @typescript-eslint/no-require-imports */
3
+
4
+ /**
5
+ * macOS Gatekeeper helpers for the vendored @openai/codex native binary.
6
+ *
7
+ * npm-installed codex may carry com.apple.quarantine and/or a Developer ID
8
+ * signature whose certificate chain was revoked. The latter can cause macOS
9
+ * to SIGKILL the process immediately (CSSMERR_TP_CERT_REVOKED). Stripping the
10
+ * broken signature and applying ad-hoc signing (--sign -) downgrades the
11
+ * failure to a first-run Gatekeeper dialog (right-click → Open), matching the
12
+ * hermes-agent Electron recovery pattern.
13
+ */
14
+
15
+ const { spawnSync, execSync } = require("child_process");
16
+ const fs = require("fs");
17
+ const path = require("path");
18
+
19
+ const CODESIGN = "/usr/bin/codesign";
20
+ const SPCTL = "/usr/sbin/spctl";
21
+
22
+ /** Avoid repeating signature checks on every codex spawn in one process. */
23
+ function getPreparedRegistry() {
24
+ if (!globalThis.__annodexCodexPreparedBinaries) {
25
+ globalThis.__annodexCodexPreparedBinaries = new Map();
26
+ }
27
+ return globalThis.__annodexCodexPreparedBinaries;
28
+ }
29
+
30
+ const preparingBinaries = new Set();
31
+
32
+ function isBinaryPrepared(binaryPath) {
33
+ const mtime = binaryMtimeMs(binaryPath);
34
+ return mtime !== null && getPreparedRegistry().get(binaryPath) === mtime;
35
+ }
36
+
37
+ function markBinaryPrepared(binaryPath) {
38
+ const mtime = binaryMtimeMs(binaryPath);
39
+ if (mtime !== null) getPreparedRegistry().set(binaryPath, mtime);
40
+ }
41
+
42
+ let quarantineClearedForCwd = null;
43
+
44
+ function isDarwin() {
45
+ return process.platform === "darwin";
46
+ }
47
+
48
+ function codesignOutput(result) {
49
+ return `${result.stdout || ""}${result.stderr || ""}`.trim();
50
+ }
51
+
52
+ function needsRevokedCertRepair(text) {
53
+ return /CSSMERR_TP_CERT_REVOKED/i.test(text || "");
54
+ }
55
+
56
+ function binaryMtimeMs(binaryPath) {
57
+ try {
58
+ return fs.statSync(binaryPath).mtimeMs;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ function spctlAssess(binaryPath, timeoutMs = 10_000) {
65
+ if (!isDarwin() || !binaryPath || !fs.existsSync(binaryPath)) {
66
+ return { accepted: false, output: "", error: "missing binary" };
67
+ }
68
+ const result = spawnSync(SPCTL, ["--assess", "--verbose", binaryPath], {
69
+ encoding: "utf8",
70
+ timeout: timeoutMs,
71
+ });
72
+ const output = codesignOutput(result);
73
+ const timedOut = result.error && /timed out/i.test(result.error.message || "");
74
+ return {
75
+ accepted: result.status === 0 || /accepted/i.test(output),
76
+ revoked: needsRevokedCertRepair(output),
77
+ timedOut,
78
+ output,
79
+ status: result.status,
80
+ error: result.error ? result.error.message : null,
81
+ };
82
+ }
83
+
84
+ function codesignVerify(binaryPath, { timeoutMs = 5_000 } = {}) {
85
+ if (!isDarwin() || !binaryPath || !fs.existsSync(binaryPath)) {
86
+ return { ok: false, revoked: false, output: "", error: "missing binary" };
87
+ }
88
+ const result = spawnSync(CODESIGN, ["--verify", "--strict", "--verbose=4", binaryPath], {
89
+ encoding: "utf8",
90
+ timeout: timeoutMs,
91
+ });
92
+ const output = codesignOutput(result);
93
+ return {
94
+ ok: result.status === 0,
95
+ revoked: needsRevokedCertRepair(output),
96
+ output,
97
+ status: result.status,
98
+ error: result.error ? result.error.message : null,
99
+ };
100
+ }
101
+
102
+ /** Fast spawn-path check — only detect revoked certs (not full strict verify). */
103
+ function needsRepairBeforeSpawn(binaryPath) {
104
+ const result = spawnSync(CODESIGN, ["--verify", binaryPath], {
105
+ encoding: "utf8",
106
+ timeout: 3_000,
107
+ });
108
+ return needsRevokedCertRepair(codesignOutput(result));
109
+ }
110
+
111
+ function shouldRepairCodexBinary(binaryPath) {
112
+ const verify = codesignVerify(binaryPath);
113
+ if (verify.revoked || !verify.ok) return true;
114
+ const spctl = spctlAssess(binaryPath, 10_000);
115
+ return spctl.revoked;
116
+ }
117
+
118
+ function repairCodexBinaryAdHoc(binaryPath) {
119
+ if (!isDarwin() || !binaryPath || !fs.existsSync(binaryPath)) {
120
+ return { ok: false, output: "", error: "missing binary" };
121
+ }
122
+ spawnSync(CODESIGN, ["--remove-signature", binaryPath], {
123
+ encoding: "utf8",
124
+ timeout: 10_000,
125
+ });
126
+ const sign = spawnSync(CODESIGN, ["--force", "--sign", "-", binaryPath], {
127
+ encoding: "utf8",
128
+ timeout: 10_000,
129
+ });
130
+ const output = codesignOutput(sign);
131
+ return {
132
+ ok: sign.status === 0,
133
+ output,
134
+ error: sign.error ? sign.error.message : null,
135
+ };
136
+ }
137
+
138
+ function collectCodexXattrTargets(cwd) {
139
+ const targets = [];
140
+ const workDir = cwd || process.cwd();
141
+
142
+ const localPkg = path.join(workDir, "node_modules", "@openai", "codex");
143
+ if (fs.existsSync(localPkg)) targets.push(localPkg);
144
+
145
+ let annodexRoot = null;
146
+ try {
147
+ annodexRoot = path.join(__dirname, "..");
148
+ } catch {
149
+ // bundled build
150
+ }
151
+
152
+ try {
153
+ const npmPrefix = execSync("npm prefix -g", { encoding: "utf8", timeout: 3000 }).trim();
154
+ if (npmPrefix) {
155
+ const globalPkg = path.join(npmPrefix, "lib", "node_modules", "@openai", "codex");
156
+ if (fs.existsSync(globalPkg)) targets.push(globalPkg);
157
+ const binShim = path.join(npmPrefix, "bin", "codex");
158
+ if (fs.existsSync(binShim)) targets.push(binShim);
159
+ }
160
+ } catch {
161
+ // npm not available
162
+ }
163
+
164
+ if (annodexRoot) {
165
+ const annodexPkg = path.join(annodexRoot, "..", "..", "node_modules", "@openai", "codex");
166
+ if (fs.existsSync(annodexPkg) && !targets.includes(annodexPkg)) targets.push(annodexPkg);
167
+ }
168
+
169
+ return targets;
170
+ }
171
+
172
+ function clearMacOSQuarantineDeep(cwd) {
173
+ if (!isDarwin()) return { cleared: 0, paths: [], skipped: true };
174
+ const targets = collectCodexXattrTargets(cwd);
175
+ const clearedPaths = [];
176
+ for (const target of targets) {
177
+ try {
178
+ execSync(`xattr -cr "${target}" 2>/dev/null || true`, {
179
+ shell: "/bin/bash",
180
+ timeout: 5000,
181
+ });
182
+ clearedPaths.push(target);
183
+ } catch {
184
+ // read-only volumes, etc.
185
+ }
186
+ }
187
+ return { cleared: clearedPaths.length, paths: clearedPaths, skipped: false };
188
+ }
189
+
190
+ function clearMacOSQuarantine(cwd) {
191
+ return clearMacOSQuarantineDeep(cwd);
192
+ }
193
+
194
+ /** Fast quarantine clear for codex spawn — target only the native binary. */
195
+ function clearMacOSQuarantineForSpawn(executablePath, cwd) {
196
+ if (!isDarwin()) return { cleared: 0, paths: [], skipped: true };
197
+ const key = cwd || process.cwd();
198
+ if (quarantineClearedForCwd === key) {
199
+ return { cleared: 0, paths: [], skipped: true };
200
+ }
201
+ const clearedPaths = [];
202
+ if (executablePath && fs.existsSync(executablePath)) {
203
+ try {
204
+ execSync(`xattr -c "${executablePath}" 2>/dev/null || true`, {
205
+ shell: "/bin/bash",
206
+ timeout: 2000,
207
+ });
208
+ clearedPaths.push(executablePath);
209
+ } catch {
210
+ // ignore
211
+ }
212
+ }
213
+ quarantineClearedForCwd = key;
214
+ return { cleared: clearedPaths.length, paths: clearedPaths, skipped: false };
215
+ }
216
+
217
+ function looksLikeNativeCodexBinary(filePath) {
218
+ if (!filePath) return false;
219
+ const base = path.basename(filePath);
220
+ return base === "codex" || base === "codex.exe";
221
+ }
222
+
223
+ /** Given a codex executable path (may be a JS shim), find the real native binary. */
224
+ function resolveNativeBinaryFromShim(executablePath) {
225
+ if (!executablePath || !fs.existsSync(executablePath)) return null;
226
+ // If it's already a native binary (inside vendor/), return as-is
227
+ if (/vendor[/\\]/.test(executablePath)) return executablePath;
228
+
229
+ // Check if it's a JS shim (e.g. node_modules/.bin/codex)
230
+ try {
231
+ const content = fs.readFileSync(executablePath, "utf8").slice(0, 512);
232
+ if (content.includes("node") || content.includes("#!/")) {
233
+ // This is a shim/script — find the native binary nearby
234
+ const shimDir = path.dirname(executablePath);
235
+ const nodeModulesDir = path.join(shimDir, "..");
236
+
237
+ const platform = process.platform;
238
+ const arch = process.arch;
239
+ let pkgName;
240
+ let triple;
241
+ if (platform === "darwin") {
242
+ if (arch === "arm64") { pkgName = "codex-darwin-arm64"; triple = "aarch64-apple-darwin"; }
243
+ else if (arch === "x64") { pkgName = "codex-darwin-x64"; triple = "x86_64-apple-darwin"; }
244
+ else return null;
245
+ } else {
246
+ return null;
247
+ }
248
+ const binaryName = "codex";
249
+
250
+ const subPaths = [
251
+ path.join("vendor", triple, "bin", binaryName),
252
+ path.join("vendor", triple, "codex", binaryName),
253
+ ];
254
+ for (const sub of subPaths) {
255
+ const candidate = path.join(nodeModulesDir, "@openai", pkgName, sub);
256
+ if (fs.existsSync(candidate)) return candidate;
257
+ }
258
+ // Also try nested layout
259
+ for (const sub of subPaths) {
260
+ const candidate = path.join(nodeModulesDir, "@openai", "codex", "node_modules", "@openai", pkgName, sub);
261
+ if (fs.existsSync(candidate)) return candidate;
262
+ }
263
+ }
264
+ } catch { /* ignore */ }
265
+ return executablePath;
266
+ }
267
+
268
+ function repairMacOSCodexPaths(binaryPaths, { force = false, mode = "doctor" } = {}) {
269
+ if (!isDarwin()) return [];
270
+ const seen = new Set();
271
+ const results = [];
272
+ for (const binaryPath of binaryPaths) {
273
+ if (!binaryPath || seen.has(binaryPath) || !fs.existsSync(binaryPath)) continue;
274
+ if (!looksLikeNativeCodexBinary(binaryPath)) continue;
275
+ seen.add(binaryPath);
276
+
277
+ const beforeVerify = mode === "doctor" ? codesignVerify(binaryPath) : null;
278
+ const beforeSpctl = mode === "doctor" ? spctlAssess(binaryPath, 10_000) : null;
279
+ const needsRepair = force
280
+ || (beforeVerify?.revoked ?? false)
281
+ || (beforeSpctl?.revoked ?? false)
282
+ || (mode === "spawn" && needsRepairBeforeSpawn(binaryPath));
283
+ if (!needsRepair) {
284
+ markBinaryPrepared(binaryPath);
285
+ results.push({
286
+ path: binaryPath,
287
+ skipped: true,
288
+ before: beforeVerify,
289
+ beforeSpctl,
290
+ });
291
+ continue;
292
+ }
293
+
294
+ const repair = repairCodexBinaryAdHoc(binaryPath);
295
+ const after = codesignVerify(binaryPath);
296
+ const afterSpctl = mode === "doctor" ? spctlAssess(binaryPath, 10_000) : null;
297
+ if (repair.ok) markBinaryPrepared(binaryPath);
298
+ results.push({
299
+ path: binaryPath,
300
+ skipped: false,
301
+ before: beforeVerify,
302
+ beforeSpctl,
303
+ repair,
304
+ after,
305
+ afterSpctl,
306
+ });
307
+ }
308
+ return results;
309
+ }
310
+
311
+ /**
312
+ * Best-effort macOS prep before spawning codex: replace revoked signatures with
313
+ * ad-hoc signing. Skips xattr on the hot path — clearing quarantine on trees
314
+ * under macOS protected folders (Documents, etc.) can take 20s+ per spawn.
315
+ * Use `annodex doctor --repair` for deep quarantine cleanup.
316
+ */
317
+ function prepareMacOSCodexForSpawn(executablePath, cwd) {
318
+ if (!isDarwin()) return { quarantine: { cleared: 0, paths: [] }, repairs: [] };
319
+
320
+ const quarantine = { cleared: 0, paths: [], skipped: true };
321
+ if (!executablePath) return { quarantine, repairs: [] };
322
+
323
+ // If the resolved executable is a JS shim (e.g. node_modules/.bin/codex),
324
+ // find and repair the underlying native binary instead.
325
+ // Spawning the shim causes Node to exec the native binary as a grandchild,
326
+ // which macOS Gatekeeper may kill even after ad-hoc signing of the shim itself.
327
+ const resolvedNative = resolveNativeBinaryFromShim(executablePath);
328
+ if (resolvedNative && resolvedNative !== executablePath) {
329
+ console.log(
330
+ `[codex-server] Resolved native codex binary from shim: ${executablePath} -> ${resolvedNative}`,
331
+ );
332
+ prepareMacOSCodexForSpawn(resolvedNative, cwd);
333
+ return { quarantine, repairs: [], shimResolved: resolvedNative };
334
+ }
335
+
336
+ if (!looksLikeNativeCodexBinary(executablePath)) {
337
+ return { quarantine, repairs: [] };
338
+ }
339
+ if (isBinaryPrepared(executablePath) || preparingBinaries.has(executablePath)) {
340
+ return { quarantine, repairs: [], skipped: true };
341
+ }
342
+
343
+ preparingBinaries.add(executablePath);
344
+ let repairs = [];
345
+ try {
346
+ repairs = repairMacOSCodexPaths([executablePath], { mode: "spawn" });
347
+ } finally {
348
+ preparingBinaries.delete(executablePath);
349
+ }
350
+ for (const item of repairs) {
351
+ if (item.skipped) continue;
352
+ if (item.repair?.ok) {
353
+ console.log(
354
+ `[codex-server] Repaired codex signature with ad-hoc signing: ${item.path}`,
355
+ );
356
+ } else if (item.before?.revoked || item.beforeSpctl?.revoked) {
357
+ console.warn(
358
+ `[codex-server] Failed to repair revoked codex signature at ${item.path}: ${item.repair?.output || item.repair?.error || "unknown error"}`,
359
+ );
360
+ }
361
+ }
362
+
363
+ return { quarantine, repairs };
364
+ }
365
+
366
+ module.exports = {
367
+ codesignVerify,
368
+ spctlAssess,
369
+ shouldRepairCodexBinary,
370
+ repairCodexBinaryAdHoc,
371
+ needsRevokedCertRepair,
372
+ clearMacOSQuarantine,
373
+ repairMacOSCodexPaths,
374
+ resolveNativeBinaryFromShim,
375
+ prepareMacOSCodexForSpawn,
376
+ };
package/next.config.ts CHANGED
@@ -6,7 +6,7 @@ const { version } = JSON.parse(readFileSync(join(__dirname, "package.json"), "ut
6
6
 
7
7
  const nextConfig: NextConfig = {
8
8
  serverExternalPackages: ["ws", "@openai/codex"],
9
- allowedDevOrigins: ['192.168.*.*'],
9
+ allowedDevOrigins: ["127.0.0.1", "localhost", "192.168.*.*"],
10
10
  env: {
11
11
  NEXT_PUBLIC_APP_VERSION: version,
12
12
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seqyuan/annodex",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "description": "AI-native bioinformatics workspace by Annoroad",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "!.next/trace",
17
17
  "!.next/trace-build",
18
18
  "lib/app-settings.js",
19
+ "lib/macos-codex-security.js",
19
20
  "lib/default-SOUL.md",
20
21
  "lib/default-HARNESS.md",
21
22
  "public",