Build the debug artifacts the session probe drives, and act on a scanner report - #6
Merged
Merged
Conversation
The verification section described signed binaries in the present tense while the only release a reader can download is v0.1.0, which predates all of it. Somebody following the README today would have met an unknown-publisher warning and concluded the page was lying. It now says which release carries what, and that neither a signature nor an attestation can be granted after the fact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t source
A `${{ }}` inside a `run:` block is expanded before the shell reads the script, so the
value lands in the source of the script and the quotes around it protect nothing. Git
allows a quote, a backtick and a semicolon in a ref name, so a tag could close the string
it was pasted into and continue as code - in jobs holding `id-token: write` and
`attestations: write`, which is enough to mint an attestation with the repository identity.
Measured rather than assumed. With the tag `v9.9.9';Set-Content ...;'` the old form ran the
payload and the variable held only `v9.9.9`, and the new form printed the whole tag and ran
nothing. The same probe reports INCONCLUSIVE if the old form ever stops firing, so it cannot
go quietly green.
Every interpolation left in these two scripts is gone, not only the ones a scanner calls
dangerous: `github.sha` and `github.repository` now come from the built-in environment
variables. "No `${{ }}` inside a `run:` block" is an invariant one grep can check, while a
list of contexts believed to be safe is a judgement that rots.
The shape was already in the repository - verify-release.yml has read `$env:TAG` since it
was written, and it is the one release workflow the scan had nothing to say about.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ses it A newly published version is the shape a supply chain attack arrives in, and this project has one maintainer, so the cheapest defence available is to let other people meet a release first. Seven days on all three ecosystems, over the three Dependabot has applied by default since 2026-07-14. It delays no fix for a known vulnerability: security updates ignore the cooldown by design, and `cargo deny check advisories` in audit.yml asks that question weekly on its own. The actions entry gets one too, and there it is the point rather than a precaution - a pinned SHA is exactly what an attacker wants moved, and that entry exists to move SHAs. Only `default-days`, because the options reference marks GitHub Actions as supporting that and not the per-SemVer-bump ones. Checked against the published dependabot-2.0 schema rather than by eye: a config error would stop Dependabot silently, which would cost more than the finding it closes. The check was seen refusing a mistyped key and a wrong value type before it was believed on the real file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A scan reported eleven findings from rules written for a web framework and for another language: path traversal and command injection from an Actix ruleset, and an insecure WebSocket rule from a JavaScript one. All eleven were read in the code rather than by severity, and none of them reaches this program. The site crate ships with nothing, no other crate depends on it, and it is in neither package. It runs from CI and from a maintainer's machine, and every path it opens is a directory named on its own command line or a name read out of this repository. Its one deleting path already refuses to empty a directory without the marker it wrote itself, which is a stronger guard than the rule would have asked for. The command that launches a target is the whole function of this tool, aimed by the user at their own machine under their own token, and `std::process::Command` gives arguments to the program literally, so there is no shell for a metacharacter to reach. The protocol being read has no encrypted scheme to move to, and the risk that rule points at is answered by `ws_endpoint_is_ours`, which pins the endpoint to the port we opened. Suppressed per rule id rather than per file, so a different rule still reports at these lines. Ten of the eleven were watched appearing and then disappearing: 3 and 3 in the site crate and 4 in the protocol client, all to zero. The eleventh does not reproduce under the open ruleset, so its marker names the reported rule and is unproven - said here rather than left to look verified. The first version of one of these notes tripped the very rule it explains, by spelling the scheme out in prose. The prose was changed, not the marker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI has been red on main since the commit that added tests/dry_run.rs, four pushes ago, on one test: the counter-probe that drives a real session to prove the dry-run assertion can fail. It refused before starting anything, reporting chrono_hook.dll missing beside chrono.exe. The cause is not the test and not the product. `cargo test` never emits that library: a test build compiles chrono-hook as a test harness, so the cdylib is not uplifted into target/debug. On a developer machine it is there from an earlier `cargo build`, so the probe passed by hand and failed on every clean runner from the day it was written. Measured rather than reasoned: parking the file locally reproduced the runner's failure word for word, and `cargo test --workspace` did not put it back. CI now builds before it tests, which costs a cdylib link because the dependencies were going to be compiled by the next step anyway. Two things guard the fix. The probe asks for the library first, so a checkout without a build gets the file name and the command to run, instead of the product's message about an incomplete installation - true for a user holding half a package, misleading for whoever is running the tests. And a second test reads the workflow, because the precondition lives in another file and the local gates build too, so nothing else would notice the step leaving. That is exactly how this was missed: the local gates reported all green while every push was red. The guard was watched failing with the step removed and with the step moved below the tests. Rust tests 418 to 419, and the count is the point: a guard nobody has seen fail is decoration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why CI is red, and what makes it green
CI has been failing on
mainfor four pushes, since the commit that addedcrates/cli/tests/dry_run.rs. One test fails: the counter-probe that drives a real session so the--dry-runassertion is known to be capable of failing.The cause is neither the test nor the product.
cargo testnever emitschrono_hook.dll- a test build compileschrono-hookas a test harness, so the cdylib is not uplifted intotarget/debug, and the session refuses before it starts anything. On a developer machine that file is present from an earliercargo build, so the probe passed by hand and failed on every clean runner from the day it was written.Measured rather than reasoned: parking the file locally reproduced the runner's failure word for word, and
cargo test --workspacedid not put it back.cargo build --workspacedid, in 3,4 s.Three changes, not one:
is_file, exactly whatcore::hook_dll_inasks.Rust tests go from 418 to 419, and the count is the point: a guard nobody has seen fail is decoration.
A matching gate was added to the local gate script so it can no longer be green while CI is red. That file is outside the repository, so it is not in this diff.
Not verified
Whether this test can pass on a hosted runner at all is unknown. The fix addresses the proven cause. This counter-probe would be the first real injection performed in CI, and runner policy could refuse it for a different reason. This run is how we find out.
Also in this branch
Four commits that were sitting unpushed, three of them acting on a static-analysis report.
run:blocks interpolated${{ }}into the body of the script. Expansion is textual and happens before the shell, so the quotes in the YAML protect nothing, and a git ref name may contain a quote, a backtick and a semicolon - in jobs holdingid-token: writeandattestations: write. Proven with a probe: with a hostile tag the old form executed the payload and the variable held only the leading characters, the new form printed the whole tag and ran nothing. Every interpolation is gone from those scripts, not only the ones a scanner calls dangerous, because "no${{ }}inside arun:block" is something one grep can check.🤖 Generated with Claude Code