From d01419249e759e7a60193daab7a07970c073ae24 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Wed, 28 Oct 2026 00:10:59 +0000 Subject: [PATCH] chore(python): document Smart App Control workaround and reproduction --- crates/bashkit-python/README.md | 11 +++ docs/start-python.md | 134 +++++++++++++++++++++++++++ knowledge/log.md | 2 + knowledge/runtimes/python-package.md | 23 +++++ 4 files changed, 170 insertions(+) diff --git a/crates/bashkit-python/README.md b/crates/bashkit-python/README.md index ea58ff4ec..4ddab0956 100644 --- a/crates/bashkit-python/README.md +++ b/crates/bashkit-python/README.md @@ -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 diff --git a/docs/start-python.md b/docs/start-python.md index 09391f4a0..d1741ddf7 100644 --- a/docs/start-python.md +++ b/docs/start-python.md @@ -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. diff --git a/knowledge/log.md b/knowledge/log.md index 17424e3c6..84d9d623e 100644 --- a/knowledge/log.md +++ b/knowledge/log.md @@ -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..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). diff --git a/knowledge/runtimes/python-package.md b/knowledge/runtimes/python-package.md index 68880a6c0..0b5324f93 100644 --- a/knowledge/runtimes/python-package.md +++ b/knowledge/runtimes/python-package.md @@ -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