Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions crates/bashkit-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ pip install 'bashkit[pydantic-ai]'
pip install 'bashkit[deepagents]'
```

### Windows 11 Smart App Control

If importing the native module fails with `An Application Control policy has
blocked this file`, PondGuard's reported workaround is to pin
`bashkit==0.20.0` and run tests with `python -m pytest`. Keep Smart App Control
enabled. The pin was verified on the reporter's machine; both 0.20.0 and 0.20.1
Windows modules lack embedded Authenticode signatures, so other machines may
make a different trust decision. `python -m pytest` avoids a blocked launcher
but cannot load a blocked native module. See the
[Windows reproduction, evidence, and workaround guide](https://bashkit.sh/docs/start-python/#windows-11-smart-app-control).

## Quick Start

### Sync Execution
Expand Down
134 changes: 134 additions & 0 deletions docs/start-python.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,140 @@ Runnable Python examples in the repo:
- [`llm_tool.py`](https://github.com/everruns/bashkit/blob/main/crates/bashkit-python/examples/llm_tool.py), exposing Bashkit as an LLM tool
- Agent integrations: [deepagents](https://github.com/everruns/bashkit/blob/main/examples/deepagent_coding_agent.py), [Pydantic AI](https://github.com/everruns/bashkit/blob/main/examples/pydantic_ai_bash_agent.py)

## Windows 11 Smart App Control

If importing Bashkit raises `DLL load failed ... An Application Control policy
has blocked this file`, first identify the file in Windows' Code Integrity log.
The same error can come from a dependency or another App Control policy.
[PondGuard reported](https://github.com/onlyarya2004-maker/pondguard/commit/2f7c1a7770930b6bc1f7a4928376816668250c00)
that Bashkit 0.20.1 was blocked on its Windows 11 / Python 3.11.9 laptop, while
0.20.0 loaded. This is a machine-specific report, not a guarantee that 0.20.0
will be allowed on every machine.

### Immediate workaround

Keep Smart App Control enabled. In the affected application's virtual environment:

```powershell
python -m pip install --only-binary=:all: --no-cache-dir "bashkit==0.20.0"
python -m pip check
python -c "from bashkit import BashTool; r = BashTool().execute_sync('echo hello'); assert r.exit_code == 0; print(r.stdout)"
python -m pytest
```

Persist the pin when resolving the application's dependencies. For example, add
`bashkit==0.20.0 ; sys_platform == 'win32'` to a constraints file and use
`python -m pip install -r requirements.txt -c constraints-windows.txt` on every
install. An existing lockfile must also retain the pin. Recheck the pin after a
signed release has been tested on the affected machine.

`python -m pytest` avoids a blocked `pytest.exe` launcher; it cannot bypass a
blocked `.pyd`. If only unrelated pytest plugin discovery imports Bashkit,
`$env:PYTEST_DISABLE_PLUGIN_AUTOLOAD = "1"` can let independent tests run, but
explicitly enable each required plugin with `-p`. This disables plugin-provided
fixtures and hooks and does not repair Bashkit. Remove the variable afterward
with `Remove-Item Env:PYTEST_DISABLE_PLUGIN_AUTOLOAD`.

### Reproduce and collect evidence

Use a Windows 11 x64 machine with Smart App Control **On**, confirmed in Windows
Security → App & browser control → Smart App Control settings. Evaluation mode
does not prove enforcement. Use Python 3.11.9 x64 to match the report, record
`py -3.11 --version`, and leave policy settings unchanged. Normal hosted Windows
CI and Linux import tests do not establish SAC compatibility.

Run this PowerShell comparison from a writable folder. Each version gets a
fresh environment and the published binary wheel; no source build or pip cache
is used. Signatures and hashes are collected without importing Bashkit:

```powershell
Start-Transcript -Path ("sac-" + (Get-Date -Format "yyyyMMdd-HHmmss") + ".txt")
$started = Get-Date
Get-ComputerInfo | Select-Object WindowsProductName, OsVersion, OsBuildNumber
py -3.11 --version
py -3.11 -c "import platform, struct; print(platform.machine(), struct.calcsize('P') * 8)"
foreach ($version in @("0.20.0", "0.20.1")) {
$venv = ".sac-$version"
if (Test-Path $venv) { throw "$venv already exists; use a fresh folder" }
py -3.11 -m venv $venv
if ($LASTEXITCODE -ne 0) { throw "venv creation failed" }
$python = "$venv\Scripts\python.exe"
& $python -m pip install --index-url https://pypi.org/simple --only-binary=:all: --no-cache-dir --report "$version-install.json" "bashkit==$version"
if ($LASTEXITCODE -ne 0) { throw "installation failed for $version" }
Get-ChildItem "$venv\Lib\site-packages\bashkit" -Recurse -Include *.pyd,*.dll |
ForEach-Object {
Get-FileHash $_.FullName -Algorithm SHA256
Get-AuthenticodeSignature $_.FullName |
Format-List Path, Status, StatusMessage, SignerCertificate, TimeStamperCertificate
}
& $python -c "from bashkit import BashTool; r = BashTool().execute_sync('echo hello'); assert r.exit_code == 0; print(r.stdout)" 2>&1 |
Tee-Object "$version-import.txt"
"Import exit code for ${version}: $LASTEXITCODE"
}
Get-WinEvent -FilterHashtable @{
LogName = "Microsoft-Windows-CodeIntegrity/Operational"
StartTime = $started
} | ForEach-Object { $_.ToXml() } | Set-Content sac-codeintegrity.xml
Stop-Transcript
```

Save the terminal output too. If log access is denied, collect it from an
elevated PowerShell window. A query with no matching events is not proof of a
SAC block. Inspect event **3077** for the enforced block, its file path and
policy identity; correlate **3089** signature events by ActivityID. **3076** is
an audit event. Check whether the blocked path is `_bashkit.pyd`, another DLL,
or a launcher, and whether the policy is SAC or a separately managed policy.
See Microsoft's [Code Integrity event reference](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/operations/event-id-explanations).

For the pytest symptom, use the affected project's environment and keep every
other dependency fixed. Record `python -m pip freeze` and `python -m pytest
--trace-config --collect-only` with each Bashkit version; identify the importing
plugin from the traceback. Swap only Bashkit with `python -m pip install
--only-binary=:all: --no-cache-dir --no-deps --force-reinstall "bashkit==0.20.1"`,
then repeat with `0.20.0`. Reapply the working pin afterward. Installing only
Bashkit and pytest in a clean environment does not recreate the project's
transitive plugin imports.

### Artifact comparison and permanent fix

The published `cp39-abi3-win_amd64` wheels were inspected on 2026-10-27. Their
SHA-256 digests matched PyPI's release JSON. Both use maturin 1.15.0 and contain
an x64 `bashkit/_bashkit.pyd` with an empty PE certificate table: neither has an
embedded Authenticode signature. Windows signature checks above additionally
show any catalog-based trust available on that machine.

| Version | Wheel SHA-256 | `_bashkit.pyd` SHA-256 |
|---|---|---|
| 0.20.0 | `4bcee1e7bf4bed6b55d80c04694c95f1116685d847fbd9bfce42f76740e7915c` | `3d1ff960bba0ad3d24934622ef298e0feede430091c2a8214de0c7ab06170354` |
| 0.20.1 | `948f3f73a3e1289caeba7581330732fd145d5b26e5c018d97345c1bd92ce9099` | `e84d96745a00a6966089e164eb9cab8527ffb9a6094affbe5c4583b4c2e30e80` |

Sources: [0.20.0 release metadata](https://pypi.org/pypi/bashkit/0.20.0/json),
[0.20.1 release metadata](https://pypi.org/pypi/bashkit/0.20.1/json).
For both wheel filenames, PyPI's
`/integrity/bashkit/{version}/{filename}/provenance` endpoint returned HTTP 404
at inspection time; no PyPI-hosted attestation was available there. The
`publish-python.yml` files at the two release tags are identical and use OIDC
trusted publishing without an Authenticode signing step. Publisher authentication,
artifact attestations, and Windows code signing are separate mechanisms.

These observations do not establish why one binary was allowed and the other
blocked. Microsoft's [SAC FAQ](https://support.microsoft.com/en-us/windows/smart-app-control-frequently-asked-questions-285ea03d-fa88-4d56-882e-6698afdb7003)
explains that unsigned apps can be allowed through cloud reputation. Different
binary reputation is a hypothesis until the affected machine's events confirm
the policy decision. The SAC failure has not been reproduced in the Linux
artifact-inspection environment.

The durable packaging change is to Authenticode-sign the Windows `.pyd` and any
bundled DLLs with a trusted code-signing identity and timestamp, then verify
their signatures. Sign before final wheel packing, or unpack/sign/repack while
regenerating wheel `RECORD` hashes. Install that exact wheel on a SAC-On Windows
11 machine and test native import, execution, and the affected pytest collection
before publishing a new patch version. Do not replace existing PyPI artifacts.
Use Microsoft's [SAC signing guidance](https://learn.microsoft.com/en-us/windows/apps/develop/smart-app-control/code-signing-for-smart-app-control)
to select a trusted signing provider. A self-signed certificate or a PyPI
attestation alone does not supply that trust. Signing credentials and a SAC-On
validation host are required before claiming this fix is shipped.

## Next steps

- [Sandbox configuration & limits](configuration.md), resource limits and sandbox options.
Expand Down
2 changes: 2 additions & 0 deletions knowledge/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## 2026-10-27

* **Windows Python packaging**: Document the reported 0.20.0 pin for the PondGuard SAC block and a fresh-environment A/B procedure with file hashes, Authenticode checks, and correlated Code Integrity events. Both published 0.20.0/0.20.1 x64 modules lack embedded signatures; both PyPI wheel provenance endpoints returned 404. No Windows reproduction or permanent signing fix is claimed. See [Python Package](runtimes/python-package.md).

* **Supply chain**: A first-party crate reaching crates.io silently breaks `cargo vet`. cargo-vet calls a workspace member first-party by provenance -- it was not fetched from crates.io -- so once the same name and version is also published, the two descriptions collide and the command refuses to run until `policy.<name>.audit-as-crates-io` says which to believe. `bashkit-scripted-tool` went public in 0.20.0 with no entry and nothing failed, because CI runs `cargo vet --locked`, which never reaches the collision check. The Audit job stayed green while the command was broken for every developer and for cargo-vet itself: refreshing `supply-chain/imports.lock` needs the non-locked run, so a bump that relies on publisher trust could not be made to pass at all. #2790's tokio 1.54.1 was exactly that bump -- it rides `[[trusted.tokio]]` (Alice Ryhl) plus a publisher fact, not an audit -- which is how a four-release-old gap surfaced. `scripts/tests/test_ci_supply_chain.py` now derives the published set from the `release-check` recipe and fails when any of those crates lacks a first-party policy entry; a missing one cannot be caught by the gate that matters, since `--locked` is the gate. See [Dependencies](operations/dependencies.md).
* **Operations**: The `rust-dependencies` group bump (tokio 1.53.3 -> 1.54.1, jaq-core 3.1.1 -> 3.1.2, jaq-std 3.0.3 -> 3.0.4, napi 3.14.2 -> 3.15.0, napi-derive 3.6.12 -> 3.6.13, plus napi-derive-backend and napi-sys behind them) failed Audit alone with seven unvetted crates -- the same rule as #2706/#2720 and the 2026-10-18 entry: a Dependabot group bump cannot go green on its own, because nothing in the bump records the audits the new versions need. Dependabot's lockfile also carried collateral its own description does not claim: six `windows-sys` 0.61.2 -> 0.52.0 re-points and `tempfile` moving to getrandom 0.4.3. Those are opportunistic re-resolution, not part of the five updates, and are left out -- the same call #2789 made.
* **Dependencies**: Two findings worth keeping from reviewing the deltas. jaq-std 3.0.4 rewrites `scan` to jq's semantics, and it is inert here: `builtins/jq/compat.rs` prepends its own `scan` to every filter and shadows the stdlib one. Keep that override rather than deferring to upstream -- upstream's new definition is `scan_(re; "g" + flags)`, which produces `"gg"` and a double-`g` compile error for a filter that already passes `g`, the exact case the compat definition was written to avoid. jaq-core 3.1.2 fixes a lexer panic on a comment that ends the input (the scanner substituted a `""` literal, breaking the invariant that the replacement is a suffix of the previous input); it does not reproduce through the jq builtin on 3.1.1, checked across eighteen filter shapes including malformed ones, all of which returned identical output on both versions. `filter_ending_in_a_comment_is_accepted` now pins the shape, which had no coverage. napi 3.15.0 is the delta that needed real reading: it adds a `napi_run_script` call, on a constant three-line source with no interpolation and only on the ReadableStream cancel path, and its new `class_identity` module checks the recorded Rust class before casting a `napi_unwrap` result, which matters here because `bashkit-js` selects `napi6` and so has no Node-API type tag to check instead. See [Vendored jaq-json](runtimes/jaq-json-vendor.md), [Node Package](runtimes/node-package.md).
Expand Down
23 changes: 23 additions & 0 deletions knowledge/runtimes/python-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,29 @@ Auth: PyPI trusted publishing (OIDC), no API tokens. Prerequisites: GitHub
environment `release-python` exists; PyPI trusted publisher configured for
`everruns/bashkit`, workflow `publish-python.yml`, environment `release-python`.

## Windows Smart App Control

PondGuard reported that the 0.20.1 Windows native module was blocked on Windows
11 / Python 3.11.9 while 0.20.0 loaded. The published x64 wheels were inspected
on 2026-10-27: both use maturin 1.15.0, both native PE certificate tables are
empty, and both PyPI wheel provenance endpoints return 404. The release-tag
`publish-python.yml` files are identical. Trusted publishing authenticates an
upload; it does not Authenticode-sign the native module. A missing signature
introduced in 0.20.1 is therefore not supported by the artifact evidence.

The [Python guide](../../docs/start-python.md#windows-11-smart-app-control)
records wheel/native hashes, the temporary Windows constraint, and a SAC-On
comparison with Code Integrity events. The policy failure remains reported,
not independently reproduced on Windows. Cloud reputation is a hypothesis, not
a demonstrated root cause; 0.20.0 is not guaranteed to load everywhere.

Decision: preserve SAC, document the reported pin, and require trusted
Authenticode signing plus import/execution/affected-plugin collection on a
SAC-On Windows 11 host before declaring a permanent packaging fix. Sign native
files before final wheel packing (or regenerate `RECORD` when repacking), then
publish a new patch version. Standard hosted Windows smoke jobs do not prove
SAC enforcement. See [Release Process](../operations/release-process.md).

## Public API

Full signatures: `crates/bashkit-python/bashkit/_bashkit.pyi`. Runnable
Expand Down
Loading