-
Notifications
You must be signed in to change notification settings - Fork 17
feat: add Windows support #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,6 +60,8 @@ jobs: | |
| - os: ubuntu-latest | ||
| target: aarch64-unknown-linux-gnu | ||
| manylinux: "2_28" | ||
| - os: windows-latest | ||
| target: x86_64-pc-windows-msvc | ||
| runs-on: ${{ matrix.os }} | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
@@ -158,7 +160,7 @@ jobs: | |
| set -euo pipefail | ||
| echo "Dists:"; ls -1 dist/ | ||
| missing=() | ||
| for target in aarch64-apple-darwin x86_64-apple-darwin x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; do | ||
| for target in aarch64-apple-darwin x86_64-apple-darwin x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu x86_64-pc-windows-msvc; do | ||
| # Maturin encodes the target in the wheel filename's platform tag: | ||
| # aarch64-apple-darwin → macosx_*_arm64 | ||
| # x86_64-apple-darwin → macosx_*_x86_64 | ||
|
|
@@ -169,6 +171,7 @@ jobs: | |
| x86_64-apple-darwin) pattern="macosx_*_x86_64" ;; | ||
| x86_64-unknown-linux-gnu) pattern="manylinux_*_x86_64" ;; | ||
| aarch64-unknown-linux-gnu) pattern="manylinux_*_aarch64" ;; | ||
| x86_64-pc-windows-msvc) pattern="win_amd64" ;; | ||
| esac | ||
| if ! ls dist/*${pattern}*.whl >/dev/null 2>&1; then | ||
| missing+=("$target") | ||
|
|
@@ -228,6 +231,8 @@ jobs: | |
| target: x86_64-unknown-linux-gnu | ||
| - os: ubuntu-latest | ||
| target: aarch64-unknown-linux-gnu | ||
| - os: windows-latest | ||
| target: x86_64-pc-windows-msvc | ||
| runs-on: ${{ matrix.os }} | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
@@ -285,9 +290,9 @@ jobs: | |
| native.js | ||
| native.d.ts | ||
|
|
||
| # ── Pack all 5 npm packages (fan-in from the per-platform builds) ───────── | ||
| # ── Pack all 6 npm packages (fan-in from the per-platform builds) ───────── | ||
| # Assembles the exact publishable set — the main @strands-agents/shell package | ||
| # plus the 4 per-platform packages — using the napi-rs v3 release flow, then | ||
| # plus the 5 per-platform packages — using the napi-rs v3 release flow, then | ||
| # packs each to a .tgz and uploads them. Download the `npm-packages` artifact | ||
| # to install/test the real tarballs locally before any publish happens. | ||
| node-pack: | ||
|
|
@@ -346,18 +351,18 @@ jobs: | |
| for d in npm/*/; do (cd "$d" && npm pack --pack-destination "$GITHUB_WORKSPACE/dist-npm"); done | ||
|
|
||
| - name: Verify the packaged set matches the publish matrix | ||
| # Guard against drift: the publish stage uses a static matrix of the 4 | ||
| # platform packages (+ the main package = 5 total). If package.json's | ||
| # Guard against drift: the publish stage uses a static matrix of the 5 | ||
| # platform packages (+ the main package = 6 total). If package.json's | ||
| # napi.targets gains/loses a target, the packed count changes here and | ||
| # this fails the run BEFORE anything is published — prompting whoever | ||
| # changed the targets to update node-publish-platform's matrix to match. | ||
| run: | | ||
| set -euo pipefail | ||
| expected=5 | ||
| expected=6 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟢 nice-to-have (no functional impact — the enforced
Purely prose; the actual matrices and the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in |
||
| actual=$(ls dist-npm/*.tgz | wc -l | tr -d ' ') | ||
| echo "Packed tarballs ($actual):"; ls -1 dist-npm/*.tgz | ||
| if [ "$actual" -ne "$expected" ]; then | ||
| echo "::error::Expected $expected npm packages (1 main + 4 platform) but packed $actual. If napi.targets changed, update node-publish-platform's matrix to match." | ||
| echo "::error::Expected $expected npm packages (1 main + 5 platform) but packed $actual. If napi.targets changed, update node-publish-platform's matrix to match." | ||
| exit 1 | ||
| fi | ||
|
|
||
|
|
@@ -374,7 +379,7 @@ jobs: | |
| name: npm-packages | ||
| path: dist-npm/*.tgz | ||
|
|
||
| # ── Publish the 4 per-platform packages (one independently retriable job each) | ||
| # ── Publish the 5 per-platform packages (one independently retriable job each) | ||
| # Each leg publishes exactly one tarball that node-inspect packed + uploaded, | ||
| # so a flaky single platform can be re-run on its own via "Re-run failed jobs" | ||
| # without touching the others. The matrix is static — its entries must match | ||
|
|
@@ -401,6 +406,7 @@ jobs: | |
| - strands-agents-shell-darwin-arm64 | ||
| - strands-agents-shell-linux-x64-gnu | ||
| - strands-agents-shell-linux-arm64-gnu | ||
| - strands-agents-shell-win32-x64-msvc | ||
| env: | ||
| V: ${{ needs.version.outputs.version }} | ||
| steps: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -121,20 +121,35 @@ impl VfsKernel { | |
| canon_base: &std::path::Path, | ||
| ) -> io::Result<Fd> { | ||
| if flags.read && !flags.write { | ||
| // Open the file, then verify via /proc/self/fd that the | ||
| // opened fd still points within the bind mount. This | ||
| // eliminates the TOCTOU between canonicalize and read. | ||
| let file = std::fs::File::open(host_path)?; | ||
| use std::os::unix::io::AsRawFd; | ||
| let fd_path = format!("/proc/self/fd/{}", file.as_raw_fd()); | ||
| if let Ok(real) = std::fs::read_link(&fd_path) | ||
| && !real.starts_with(canon_base) | ||
|
|
||
| // Defense-in-depth TOCTOU check: after opening, verify via | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are big comments like these common across the project?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| // /proc/self/fd that the opened fd still points within the bind | ||
| // mount. This closes the race between canonicalize() and open(). | ||
| // | ||
| // This is Linux-only: /proc/self/fd is a Linux procfs interface | ||
| // with no portable equivalent (macOS has no /proc; Windows has no | ||
| // procfs at all). On other platforms the canonical-path check | ||
| // performed before this function is called is the primary security | ||
| // boundary, so we simply skip this extra verification there. | ||
| #[cfg(target_os = "linux")] | ||
| { | ||
| return Err(io::Error::new( | ||
| io::ErrorKind::PermissionDenied, | ||
| "access denied: path escaped bind mount", | ||
| )); | ||
| use std::os::unix::io::AsRawFd; | ||
| let fd_path = format!("/proc/self/fd/{}", file.as_raw_fd()); | ||
| if let Ok(real) = std::fs::read_link(&fd_path) | ||
| && !real.starts_with(canon_base) | ||
| { | ||
| return Err(io::Error::new( | ||
| io::ErrorKind::PermissionDenied, | ||
| "access denied: path escaped bind mount", | ||
| )); | ||
| } | ||
| } | ||
| // `canon_base` is only consumed by the Linux-only check above; on | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is self explanatory to a rust engineer.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair — the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like the same function but you used an import differently and blocked. A strategy for conditional compilation around the unix import and symlink and cargo env check around the blocking behaviour might reduce this to one function, which could then be inlined ~possibly. Only if it improves readability.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ignore that, meant to comment elsewhere. |
||
| // other targets it is intentionally unused. | ||
| #[cfg(not(target_os = "linux"))] | ||
| let _ = canon_base; | ||
|
|
||
| use std::io::Read; | ||
| let mut data = Vec::new(); | ||
| let mut file = file; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4277,6 +4277,9 @@ fn builder_bind_direct_readonly_host() { | |
| })); | ||
| } | ||
|
|
||
| // Uses std::os::unix::fs::symlink — symlink creation differs on Windows | ||
| // (requires elevated privileges), so this escape test is Unix-only. | ||
| #[cfg(unix)] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may not need conditional compilation. It's often more reliable to check cargo envvars at compile time since linting works on all platforms. Worth double checking.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good instinct, but here the gate has to be a compile-time
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like the same function but you used an import differently and blocked. A strategy for conditional compilation around the unix import and symlink and cargo env check around the blocking behaviour might reduce this to one function, which could then be inlined ~possibly. Only if it improves readability. |
||
| #[test] | ||
| fn bind_direct_symlink_escape_blocked() { | ||
| let (rt, local) = rt(); | ||
|
|
@@ -5349,7 +5352,7 @@ umask = "077" | |
|
|
||
| [[bind]] | ||
| mode = "copy" | ||
| source = "{}" | ||
| source = '{}' | ||
| destination = "/workspace" | ||
| readonly = true | ||
|
|
||
|
|
@@ -7381,6 +7384,9 @@ expect!(cmd_sleep_zero, "command sleep 0 && echo ok", "ok"); | |
|
|
||
| // ── dangling symlink escape prevention ────────────────────────────── | ||
|
|
||
| // Uses std::os::unix::fs::symlink — symlink creation differs on Windows | ||
| // (requires elevated privileges), so this escape test is Unix-only. | ||
| #[cfg(unix)] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same reasoning as the other symlink test —
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as before. |
||
| #[test] | ||
| fn bind_direct_dangling_symlink_blocked() { | ||
| let (rt, local) = rt(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically speaking, since this is a rust project the whole CI/CD including maturin could be rust.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed in principle — a Rust-driven CI/CD (e.g. an xtask that wraps maturin/napi) would be cleaner and more consistent. This PR keeps the existing shell-based matrix to stay scoped to Windows enablement, but it's a good follow-up; happy to file an issue for a CI-in-Rust pass if you'd like.