Skip to content

Update cargowall to v2.0.0-rc.7 and carry the host search list (#84) - #85

Merged
matthewdevenny merged 2 commits into
mainfrom
matt/update-cargowall-action
Sep 17, 2026
Merged

matthewdevenny merged 2 commits into
mainfrom
matt/update-cargowall-action

Conversation

@matthewdevenny

@matthewdevenny matthewdevenny commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Bumps the pinned binary to v2.0.0-rc.7 and fixes #84, which that release surfaced.

The pin

amd64: cc4eadb28c154bb01ac611f8b5eedf6e83feb9e3a152ba421f97e6c228b99e84
arm64: 2b3dd7fdaf6df7e297c06f29c52495fdf23543c20f203bcc2d45509cc9f0c333

Digests taken from the release checksums.txt and confirmed against a local sha256 of the downloaded assets; gh attestation verify passed on both arches at pin time. rc.7 is three DNS-proxy commits and cmd/flags.go is untouched, so there are no new flags to pass:

  • #128 — systemd-resolved synthetic names (_gateway, _outbound, the machine hostname, the localhost family) are answered rather than left to NXDOMAIN for the whole run.
  • #130 — the host's own resolv.conf search list becomes a strip-only suffix source.
  • #131 — _gateway is usable as a rule value, L4-only.

#84: the search-list feature never engaged under this action

src/start.ts replaced /etc/resolv.conf with a bare nameserver 127.0.0.1 line, and cargowall was spawned 40 lines later. Its seedHostSearchDomains reads 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 Node dns.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 in src/dns.ts — 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.

Only search/domain are carried. options is 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-ref is the exception.

Docs

tls-sni: enforce is recast as name-alone — an allowed name still opens any L7-scoped IP, which is precisely the gap enforce-pinned closes. The README table said the pinned semantics for both rungs, and action.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 new src/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.
  • The repoint — 5 spawn tests on the buffer handed to tee, covering the sudo-read fallback and that a search $(id).lan line reaches tee verbatim without touching a shell. The fallback test was mutation-checked: replacing return code === 0 ? contents : null with return null fails 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.yml behind a shared ./.github/actions/assert-l7-verdict probe (dial an allowed IP presenting another name; assert the recorded verdict, optionally its l7_reason). test.yml drops 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 to registry.npmjs.org's IP — passes under enforce and is recorded name_not_at_ip, and drops under enforce-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.yml is a new workflow file, so it will not be a required check until someone adds it to branch protection.

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>
Copilot AI lite review requested due to automatic review settings September 17, 2026 14:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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/domain resolver 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.

Comment thread src/start.ts Outdated
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>
@matthewdevenny
matthewdevenny merged commit 7fec72e into main Sep 17, 2026
30 checks passed
@matthewdevenny
matthewdevenny deleted the matt/update-cargowall-action branch September 17, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Action wipes the runner's resolv.conf search list, so cargowall's host search-domain stripping is inert

2 participants