ci(security): SHA-pin third-party actions, move dev host to secrets (#823) - #826
Merged
Conversation
deploy-dev.yml referenced tailscale/github-action@v2 and appleboy/ssh-action@v1 — both unpinned third-party actions handling TAILSCALE_AUTH_KEY (tailnet join) and DEV_SSH_KEY (sudo SSH on dev host). A single supply-chain hijack (account takeover, malicious forced retag) exfils both credentials on the next push to dev. SHA-pinned to mitigate. Same workflow hardcoded the dev Tailscale CGNAT IP and SSH username in a PUBLIC repo, leaking the dev host's tailnet topology. Moved both to GitHub repo secrets (DEV_HOST, DEV_USER) — must be set before merge or the next deploy fails fast. CLAUDE.md forbids internal addresses in this public tree. While here, SHA-pinned the other two workflows with third-party actions (pypa/gh-action-pypi-publish, google-github-actions/*) for defense in depth. First-party actions/* keep their major-tag pins per GitHub's documented recommendation (GitHub itself signs and re-tags those). After-state: zero unpinned third-party uses: lines across .github/workflows/.
This was referenced May 13, 2026
Contributor
Author
|
Deploy prerequisite — read before merging This PR moves the dev host/user out of the workflow file and into repo secrets. Before merging, an admin must set the following in Settings → Secrets and variables → Actions:
If the secrets are not configured before the next push to |
vybe
approved these changes
May 13, 2026
vybe
left a comment
Contributor
There was a problem hiding this comment.
SHA pins are correct, first-party actions/* correctly left on major tags, dev host topology properly moved to secrets. Confirm DEV_HOST and DEV_USER are set in repo secrets before the next deploy-dev run.
3 tasks
4 tasks
vybe
pushed a commit
that referenced
this pull request
May 26, 2026
* fix(ci): stash local changes before git pull on dev deploy Every deploy to dev since 2026-05-13 (#826) failed. Sequence of failures: 1. PR #826 moved DEV_HOST/DEV_USER hardcoded refs to repo secrets. Secrets weren't set → 7 weeks of "missing server host" failures (resolved 2026-05-26 — secrets configured). 2. Once secrets worked, SSH connection succeeded but `git pull` aborted at "Your local changes to the following files would be overwritten by merge: docker-compose.prod.yml". The dev VM has a persistent checkout that accumulated hand-applied tweaks (or leftover state from a prior failed deploy). Root cause is the pull-style deploy model itself: dev VM owns a long-lived source checkout that the SSH script `git pull`s on every deploy. Build happens on the VM, not on CI. Local edits to tracked files block the pull. This patch: - Stashes dirty changes under a deploy-run-keyed message before pulling, so the deploy proceeds without losing the hand-applied edits. They survive in `git stash list` + the reflog (~90 days) on the VM. - Switches `git pull` to `--ff-only` so a divergent history fails fast instead of producing a silent merge commit. - Comments the long-term fix (move build to CI + image registry => stateless dev VM; tracked as a follow-up — file an issue). After merge, the next push to dev triggers a deploy that: - Stashes the existing docker-compose.prod.yml modification under `auto-stash-deploy-<run_id>` - Pulls cleanly (fast-forward) - Resumes normal build/restart The hand-applied change is recoverable with `git stash list` + `git stash show` + `git stash apply` on the VM. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * fix(ci): use workflow-syntax run_id so stash name actually has the run id Review feedback on the previous commit: `$GITHUB_RUN_ID` lives on the GH Actions runner, not on the dev VM where the SSH script runs. `${GITHUB_RUN_ID:-manual}` silently fell back to "manual" every time, defeating the deploy-run-keyed audit trail intent. `${{ github.run_id }}` is interpolated at YAML render time, so the literal run id is baked into the script before drone-ssh delivers it to the remote. Added an inline comment explaining the trap so the next person to touch the script doesn't re-introduce it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.
Fixes #823
Summary
deploy-dev.ymlreferencedtailscale/github-action@v2andappleboy/ssh-action@v1— both unpinned third-party actions handlingTAILSCALE_AUTH_KEY(tailnet join) andDEV_SSH_KEY(sudo SSH on dev host). A single supply-chain hijack (account takeover, malicious forced retag) exfils both credentials on the next push todev. SHA-pinned to mitigate.Same workflow hardcoded the dev Tailscale CGNAT IP and SSH username in a PUBLIC repo, leaking the dev host's tailnet topology. Moved both to GitHub repo secrets (
DEV_HOST,DEV_USER) — must be set in repo secrets before merge or the next deploy fails fast. CLAUDE.md forbids internal addresses in this public tree.While here, SHA-pinned the other two workflows with third-party actions (
pypa/gh-action-pypi-publish,google-github-actions/*) for defense in depth. First-partyactions/*keep their major-tag pins per GitHub's documented recommendation (GitHub itself signs and re-tags those).After-state: zero unpinned third-party
uses:lines across.github/workflows/.What changed
.github/workflows/deploy-dev.yml: SHA-pin tailscale + ssh-action; moveDEV_HOST/DEV_USERto secrets.github/workflows/publish-cli.yml: SHA-pin pypa/gh-action-pypi-publish.github/workflows/sync-docs-to-vertex.yml: SHA-pin google-github-actions/*Required before merge (admin)
Two new repo secrets must be set:
DEV_HOST— the dev tailnet IPDEV_USER— the SSH usernameWithout these, the next
deploy-devrun fails fast (which is the safer failure mode than silently exposing the values in workflow logs).Test plan
v2/v1tag pointers (no version drift).github/workflows/foruses: [^a-z]patterns (no remaining unpinned third-party)Out of scope
This PR was previously bundled into #798 (circuit-breaker fix) — split out for isolated security review.
Companion PR #819 adds Dependabot + CODEOWNERS to keep these SHAs current and gate future changes to
.github/.🤖 Generated with Claude Code