diff --git a/docs/security/SUPPLY-CHAIN-SAFE-PATTERNS.md b/docs/security/SUPPLY-CHAIN-SAFE-PATTERNS.md index 0eaa3772..d6d83b03 100644 --- a/docs/security/SUPPLY-CHAIN-SAFE-PATTERNS.md +++ b/docs/security/SUPPLY-CHAIN-SAFE-PATTERNS.md @@ -67,8 +67,8 @@ the manifest plus repository signature verification. The immutable identifier is *how* we lock a decision, but the decision itself is **content review at first pin**. A SHA-256 that -points at malicious code is still malicious; a hand-verified -script run `curl | bash`-style after a careful read is safe. +points at malicious code is still malicious; the protection is +reading the content before it runs, not the hash on its own. In this factory, Aaron's standing policy (2026-04-22) is: *"never run a script you download without checking it for vulnerability, @@ -88,8 +88,20 @@ them first."* So the actual author-time protocol is: cache of your review. Any bump invalidates the cache and forces a re-read. -The delivery mechanism (`curl | bash` vs `curl -o path && bash -path`) is not the risk. The risk is unvalidated content. +The risk the protocol targets is **unvalidated content**, and +the delivery mechanism matters only insofar as it permits or +prevents validation: + +- **At first contact, `curl | bash` is disallowed** — the pipe + executes the bytes before any human or lint has read them, + which makes step 2 impossible. Use `curl -o path && bash + path` (or equivalent split) so the content lands on disk + first. +- **After SHA-256-pinning, `curl | bash` becomes + acceptable** in automation — the pin is the cached review, + and the hash is verified before the pipe executes. But the + pin must have been earned by the four-step protocol the + first time the content was admitted. ## Third-party-code ingress points diff --git a/tests/Tests.FSharp/Formal/Alloy.Runner.Tests.fs b/tests/Tests.FSharp/Formal/Alloy.Runner.Tests.fs index afef56d9..e352dff1 100644 --- a/tests/Tests.FSharp/Formal/Alloy.Runner.Tests.fs +++ b/tests/Tests.FSharp/Formal/Alloy.Runner.Tests.fs @@ -26,8 +26,13 @@ open global.Xunit let private repoRoot = - let cwd = Directory.GetCurrentDirectory() - let mutable dir = DirectoryInfo cwd + // Walk up from the test assembly's directory, NOT the process CWD. + // xUnit parallelizes test classes, so CWD-mutating tests (e.g. + // WitnessDurableBackingStore under-CWD-churn) can race with this + // module's static init on macOS and trip the walk-up loop. Fixed + // by reading AppContext.BaseDirectory, which is immutable for the + // lifetime of the AppDomain. + let mutable dir = DirectoryInfo AppContext.BaseDirectory while not (isNull dir) && not (File.Exists (Path.Combine(dir.FullName, "Zeta.sln"))) do dir <- dir.Parent if isNull dir then invalidOp "Could not locate repo root (Zeta.sln)" diff --git a/tests/Tests.FSharp/Formal/Tlc.Runner.Tests.fs b/tests/Tests.FSharp/Formal/Tlc.Runner.Tests.fs index 0c90f547..f4691fb6 100644 --- a/tests/Tests.FSharp/Formal/Tlc.Runner.Tests.fs +++ b/tests/Tests.FSharp/Formal/Tlc.Runner.Tests.fs @@ -41,9 +41,15 @@ type TlcTestCollection () = class end let private repoRoot = - // Walk up from bin/Release/net10.0 to the repo root. - let cwd = Directory.GetCurrentDirectory() - let mutable dir = DirectoryInfo cwd + // Walk up from the test assembly's directory, NOT the process CWD. + // xUnit parallelizes test classes, so CWD-mutating tests can race + // with this module's static init (observed as + // TypeInitializationException on macOS-14 in the Alloy sibling + // module). AppContext.BaseDirectory is immutable for the lifetime + // of the AppDomain and always points at + // `/tests/Tests.FSharp/bin/Release/net10.0/` under + // `dotnet test`, so walking up reliably finds Zeta.sln. + let mutable dir = DirectoryInfo AppContext.BaseDirectory while not (isNull dir) && not (File.Exists (Path.Combine(dir.FullName, "Zeta.sln"))) do dir <- dir.Parent if isNull dir then invalidOp "Could not locate repo root (Zeta.sln)"