Skip to content

Harden docker-sbx install: remove curl|sudo sh root install - #62991

Closed
pelikhan with Copilot wants to merge 6 commits into
mainfrom
copilot/setup-security-fix
Closed

pelikhan with Copilot wants to merge 6 commits into
mainfrom
copilot/setup-security-fix

Conversation

Copilot AI commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

sudo_docker_sbx_install.sh piped an unversioned, unverified remote script (get.docker.com) directly into a root shell to configure the Docker apt repository — a classic supply-chain risk with no pinning or checksum verification, inconsistent with the hardened pattern already used by sibling installers.

Changes

  • Replaced curl -fsSL https://get.docker.com | sudo REPO_ONLY=1 sh with deterministic apt-repo configuration:
    • Fetch Docker's GPG signing key to a file (never piped into a shell)
    • Install the key to /etc/apt/keyrings/docker.asc
    • Write an explicit deb [signed-by=...] apt source line
    • Run apt-get update && apt-get install -y docker-sbx
  • Mirrors the existing pattern in install_gh_cli.sh (fetch-key-to-file + signed-by source line) and the pinned/verified download style in sudo_gvisor_install.sh.
  • Added a runner-guard:ignore RGS-012 annotation with justification, since the key is only downloaded to disk and used for apt signature verification, not executed.
  • Added a changeset documenting the hardening.
# before
curl -fsSL https://get.docker.com | sudo REPO_ONLY=1 sh

# after
curl -fsSL "https://download.docker.com/linux/${DISTRO_ID}/gpg" -o /tmp/docker.asc
sudo install -m 0644 /tmp/docker.asc "${KEYRING_PATH}"
echo "deb [arch=$(dpkg --print-architecture) signed-by=${KEYRING_PATH}] https://download.docker.com/linux/${DISTRO_ID} ${DISTRO_CODENAME} stable" \
  | sudo tee "${SOURCE_LIST}" > /dev/null
sudo apt-get update -qq

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix unpinned curl sudo install in sudo_docker_sbx_install.sh Harden docker-sbx install: remove curl|sudo sh root install Sep 23, 2026
Copilot AI requested a review from pelikhan September 23, 2026 17:01
@pelikhan
pelikhan marked this pull request as ready for review September 23, 2026 17:06
Copilot AI balanced review requested due to automatic review settings September 23, 2026 17:06

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

The downloaded repository key remains unauthenticated, and the security behavior lacks regression coverage.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Hardens Docker sbx installation by replacing remote root-shell execution with explicit apt repository configuration.

Changes:

  • Adds Docker’s signing key and signed apt source directly.
  • Documents the security hardening in a patch changeset.
File Description
actions/​setup/​sh/​sudo_docker_sbx_install.sh Replaces curl | sudo sh with apt repository setup.
.changeset/​harden-docker-sbx-install-apt-repo.md Records the installation hardening.

echo "Adding Docker apt repository for ${DISTRO_ID} ${DISTRO_CODENAME}..."
sudo install -m 0755 -d /etc/apt/keyrings
# runner-guard:ignore RGS-012 -- fetches Docker's GPG signing key to a file (never piped to a shell); the key is used only to verify the signed-by apt repository below.
curl -fsSL "https://download.docker.com/linux/${DISTRO_ID}/gpg" -o /tmp/docker.asc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented in 820506d: the downloaded key is verified with gpg --show-keys against the pinned Docker fingerprint before installation. The inline rotation instruction requires verifying any replacement against Docker's published signing-key documentation.

Comment on lines +27 to +28
# runner-guard:ignore RGS-012 -- fetches Docker's GPG signing key to a file (never piped to a shell); the key is used only to verify the signed-by apt repository below.
curl -fsSL "https://download.docker.com/linux/${DISTRO_ID}/gpg" -o /tmp/docker.asc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented in 820506d: TestDockerSbxShellScriptContent now requires the fingerprint verification, signed-by source, secure temporary file, and Docker download endpoint, while rejecting the legacy endpoint and pipe-to-shell form.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

✅ Ponytail Reviewer completed successfully!

Lean already. Ship.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • ab.chatgpt.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "ab.chatgpt.com"

See Network Configuration for more information.

Generated by Ponytail Reviewer for #62991

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • o205451.ingest.us.sentry.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "o205451.ingest.us.sentry.io"

See Network Configuration for more information.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

✅ Design Decision Gate 🏗️ completed the design decision gate check. See the comment below for the result and any generated ADR draft.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

✅ Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. PR #62991 only changed production files (.changeset/ and sudo_docker_sbx_install.sh). Test Quality Sentinel skipped.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • o205451.ingest.us.sentry.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "o205451.ingest.us.sentry.io"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

✅ PR Code Quality Reviewer completed the code quality review.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • o205451.ingest.us.sentry.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "o205451.ingest.us.sentry.io"

See Network Configuration for more information.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-09-23T17:42:11Z
review_event: REQUEST_CHANGES
top_themes:
  - unpinned Docker apt trust root
  - regression test does not enforce hardening
  - missing distro/codename validation for apt source generation
files_reviewed:
  - .changeset/harden-docker-sbx-install-apt-repo.md
  - actions/setup/sh/sudo_docker_sbx_install.sh
comment_count: 1

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • o205451.ingest.us.sentry.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "o205451.ingest.us.sentry.io"

See Network Configuration for more information.

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 31.3 AIC · ⌖ 7.23 AIC · ⊞ 20.3K · ◷
Comment /review to run again

@github-actions github-actions Bot 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.

Request changes

This hardening is still incomplete: the repository trust root is still accepted from the network without any identity check, and the regression tests still would not catch a slide back to the original curl | sudo sh behavior.

Blocking themes
  • Fetching the Docker signing key into a file is better than piping a script into root, but it is still a live, unpinned trust root. A compromised key endpoint or MITM still turns into a trusted apt repository.
  • The shell-script test still only looks for broad substrings like sudo and apt-get install, so it does not actually enforce the new security property this PR is claiming. That leaves the exact regression we care about invisible to CI.
  • This rewrite also assumes VERSION_CODENAME is present and published by Docker, with no explicit guard. When that assumption is false the job fails later as a generic apt-get update error instead of surfacing a supported-platform check.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • o205451.ingest.us.sentry.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "o205451.ingest.us.sentry.io"

See Network Configuration for more information.

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 31.3 AIC · ⌖ 7.23 AIC · ⊞ 20.3K
Comment /review to run again

sudo install -m 0644 /tmp/docker.asc "${KEYRING_PATH}"
rm -f /tmp/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=${KEYRING_PATH}] https://download.docker.com/linux/${DISTRO_ID} ${DISTRO_CODENAME} stable" \

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.

This hard-codes ${DISTRO_CODENAME} into the apt source without validating that Docker publishes that suite, so on runners or derivatives where the field is empty or unsupported this step now dies at apt-get update and takes every docker-sbx workflow down.

💡 Why this matters and how to harden it

The old get.docker.com path handled distro detection internally; this replacement moved that responsibility into our script but never added the guardrails. At minimum, fail fast with a clear error when VERSION_CODENAME is empty, and preferably verify that the Docker repo exposes dists/${DISTRO_ID}/dists/${DISTRO_CODENAME} before writing the source line.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented in 820506d: the installer now validates non-empty ID and VERSION_CODENAME values before writing the apt source, failing with a clear error when either is unavailable.

@github-actions github-actions Bot 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.

Skills-Based Review 🧠

Applied /diagnosing-bugs. Two other Copilot review comments already flag the key issues (unverified key fingerprint, weak test regression coverage) — I focused on a gap not yet raised.

📋 Key Themes & Highlights

Key Themes

  • Predictable /tmp path (line 28): /tmp/docker.asc is a fixed, guessable filename — a TOCTOU/symlink race on shared runners could poison the very trust root this PR hardens against. Suggested mktemp + trap cleanup.
  • Two existing Copilot comments already correctly call out (1) the missing pinned-fingerprint verification of the downloaded GPG key, and (2) the test suite (TestDockerSbxShellScriptContent) not asserting against the new signed-repo flow or regression to curl | sudo sh. These remain valid and should be addressed together with the /tmp fix.

Positive Highlights

  • ✅ Removes the unversioned curl | sudo sh root install — clear net security improvement over the previous pattern.
  • ✅ Consistent with the existing hardened pattern in install_gh_cli.sh (fetch-key-to-file + signed-by source line).
  • ✅ Includes a changeset and an explicit runner-guard:ignore justification.

@copilot please address the review comments above.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • o205451.ingest.us.sentry.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "o205451.ingest.us.sentry.io"

See Network Configuration for more information.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · 40.6 AIC · ⌖ 14.5 AIC · ⊞ 10.1K
Comment /matt to run again

echo "Adding Docker apt repository for ${DISTRO_ID} ${DISTRO_CODENAME}..."
sudo install -m 0755 -d /etc/apt/keyrings
# runner-guard:ignore RGS-012 -- fetches Docker's GPG signing key to a file (never piped to a shell); the key is used only to verify the signed-by apt repository below.
curl -fsSL "https://download.docker.com/linux/${DISTRO_ID}/gpg" -o /tmp/docker.asc

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.

[/diagnosing-bugs] /tmp/docker.asc is a predictable, fixed filename in a world-writable shared directory — on a multi-tenant or reused runner this is a classic TOCTOU/symlink race: another process (or a prior failed run) could pre-create/replace that path before install -m 0644 reads it, silently poisoning the trust root this script is trying to harden.

💡 Suggested fix

Use mktemp for the scratch file instead of a fixed /tmp path:

KEY_TMP="$(mktemp)"
trap 'rm -f "$KEY_TMP"' EXIT
curl -fsSL "(download.docker.com/redacted) -o "$KEY_TMP"
sudo install -m 0644 "$KEY_TMP" "${KEYRING_PATH}"

This closes the same class of predictable-path risk the PR is otherwise fixing (unverified root-trust material), and is consistent with treating /tmp as untrusted on shared runners.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented in 820506d: the downloaded key now uses mktemp and an EXIT cleanup trap, eliminating the predictable shared /tmp/docker.asc path.

@github-actions github-actions Bot 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.

Impeccable review — harden + audit

This PR's core goal (replacing curl | sudo sh with a deterministic apt repo setup) is a solid direction, but the new implementation has issues that undermine the hardening intent.

Blocking

  • Predictable /tmp intermediate file (sudo_docker_sbx_install.sh:28-29): the GPG key is fetched unprivileged to a fixed /tmp/docker.asc path and then copied by sudo install into the trust-root keyring. A pre-existing symlink at that path would let an unprivileged local actor redirect a privileged write. install_gh_cli.sh avoids this by piping the download straight into sudo dd of=... with no intermediate world-writable file — recommend mirroring that, or using mktemp for a private temp path.
  • Key is unauthenticated before becoming the trust root (existing Copilot comment, still open): signed-by scopes trust to this key but nothing here verifies the downloaded key's identity/fingerprint, so a compromised download.docker.com response (or MITM without TLS pinning beyond ambient CA trust) becomes silently trusted. Consider verifying against Docker's published fingerprint before installing it as a keyring.

Non-blocking

  • Test coverage gap (existing Copilot comment, still open): TestDockerSbxShellScriptContent for sudo_docker_sbx_install.sh only checks for sudo, apt-get install, docker-sbx, sbx version, /dev/kvm — none of which distinguish the new deterministic-apt-repo approach from the old curl | sudo sh implementation. Add assertions (e.g. for signed-by=, KEYRING_PATH, absence of get.docker.com) so a regression back to piping-into-root-shell would fail the test.

Good use of the existing install_gh_cli.sh pattern as a reference and the changeset docs the rationale well.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • o205451.ingest.us.sentry.io

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "o205451.ingest.us.sentry.io"

See Network Configuration for more information.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · copilot · sonnet50 · 38.8 AIC · ⌖ 13.3 AIC · ⊞ 8.1K

echo "Adding Docker apt repository for ${DISTRO_ID} ${DISTRO_CODENAME}..."
sudo install -m 0755 -d /etc/apt/keyrings
# runner-guard:ignore RGS-012 -- fetches Docker's GPG signing key to a file (never piped to a shell); the key is used only to verify the signed-by apt repository below.
curl -fsSL "https://download.docker.com/linux/${DISTRO_ID}/gpg" -o /tmp/docker.asc

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.

Symlink/TOCTOU risk with predictable /tmp/docker.asc path. This introduces an unprivileged curl write to a fixed, world-writable /tmp path, followed by a privileged sudo install copy into the apt trust-root keyring (/etc/apt/keyrings/docker.asc). A local attacker/process could pre-create /tmp/docker.asc as a symlink before this step runs, and the subsequent sudo install would then copy attacker-controlled content into the apt-trusted keyring.

The sibling install_gh_cli.sh avoids this class of bug entirely by piping straight into the privileged write (curl -fsSL ... | sudo dd of="${KEYRING_PATH}"), never touching an intermediate world-writable file. Recommend the same pattern here, or at minimum use mktemp for a private, non-predictable temp file instead of the fixed /tmp/docker.asc path.

@copilot please address this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented in 820506d: the installer now uses a private mktemp file and cleans it on exit, preventing a predictable-path symlink or TOCTOU attack.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

PR-finisher pass completed. The in-scope findings are addressed in 820506d, validation passed, and replies were posted to every open review thread. CI on the new head still requires a maintainer-triggered run.

Comment on lines +39 to +44
KEY_FINGERPRINT="$(gpg --show-keys --with-colons "${KEY_TMP}" | awk -F: '$1 == "fpr" { print $10; exit }')"
if [[ "${KEY_FINGERPRINT}" != "${DOCKER_GPG_FINGERPRINT}" ]]; then
echo "Downloaded Docker GPG key fingerprint does not match the expected fingerprint." >&2
exit 1
fi
sudo install -m 0644 "${KEY_TMP}" "${KEYRING_PATH}"
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

PR-finisher pass completed in 3c476df. The installer now rejects empty keyrings and validates every primary Docker key fingerprint, with regression coverage; local formatting, lint, unit/integration tests, shellcheck, recompilation, secret scan, and final security validation passed. CI and mergeability are pending on the new head.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please move this PR forward.

Open review feedback remains from: github-advanced-security and prior copilot review passes.

Please refresh the branch, verify all review findings are addressed on the latest head, and run the pr-finisher skill before handing back for maintainer review.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "github.com"

See Network Configuration for more information.

Generated by 👨‍🍳 PR Sous Chef · pi · gpt54 · 29.1 AIC · ⌖ 8.59 AIC · ⊞ 9.6K · ◷
Comment /souschef to run again

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Open review feedback remains from copilot review passes and github-advanced-security. Please refresh the branch if needed, make sure each open review concern is addressed on the current head, and run the pr-finisher skill before handing back for maintainer review.

Current blockers still called out in review:

  • verify the Docker repository signing key fingerprint before trusting the downloaded key
  • keep the regression coverage strong enough to catch any slide back toward curl | sudo sh
  • confirm the installer’s supported-platform/codename validation is explicit and complete
  • if the remaining GitHub Advanced Security finding has already been addressed on the latest head, reply in-thread with the exact fix and validation evidence

Please summarize the focused validation you ran after the next push.

Generated by 👨‍🍳 PR Sous Chef · pi · gpt54 · 18.6 AIC · ⌖ 8.68 AIC · ⊞ 9.6K · ◷
Comment /souschef to run again

@pelikhan pelikhan closed this Sep 23, 2026
Copilot stopped work on behalf of gh-aw-bot due to an error September 23, 2026 19:49
Copilot AI requested a review from gh-aw-bot September 23, 2026 19:49
@github-actions
github-actions Bot deleted the copilot/setup-security-fix branch October 1, 2026 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[setup-security] Unpinned curl | sudo sh root install in sudo_docker_sbx_install.sh (supply-chain risk)

5 participants