Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3fa116b
mason: implement Windows host fs module and raw-spelling validation
ualtinok Oct 10, 2026
76b9008
mason: add comprehensive Windows fs refusal tests and deterministic t…
ualtinok Oct 10, 2026
3d184fc
mason: suppress unused import warning in fs test module on non-unix
ualtinok Oct 10, 2026
c63f497
Merge: the Windows fs built-in, with Windows refusal tests
ualtinok Oct 10, 2026
0611455
Refuse the superscript COM and LPT device names on Windows
ualtinok Oct 10, 2026
5621897
mason: implement Windows host git module and job containment
ualtinok Oct 10, 2026
7d24263
mason: fix the Windows fs built-in from its security review
ualtinok Oct 10, 2026
51662ad
mason: add basal-launch, the Windows confined worker launcher
ualtinok Oct 10, 2026
1f8d083
mason: clarify basal-launch comments for a reader without context
ualtinok Oct 10, 2026
8c40332
mason: treat a job-killed worker as killed, and give the worker a lon…
ualtinok Oct 10, 2026
02b46b1
Merge commit '7d24263' into alfonso/task/bg_d6985ae075a52784-basal-wi…
ualtinok Oct 10, 2026
9ea2994
Merge: Windows fs built-in fixes from its security review (44 Windows…
ualtinok Oct 10, 2026
651c119
Draw host entropy from BCryptGenRandom on Windows
ualtinok Oct 10, 2026
9cafad6
Run basal-host's tests on their own in the Windows baseline job
ualtinok Oct 10, 2026
0897adc
Re-anchor the random-source control on the shared entropy call
ualtinok Oct 10, 2026
4ada060
Merge: basal-launch, the Windows confined worker launcher (all 18 lau…
ualtinok Oct 10, 2026
0eb28a0
mason: confine Windows git reads and pin their approved directories
ualtinok Oct 10, 2026
80c4314
mason: accept the host Windows temp directory separator
ualtinok Oct 10, 2026
4bd6090
mason: observe handle inheritance in the exact Windows git child
ualtinok Oct 10, 2026
c61d815
mason: Windows worker entry and startup checks
ualtinok Oct 10, 2026
8cad58c
Merge: the Windows git built-in, redone from its security review (all…
ualtinok Oct 10, 2026
719a928
mason: run the Windows worker tests under a development name
ualtinok Oct 10, 2026
74cfacf
Merge: the Windows worker entry and startup checks (every new Windows…
ualtinok Oct 10, 2026
bafd737
Check text out with LF on every OS
ualtinok Oct 10, 2026
90525fc
mason: basal-launch shared spawn lock, OwnedProcess and spawn_plain f…
ualtinok Oct 10, 2026
6721037
mason: add Windows worker probes and production measurement tooling
ualtinok Oct 10, 2026
998fc1e
mason: port the shared test kit and rig to Windows
ualtinok Oct 10, 2026
ce788d3
mason: basal-module spawns, supervises and kills Windows workers thro…
ualtinok Oct 10, 2026
024bd1c
Merge (CI only): Windows test kit and rig
ualtinok Oct 10, 2026
794a020
mason: preload probe-only Winsock and refine measured provenance
ualtinok Oct 10, 2026
c40cfc8
mason: record production Windows handle provenance and passing probes
ualtinok Oct 10, 2026
3f896a5
mason: fix measured Windows test-kit and rig build failures
ualtinok Oct 10, 2026
585b98f
Merge (CI only): Windows test kit fix
ualtinok Oct 10, 2026
a65a248
Merge: Windows probes and production handle provenance (all probes pa…
ualtinok Oct 10, 2026
94973a0
mason: align native Windows path fixtures with verified roots
ualtinok Oct 10, 2026
0d42e48
mason: name each launch's station and TEMP directory randomly
ualtinok Oct 10, 2026
e9f980f
mason: Windows manifest roots, store-closing test fixtures and codemo…
ualtinok Oct 10, 2026
3b88388
Merge (CI only): 94973a0d36cc700a2c8f5d090dd4ee2188d260dd
ualtinok Oct 10, 2026
5c45fcf
Merge (CI only): Windows module fixes for manifest roots and launcher…
ualtinok Oct 10, 2026
dc5a30b
Re-anchor the worker dependency control on basal-rig's Windows lockfi…
ualtinok Oct 10, 2026
ea0759e
mason: 8 MiB main-thread stack reserve for ck-basal-worker on Windows
ualtinok Oct 10, 2026
42270ca
Merge (CI only): Windows probes and the worker stack reserve
ualtinok Oct 10, 2026
feb4ab4
mason: assert the Windows raw-path dispatch refusal code
ualtinok Oct 10, 2026
2418893
Merge (CI only): test kit dot-dot refusal on Windows
ualtinok Oct 10, 2026
0fc2af7
Merge: an 8 MiB main-thread stack for the Windows worker (the four st…
ualtinok Oct 10, 2026
ec19512
Merge: the module spawns, supervises and kills Windows workers throug…
ualtinok Oct 10, 2026
98c4a54
mason: exercise native rig process discovery with a real image
ualtinok Oct 10, 2026
97fe601
Merge (CI only): Windows rig process-path control
ualtinok Oct 10, 2026
2249366
mason: protect the rig image locator raw spelling
ualtinok Oct 10, 2026
bbfa982
Merge (CI only): exact Windows rig image path spelling
ualtinok Oct 10, 2026
97e08fd
Merge (CI only): integration/windows
ualtinok Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[env]
# Allocation accounting is unused; its global mutex serializes SQLite callers.
LIBSQLITE3_FLAGS = "-DSQLITE_DEFAULT_MEMSTATUS=0"

# Link the C runtime statically on Windows, so no image imports a C runtime
# DLL. The confined worker should load only the system's own DLLs, and the
# dynamic C runtime is a separately installed component.
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Check text out with LF on every OS. Tests read docs, fixtures and digest
# test vectors byte for byte, and a CRLF checkout on Windows would change them.
* text=auto eol=lf
90 changes: 86 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,11 @@ jobs:
# first failure. Every log is uploaded as the windows-baseline-logs artifact.
windows-baseline:
if: github.event_name != 'schedule'
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
os: [windows-latest, windows-2022]
runs-on: ${{ matrix.os }}
continue-on-error: true
timeout-minutes: 120
defaults:
Expand All @@ -381,11 +385,77 @@ jobs:
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('Cargo.lock') }}-${{ steps.rust.outputs.version }}-windows-baseline-${{ github.sha }}
key: ${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('Cargo.lock') }}-${{ steps.rust.outputs.version }}-windows-baseline-${{ matrix.os }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('Cargo.lock') }}-${{ steps.rust.outputs.version }}-windows-baseline-
${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('Cargo.lock') }}-${{ steps.rust.outputs.version }}-windows-baseline-${{ matrix.os }}-
- name: Prepare the log directory
run: mkdir -p "$RUNNER_TEMP/windows-baseline"
# The Windows launcher's own tests start real confined children, so
# they can only run here. The deviations feature adds the test-only
# launch variants, one per confinement check. They run first and print
# what the parent read back, so the log shows it even when the
# workspace steps below fail.
- name: Test the Windows launcher with its test variants
id: test-launch
continue-on-error: true
timeout-minutes: 20
run: |
cargo test -p basal-launch --locked -- --show-output 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-basal-launch-production.log"
cargo test -p basal-launch --features deviations --locked -- --show-output 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-basal-launch.log"
# The worker's tests on their own: its Windows startup checks, its image
# and a real activation, each worker started through the launcher. The
# plain build is the production worker, which must refuse the
# launcher's request to skip a check. The deviations build is a test
# worker that honours that request, for the three checks the parent
# cannot break from outside.
- name: Test the worker on its own
id: test-worker
continue-on-error: true
timeout-minutes: 30
run: cargo test -p basal-worker --locked --no-fail-fast -- --show-output 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-basal-worker.log"
# The creators and counts of production startup handles are measured
# once per Windows runner image. Every worker startup still compares its
# live handle table with the fixed per-image count limits; the image test
# rejects DLL imports absent from the worker's windows-imports.txt file.
- name: Witness the startup-handle and import-inventory mutants
continue-on-error: true
timeout-minutes: 10
run: python crates/basal-worker/tests/windows_mutation_witness.py "$RUNNER_TEMP/windows-baseline"
- name: Test the worker with its test variants
id: test-worker-deviations
continue-on-error: true
timeout-minutes: 30
run: cargo test -p basal-worker --features deviations --locked --no-fail-fast -- --show-output 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-basal-worker-deviations.log"
# The module's tests: workers started, supervised and killed through
# the launcher; worker deaths from a confinement fault (an invalid
# handle or access violation) counted apart from other crashes; and
# the store directory and database readable by their owner only.
# basal-core's tests run with them: the module's store and runtime
# are basal-core's.
- name: Test the module and the core
id: test-module
continue-on-error: true
timeout-minutes: 40
run: cargo test -p basal-module -p basal-core --locked --no-fail-fast -- --show-output 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-basal-module.log"
# Every Windows spawn takes one process-wide lock while its child's
# handles are inheritable. This test starts confined, plain and git
# children at once and checks that no child inherited another child's
# stdin, stdout or stderr.
- name: Test the shared spawn lock across the launcher and git
id: test-spawn-lock
continue-on-error: true
timeout-minutes: 20
run: cargo test -p basal-host --lib --locked -- spawn_lock_tests --show-output 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-spawn-lock.log"
- name: Test the shared test kit on its own
id: test-testkit
continue-on-error: true
timeout-minutes: 30
run: cargo test -p basal-testkit --locked --no-fail-fast 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-basal-testkit.log"
- name: Test the integration rig on its own
id: test-rig
continue-on-error: true
timeout-minutes: 30
run: cargo test -p basal-rig --locked --no-fail-fast 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-basal-rig.log"
- name: Check every workspace target
id: check
continue-on-error: true
Expand All @@ -406,6 +476,13 @@ jobs:
continue-on-error: true
timeout-minutes: 40
run: cargo test --workspace --locked --no-fail-fast 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-no-fail-fast.log"
# The workspace test can't build until every crate compiles on Windows,
# so basal-host's own tests run separately to report its built-ins now.
- name: Test basal-host on its own
id: test-host
continue-on-error: true
timeout-minutes: 30
run: cargo test -p basal-host --lib --locked --no-fail-fast 2>&1 | tee "$RUNNER_TEMP/windows-baseline/test-basal-host.log"
- name: Summarize the baseline
if: always()
run: |
Expand All @@ -414,10 +491,15 @@ jobs:
echo
echo "- cargo check --workspace --all-targets --locked: ${{ steps.check.outcome }}"
echo "- cargo test --workspace --locked: ${{ steps.test.outcome }}"
echo "- cargo test -p basal-launch --features deviations --locked: ${{ steps.test-launch.outcome }}"
echo "- cargo test -p basal-module -p basal-core --locked --no-fail-fast: ${{ steps.test-module.outcome }}"
echo "- cargo test -p basal-host --lib --locked -- spawn_lock_tests: ${{ steps.test-spawn-lock.outcome }}"
echo "- cargo test -p basal-testkit --locked --no-fail-fast: ${{ steps.test-testkit.outcome }}"
echo "- cargo test -p basal-rig --locked --no-fail-fast: ${{ steps.test-rig.outcome }}"
} >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
if: always()
with:
name: windows-baseline-logs
name: windows-baseline-logs${{ matrix.os == 'windows-2022' && '-windows-2022' || '' }}
path: ${{ runner.temp }}/windows-baseline
if-no-files-found: error
26 changes: 22 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"crates/basal-core",
"crates/basal-testkit",
"crates/basal-module",
"crates/basal-launch",
"crates/basal-rig",
]

Expand All @@ -20,6 +21,7 @@ rust-version = "1.88"
# Dependencies do not require sibling checkouts.
basal-core = { path = "crates/basal-core" }
basal-host = { path = "crates/basal-host" }
basal-launch = { path = "crates/basal-launch" }
basal-proto = { path = "crates/basal-proto" }
basal-testkit = { path = "crates/basal-testkit" }
blake3 = "1.8"
Expand All @@ -34,7 +36,7 @@ croner = { version = "4.0", default-features = false, features = ["jiff"] }
jiff = { version = "0.2.37", default-features = false, features = ["std", "tzdb-bundle-always"] }
# The fleet's storage mechanics: open with the single-writer lease, WAL and a
# busy timeout, and versioned migrations. Pinned exactly, as astrocyte does.
cortexkit-store = { version = "=0.2.1", features = ["sqlite"] }
cortexkit-store = { version = "=0.2.4", features = ["sqlite"] }
cortexkit-store-types = "0.2.2"
# The provider-owned resource-busy refusal is shared across the fleet.
cortexkit-resource-busy = { git = "https://github.com/cortexkit/commons", rev = "c46f324133c5df6411703714e4c147f7cc8554a0" }
Expand Down
92 changes: 92 additions & 0 deletions crates/basal-core/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,35 @@ fn check_root(field: &str, root: &str) -> Result<(), ManifestError> {
if root.chars().any(char::is_control) {
return Err(invalid(field, format!("{root:?} has a control character")));
}
if cfg!(windows) {
check_windows_root(field, root)
} else {
check_unix_root(field, root)
}
}

/// A Windows root is spelt exactly as the Windows `fs` built-in accepts a
/// path: an absolute drive path (`C:\dir`), never a UNC, device,
/// extended-length or drive-relative path, an alternate data stream, a `.`
/// or `..` component, or a reserved device name. `~` and `~/...` (the
/// user's profile folder) stay accepted as on Unix, with each component
/// under `~/` held to the same rules.
fn check_windows_root(field: &str, root: &str) -> Result<(), ManifestError> {
let spelling = if root == "~" {
return Ok(());
} else if let Some(rest) = root.strip_prefix("~/") {
// The components are checked as if under a drive root; the drive
// letter itself is never used.
format!("C:\\{}", rest.replace('/', "\\"))
} else {
root.to_owned()
};
basal_host::builtins::fs::windows::validate_raw_spelling(&spelling)
.map_err(|denial| invalid(field, format!("{root:?}: {}", denial.message)))
}

/// A Unix root starts with `/` or `~/` (or is `~`).
fn check_unix_root(field: &str, root: &str) -> Result<(), ManifestError> {
let rest = if root == "~" {
""
} else if let Some(rest) = root.strip_prefix("~/") {
Expand Down Expand Up @@ -726,6 +755,69 @@ impl Manifest {
mod tests {
use super::*;

#[test]
fn windows_roots_are_the_fs_built_ins_drive_paths_or_under_the_profile() {
for root in [
r"C:\x",
r"C:\",
r"d:\Users\me\notes",
"~",
"~/",
"~/notes",
"~/notes/2026",
] {
assert_eq!(check_windows_root("fs.read", root), Ok(()), "{root}");
}
for root in [
r"\\server\share\x",
"//server/share/x",
r"\\?\C:\x",
r"\\.\C:\x",
r"\??\C:\x",
r"C:x",
"C:",
r"C:\x\file.txt:stream",
r"C:\x::$DATA",
r"C:\x\..\y",
r"C:\x\.",
r"C:\x\CON",
"C:/x",
"/x",
"x",
"~/../x",
"~/a:b",
r"~\x",
] {
assert!(
matches!(
check_windows_root("fs.write", root),
Err(ManifestError::Invalid { ref field, .. }) if field == "fs.write"
),
"{root} was accepted"
);
}
}

/// The platform's own grammar is the one a manifest is parsed with.
#[test]
fn manifest_roots_follow_this_platforms_spelling() {
let parse = |root: &str| {
Manifest::parse(
&serde_json::json!({
"id":"roots", "version":1, "purpose":"test",
"trigger":{"events":[{"module":"echo","name":"tick","version":1}]},
"fs": {"read": [root]}
})
.to_string(),
)
.map(|_| ())
};
assert!(parse("~/notes").is_ok());
assert_eq!(parse(r"C:\x").is_ok(), cfg!(windows));
assert_eq!(parse("/x").is_ok(), !cfg!(windows));
assert!(parse(r"\\server\share").is_err());
}

#[test]
fn multibyte_durations_are_refused_without_panicking() {
for text in ["é", "10é", "10中", "10🦀", "é1s", "١s", "1sé"] {
Expand Down
Loading
Loading