Skip to content

security: verify self-update checksum and stop leaking GitHub token via argv - #37

Merged
locus313 merged 1 commit into
mainfrom
fix/self-update-integrity-and-token-exposure
Aug 28, 2026
Merged

security: verify self-update checksum and stop leaking GitHub token via argv#37
locus313 merged 1 commit into
mainfrom
fix/self-update-integrity-and-token-exposure

Conversation

@locus313

Copy link
Copy Markdown
Owner

Summary

Fixes two findings from a security review:

  • HIGH: self_update replaced the running (root) script after only checking it was non-empty and started with #!/bin/bash — no integrity verification. Now verifies the downloaded script's sha256 against the digest GitHub's Releases API reports for that asset before replacing the current script. Missing/mismatched digest aborts the update.
  • MEDIUM: fetch_api_key passed GITHUB_TOKEN to curl as a literal argv arg, visible to other local users via /proc/<pid>/cmdline/ps. Now passed via a curl -K - stdin config instead.

Also bumped SCRIPT_VERSION to 0.1.10 (required by CI) and updated README security notes + stale line-number references in .github/copilot-instructions.md.

Testing

  • bash -n sync-ssh-keys.sh
  • bash test.sh — all 10 tests + shellcheck pass
  • Manually verified get_latest_release_url/download_latest_script against the real GitHub API: correct digest downloads and passes, tampered/mismatched digest is rejected
  • Manually verified fetch_api_key still authenticates correctly via the new curl config approach

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

…ia argv

- self_update now verifies the downloaded script against the sha256
  digest GitHub's Releases API reports for the asset, before replacing
  the running (root-executed) script. A missing or mismatched digest
  aborts the update.
- fetch_api_key passes GITHUB_TOKEN to curl via a stdin config file
  (curl -K -) instead of as a command-line argument, so it no longer
  appears in /proc/<pid>/cmdline or ps output for other local users.
- Bump SCRIPT_VERSION to 0.1.10.
- Update README security section and copilot-instructions.md line
  references to match.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@locus313
locus313 merged commit 2fae9b0 into main Aug 28, 2026
7 checks passed
@locus313
locus313 deleted the fix/self-update-integrity-and-token-exposure branch August 28, 2026 01:48
locus313 added a commit that referenced this pull request Aug 28, 2026
Two gaps found while reviewing CI coverage:

- Self-update's sha256 digest verification (added in #37) had zero test
  coverage — the existing check only grepped for function names, not
  behavior. test_self_update_round_trip runs a full --self-update against
  the real latest GitHub release on a scratch copy of the script, so a
  broken digest extraction or verification would fail CI.
- Integration tests checked authorized_keys/.ssh permission bits (700/600)
  but never verified chown ownership, even though get_user_gid/chown is
  its own code path that could silently leave files root-owned.
  test_file_ownership_correct checks both are owned by the target user.

Verified locally end-to-end: 11/11 functional tests and 5/5 integration
tests pass, including the new round-trip and ownership checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
locus313 added a commit that referenced this pull request Aug 28, 2026
* ci: extract test.yml assertions into standalone scripts

test.yml previously embedded every test case as inline bash in the
workflow YAML, so adding a test meant editing the workflow. Split the
assertions into .github/scripts/functional-tests.sh and
integration-tests.sh (using the same run_test-harness pattern as
test.sh); the workflow now only provisions CI fixtures (packages, test
users) and calls the scripts. New tests are added as a test_* function
+ run_test line in the appropriate script, no workflow changes needed.

Behavior is unchanged — same test users, same fixtures, same pass/fail
messages, verified by running both scripts locally end-to-end
(including the symlink-attack integration tests) against a live
GitHub API/keys endpoint.

Also fixes a `set -o pipefail` bug introduced during extraction: piping
the (intentionally failing) sync script into `grep -q` made pipefail
report the script's exit code instead of grep's match result, flipping
an assertion that checks for an expected error message.

Updates AGENTS.md, .github/copilot-instructions.md, and TESTING.md
references to point at the new script locations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: add self-update round-trip and file ownership coverage

Two gaps found while reviewing CI coverage:

- Self-update's sha256 digest verification (added in #37) had zero test
  coverage — the existing check only grepped for function names, not
  behavior. test_self_update_round_trip runs a full --self-update against
  the real latest GitHub release on a scratch copy of the script, so a
  broken digest extraction or verification would fail CI.
- Integration tests checked authorized_keys/.ssh permission bits (700/600)
  but never verified chown ownership, even though get_user_gid/chown is
  its own code path that could silently leave files root-owned.
  test_file_ownership_correct checks both are owned by the target user.

Verified locally end-to-end: 11/11 functional tests and 5/5 integration
tests pass, including the new round-trip and ownership checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant