Skip to content

[sec-check] fix(systemd): guard hive-snapshot.service against planted /tmp checkout (#5483) - #5499

Merged
kubestellar-prow[bot] merged 1 commit into
v4from
sec-check/5483-snapshot-checkout-guard
Sep 1, 2026
Merged

[sec-check] fix(systemd): guard hive-snapshot.service against planted /tmp checkout (#5483)#5499
kubestellar-prow[bot] merged 1 commit into
v4from
sec-check/5483-snapshot-checkout-guard

Conversation

@kubestellar-hive

Copy link
Copy Markdown
Contributor

Security Fix

Claims: systemd/hive-snapshot.service, new src/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.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. A local unprivileged user who won that race got code execution as dev on the next hive-snapshot.timer fire — 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:

Not in this PR (App token lacks workflows permission)

A maintainer should wire the test into .github/workflows/v2-ci.yml next to the #5435 step:

      # #5483: hive-snapshot.service had the same exposure — ExecStart resolved
      # /tmp/hive/dashboard/publish-snapshot.sh out of a world-writable,
      # reboot-cleared parent, and the script reads the GitHub App token cache.
      - name: hive-snapshot.service does not execute planted code (#5483)
        working-directory: .
        run: bash src/deploy/test_hive_snapshot_unit_contract.sh

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

…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>
@kubestellar-hive kubestellar-hive Bot added the hold label Sep 1, 2026
@kubestellar-prow kubestellar-prow Bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Changelog: this PR changes code but does not touch CHANGELOG.md.

If it is user-visible — a feature, a fix an operator would notice, a
security change, a migration, a deprecation, or anything breaking —
please add a line under ## Unreleased. If it is a refactor, a
test-only change, or dependency churn, no entry is needed and you can
ignore this.

This is a reminder, not a gate; it never blocks a merge.

@kubestellar-prow kubestellar-prow Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Sep 1, 2026
@clubanderson clubanderson added approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. and removed hold labels Sep 1, 2026
@kubestellar-prow

Copy link
Copy Markdown
Contributor

[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

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubestellar-prow
kubestellar-prow Bot merged commit b9a21e4 into v4 Sep 1, 2026
52 checks passed
@kubestellar-prow
kubestellar-prow Bot deleted the sec-check/5483-snapshot-checkout-guard branch September 1, 2026 15:06
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Thank you for your contribution! Your PR has been merged.

Check out what's new:

Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. copilot dco-signoff: yes Indicates the PR's author has signed the DCO. lgtm Indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant