Reproduction sandbox: canonical (/private/var) paths so reproduce.sh can execute
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 changed file +16 −1
modified
services/research-runner/src/runner.ts
+16 −1
@@ -479,7 +479,22 @@ export class ResearchRunner { | ||
| 479 | 479 | await mkdir(join(reproDir, "results/tables"), { recursive: true }); |
| 480 | 480 | await mkdir(join(reproDir, "results/figures"), { recursive: true }); |
| 481 | 481 | await mkdir(join(reproDir, "home"), { recursive: true }); |
| 482 | − const profile = `(version 1)\n(allow default)\n(deny file-write*)\n(allow file-write* (subpath "${reproDir}") (subpath "/private/tmp") (subpath "/tmp") (subpath "/private/var/folders") (literal "/dev/null") (regex #"^/dev/tty") (subpath "${this.host.tectonicCache ?? join(this.ws, ".cache/tectonic")}"))\n(deny file-read* (subpath "/Users") (subpath "/etc/autoscholar") (subpath "/var/autoscholar/runs") (subpath "/private/var/autoscholar/runs"))\n(allow file-read* (subpath "${reproDir}") (subpath "${this.ws}/.cache"))\n`; | |
| 482 | + // Seatbelt matches canonical paths (/private/var/...): resolve every path and allow both spellings | |
| 483 | + const real = async (p: string) => realpath(p).catch(() => p); | |
| 484 | + const reproReal = await real(reproDir); | |
| 485 | + const wsReal = await real(this.ws); | |
| 486 | + const cacheDir = this.host.tectonicCache ?? join(this.ws, ".cache/tectonic"); | |
| 487 | + const cacheReal = await real(cacheDir); | |
| 488 | + const both = (p: string, q: string) => [...new Set([p, q])].map((x) => `(subpath "${x}")`).join(" "); | |
| 489 | + const profile = [ | |
| 490 | + "(version 1)", | |
| 491 | + "(allow default)", | |
| 492 | + "(deny file-write*)", | |
| 493 | + `(allow file-write* ${both(reproDir, reproReal)} (subpath "/private/tmp") (subpath "/tmp") (subpath "/private/var/folders") (literal "/dev/null") (regex #"^/dev/tty") ${both(cacheDir, cacheReal)} ${both(join(this.ws, ".cache"), join(wsReal, ".cache"))})`, | |
| 494 | + `(deny file-read* (subpath "/Users") (subpath "/etc/autoscholar") (subpath "/private/etc/autoscholar") (subpath "/var/autoscholar/runs") (subpath "/private/var/autoscholar/runs"))`, | |
| 495 | + `(allow file-read* ${both(reproDir, reproReal)} ${both(join(this.ws, ".cache"), join(wsReal, ".cache"))} ${both(cacheDir, cacheReal)})`, | |
| 496 | + "", | |
| 497 | + ].join("\n"); | |
| 483 | 498 | const profilePath = join(this.ws, "_repro/repro.sb"); |
| 484 | 499 | await writeFile(profilePath, profile); |
| 485 | 500 | const t0 = Date.now(); |
| 486 | 501 | |