Update cargowall to v2.0.0-rc.7 and carry the host search list (#84) - #85
Merged
Merged
Conversation
Bumps the pinned binary to v2.0.0-rc.7 (digests repinned from the release checksums, attestation verified on both arches at pin time). rc.7 is three DNS-proxy changes and no new flags: systemd-resolved synthetic names are answered rather than left to NXDOMAIN, `_gateway` is usable as a rule value, and the host's own resolv.conf search list becomes a strip-only suffix source. That last one never engaged under this action. The action replaced /etc/resolv.conf with a bare `nameserver 127.0.0.1` line before spawning cargowall, and the daemon reads the search list from that same file when its DNS server starts — so the list was empty in every job, while the feature worked for cargowall run any other way. Fixes #84. The replacement file is now built in TypeScript (`proxyResolvConf`, beside the existing resolv.conf parser) and fed to `sudo tee` on stdin, so DHCP-written text never reaches a shell. `repointResolvConf`/`readResolvConf` sit beside `restoreDns` as its inverse, with a successful read as the single existence signal: a file the runner user cannot read is read through sudo rather than treated as absent, which would silently cost the search list. Also recasts `tls-sni: enforce` in the docs as name-alone — an allowed name still opens any L7-scoped IP, which is the gap `enforce-pinned` closes — and brings action.yml's input description in line with the README. Test coverage: - proxyResolvConf: 8 document-level cases in a new src/dns.test.ts (the first tests this file has had), including that `options` is not carried. - The repoint: 5 spawn tests asserting the buffer handed to tee, the sudo read fallback, and that file contents never reach a shell. - test-host-search-domains: supplies its own private suffix rather than depending on DHCP or the PSL filter, and greps the daemon log to prove the binary read the list — a rewrite that preserved the line but ran too late would otherwise still pass. The four v2-preview L7 jobs move to .github/workflows/test-l7.yml behind a shared ./.github/actions/assert-l7-verdict probe, which also adds the enforce vs enforce-pinned differential: an allowed name presented at an IP it never resolved to passes under `enforce` (reported name_not_at_ip) and drops under `enforce-pinned`. test.yml drops from 1195 to 853 lines. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018LnfPaPw59HmhxuqUBqTq7 Signed-off-by: Matthew DeVenny <matt@codecargo.com>
There was a problem hiding this comment.
🟡 Changes recommended
Resolver restoration can fail when the backup is unreadable by the runner user.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Updates CargoWall to v2.0.0-rc.7, preserves host DNS search domains, and expands L7 workflow coverage.
Changes:
- Updates binary pins and bundled output.
- Preserves
search/domainresolver directives. - Clarifies L7 behavior and adds workflow tests.
File summaries
| File | Summary |
|---|---|
src/start.ts |
Repoints and restores resolver configuration. |
src/start.spawn.test.ts |
Tests resolver repointing behavior. |
src/setup.ts |
Updates binary version and digests. |
src/dns.ts |
Builds the proxy resolver configuration. |
src/dns.test.ts |
Tests resolver transformations. |
README.md |
Documents DNS and L7 semantics. |
dist/main/index.js |
Updates bundled runtime. |
action.yml |
Clarifies TLS-SNI behavior. |
.github/workflows/test.yml |
Adds host search-domain coverage. |
.github/workflows/test-l7.yml |
Adds dedicated L7 workflow coverage. |
.github/actions/assert-l7-verdict/action.yml |
Adds reusable L7 verdict assertions. |
Review details
- Files reviewed: 10/11 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
restoreDns swallowed everything, so a `sudo cp` that failed was indistinguishable from having no backup — and it leaves the runner resolving through a proxy that is no longer running. Reachable rather than theoretical: sudo lockdown denies the action's own sudo. Splits the two: an absent backup still returns quietly (the common case when there was no resolv.conf to begin with), a failed copy warns. The existence probe is F_OK by default, which is what is wanted here — the copy runs as root, so whether this user can read the backup decides nothing — and that is now said in a comment rather than left to be inferred. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018LnfPaPw59HmhxuqUBqTq7 Signed-off-by: Matthew DeVenny <matt@codecargo.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.
Bumps the pinned binary to v2.0.0-rc.7 and fixes #84, which that release surfaced.
The pin
Digests taken from the release
checksums.txtand confirmed against a localsha256of the downloaded assets;gh attestation verifypassed on both arches at pin time. rc.7 is three DNS-proxy commits andcmd/flags.gois untouched, so there are no new flags to pass:_gateway,_outbound, the machine hostname, the localhost family) are answered rather than left to NXDOMAIN for the whole run.resolv.confsearch list becomes a strip-only suffix source._gatewayis usable as a rule value, L4-only.#84: the search-list feature never engaged under this action
src/start.tsreplaced/etc/resolv.confwith a barenameserver 127.0.0.1line, and cargowall was spawned 40 lines later. ItsseedHostSearchDomainsreads the search list from that same file when the DNS server starts — after the rewrite — so the strip-only list was empty in every job, while the feature worked for cargowall run any other way. The users it was built for (a self-hosted runner with a private search domain, c-ares clients like Nodedns.resolve*and grpcio that end their search on a refused multi-label attempt) got none of it.The replacement file is now built in TypeScript —
proxyResolvConf, beside the existing resolv.conf parser insrc/dns.ts— and fed tosudo teeon stdin, so DHCP-written text never reaches a shell.repointResolvConf/readResolvConfsit besiderestoreDnsas its inverse, with a successful read as the single existence signal: a file the runner user cannot read is read throughsudorather than treated as absent, which would silently cost the search list.Only
search/domainare carried.optionsis not — it is not what cargowall reads, and reinstating arbitrary resolver options (ndots,trust-ad) is a wider change than this.Ordering dependency worth knowing: restoring the list makes stub resolvers resume search-expanding single-label lookups, which is safe only because the pinned binary strips the host suffix. The action pins the binary so the two ship together; a caller supplying an older one via
binary-path/source-refis the exception.Docs
tls-sni: enforceis recast as name-alone — an allowed name still opens any L7-scoped IP, which is precisely the gapenforce-pinnedcloses. The README table said the pinned semantics for both rungs, andaction.yml's input description (the copy GitHub renders for a security knob) disagreed with it. Both now match the behaviour the new tests assert.Tests
proxyResolvConf— 8 document-level cases in a newsrc/dns.test.ts, the first tests that file has had. They assert the document ("nameserver 1.1.1.1\nsearch corp.lan\noptions ndots:5\n"→"nameserver 127.0.0.1\nsearch corp.lan\n"), not a command string.tee, covering the sudo-read fallback and that asearch $(id).lanline reachesteeverbatim without touching a shell. The fallback test was mutation-checked: replacingreturn code === 0 ? contents : nullwithreturn nullfails it, and only it.test-host-search-domains— supplies its own private suffix rather than depending on what DHCP handed the runner or what survives cargowall's PSL filter, then greps the daemon log to prove the binary actually read the list. A rewrite that preserved the line but ran too late would otherwise still pass.L7 workflow extraction
The four v2-preview L7 jobs move to
.github/workflows/test-l7.ymlbehind a shared./.github/actions/assert-l7-verdictprobe (dial an allowed IP presenting another name; assert the recorded verdict, optionally itsl7_reason).test.ymldrops from 1195 to 853 lines.That extraction also adds the enforce vs enforce-pinned differential, which nothing covered before: one probe —
github.com's SNI presented toregistry.npmjs.org's IP — passes underenforceand is recordedname_not_at_ip, and drops underenforce-pinned. Asserting the reason is what proves the per-IP binding did the work rather than a plain name mismatch. Both jobs assert on the audit log rather than curl's exit code, so neither depends on how an edge responds to an unexpected SNI.Note:
test-l7.ymlis a new workflow file, so it will not be a required check until someone adds it to branch protection.