[sec-check] fix(systemd): guard hive-snapshot.service against planted /tmp checkout (#5483) - #5499
Conversation
…ut (#5483) hive-snapshot.service executed /tmp/hive/dashboard/publish-snapshot.sh as User=dev with no ownership guard. /tmp is world-writable and cleared on reboot; the sticky bit does not prevent CREATING /tmp/hive/dashboard/ publish-snapshot.sh in the window before hive-deploy repopulates the checkout, so a local unprivileged user who won that race got code execution as dev on the next timer fire — and the real script reads the GitHub App token from /var/run/hive-metrics/gh-app-token.cache. Same class and same fix as #5435 / #5481: - ExecStartPre=/usr/local/bin/hive-checkout-guard.sh /tmp/hive/dashboard publish-snapshot.sh — the existing guard generalizes unmodified; its exit status is the assertion, so a violation stops ExecStart. - Hardening pass mirroring hive-discord.service: NoNewPrivileges, ProtectSystem=full, ProtectHome=read-only, PrivateDevices, RestrictSUIDSGID. PrivateTmp and ProtectSystem=strict deliberately absent (both would break execution out of the /tmp checkout) and pinned absent by the contract test. - src/deploy/test_hive_snapshot_unit_contract.sh executes the guard with this unit's argument shape (the entrypoint is the 755 script itself, so the writable-file check is the live one) and pins the unit's directives. The v2-ci.yml step wiring this test in (next to the #5435 step) is NOT in this PR: the App push token lacks workflows permission. A maintainer should add it; exact step text is in the PR body. Refs #5483 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: sec-check <sec-check@hive.kubestellar.io>
|
Changelog: this PR changes code but does not touch If it is user-visible — a feature, a fix an operator would notice, a This is a reminder, not a gate; it never blocks a merge. |
|
[APPROVALNOTIFIER] This PR is APPROVED Approval requirements bypassed by manually added approval. This pull-request has been approved by: The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Thank you for your contribution! Your PR has been merged. Check out what's new:
Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey |
Security Fix
Claims:
systemd/hive-snapshot.service, newsrc/deploy/test_hive_snapshot_unit_contract.sh— the #5483 / #5435-class systemd-unit-executes-from-/tmp cluster. Disjoint from hold PR #4032 (proxy auth injection:src/pkg/proxy/,src/pkg/github/,src/pkg/config/proxy_inject*).hive-snapshot.serviceexecuted/tmp/hive/dashboard/publish-snapshot.shasUser=devwith no ownership guard./tmpis world-writable and cleared on reboot; the sticky bit does not prevent creating/tmp/hive/dashboard/publish-snapshot.shin the window before hive-deploy repopulates the checkout. A local unprivileged user who won that race got code execution asdevon the nexthive-snapshot.timerfire — and the real script reads the GitHub App token from/var/run/hive-metrics/gh-app-token.cache, so planted code runs with a path to that credential.What this changes
Same class and same fix as #5435 / #5481:
ExecStartPre=/usr/local/bin/hive-checkout-guard.sh /tmp/hive/dashboard publish-snapshot.sh— the existing guard (already installed by hive-deploy since fix(security): stop hive-discord.service executing code planted under /tmp (#5435) #5481) generalizes unmodified; its exit status is the assertion, so a violation stops ExecStart.hive-discord.service:NoNewPrivileges,ProtectSystem=full,ProtectHome=read-only,PrivateDevices,RestrictSUIDSGID.PrivateTmpandProtectSystem=strictare deliberately absent (both would break execution out of the /tmp checkout) and are pinned absent by the contract test, per hive-snapshot.service executes publish-snapshot.sh from /tmp/hive with no ownership guard (same class as #5435) #5483's guidance.src/deploy/test_hive_snapshot_unit_contract.shexecutes the guard with this unit's argument shape — the entrypoint is the 755 script itself, so the writable-file check (not the exec bit) is the live one — and pins the unit's directives. 13/13 passing locally; the existing [sec-check] hive-discord.service executes node bot.js from world-writable-parent /tmp/hive/discord — local code-execution as service user #5435 discord contract test still passes 21/21.Not in this PR (App token lacks
workflowspermission)A maintainer should wire the test into
.github/workflows/v2-ci.ymlnext to the #5435 step:Refs #5483 (CI wiring above still pending, so not using a closing keyword)
Filed by sec-check agent (ACMM L4/L5 — hold-gated mode). Hold-gated: human review required.
— hive: agent=sec-check backend=copilot model=claude-fable-5