Skip to content

ci(security): SHA-pin third-party actions, move dev host to secrets (#823) - #826

Merged
vybe merged 1 commit into
devfrom
feature/823-sha-pin-third-party-actions
May 13, 2026
Merged

ci(security): SHA-pin third-party actions, move dev host to secrets (#823)#826
vybe merged 1 commit into
devfrom
feature/823-sha-pin-third-party-actions

Conversation

@AndriiPasternak31

Copy link
Copy Markdown
Contributor

Fixes #823

Summary

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 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-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/.

What changed

  • .github/workflows/deploy-dev.yml: SHA-pin tailscale + ssh-action; move DEV_HOST / DEV_USER to 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 IP
  • DEV_USER — the SSH username

Without these, the next deploy-dev run fails fast (which is the safer failure mode than silently exposing the values in workflow logs).

Test plan

  • All SHAs verified against current v2 / v1 tag pointers (no version drift)
  • grep'd .github/workflows/ for uses: [^a-z] patterns (no remaining unpinned third-party)
  • Validated YAML structure — workflows parse cleanly

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

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/.
@AndriiPasternak31

Copy link
Copy Markdown
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:

  • DEV_HOST — dev deploy target hostname
  • DEV_USER — SSH user

If the secrets are not configured before the next push to dev, the deploy job will fail on ${{ secrets.DEV_HOST }} resolving to an empty string. Fail-fast is the correct mode here, but worth a heads-up so the merger doesn't get surprised by a red deploy.

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

@vybe
vybe merged commit 9b8e0b0 into dev May 13, 2026
19 checks passed
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)
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.

2 participants