From beb6b1df5436ac8c60427eea7e20d47c361c4a8d Mon Sep 17 00:00:00 2001 From: wecbaiyk-blip Date: Sat, 9 May 2026 03:27:40 +0300 Subject: [PATCH] security: reject symlinks in PR-supplied Firebase preview artifact `upload-artifacts-to-firebase` extracts a PR-supplied tarball (`unsafe-artifact.zip` -> `deploy-artifact.tar.gz`) into the Firebase public directory and then deploys it to a Firebase Hosting preview channel. The artifact is explicitly annotated as `RISK` in this action's comments and the README points at GitHub Security Lab's guidance on preventing pwn requests. Currently `tar -xvzf` materialises any symlinks that the PR author chose to put in their artifact onto the runner's `firebase-public-dir`. Downstream tooling that walks `firebase-public-dir` to enumerate the files to upload (e.g. `firebase-tools` `listFiles`) calls `fs.readFile*` on each entry, which follows symlinks at the OS layer. A tar entry like `public/leak -> /proc/self/environ` or `public/leak -> ~/.config/gcloud/application_default_credentials.json` would therefore have its target's bytes uploaded to the publicly-readable preview CDN. Defense in depth: after extraction, fail the deploy if the public directory contains any symlinks. There is no legitimate reason for a static-hosting artifact to contain symlinks, so refusing them outright is safer than trying to enumerate safe symlink targets. The check uses `find -type l` which detects both symlink-to-file and symlink-to-directory entries regardless of whether their target is reachable. `set -euo pipefail` is added at the top of the step so a shell error during extraction does not silently fall through to the deploy step. This patch complements upstream `firebase-tools` hardening (separate PRs against `firebase/firebase-tools`) and stays defensive even if those land later or are partially reverted. --- .../upload-artifacts-to-firebase/action.yml | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/github-actions/previews/upload-artifacts-to-firebase/action.yml b/github-actions/previews/upload-artifacts-to-firebase/action.yml index 229f90dfd..84447c5ad 100644 --- a/github-actions/previews/upload-artifacts-to-firebase/action.yml +++ b/github-actions/previews/upload-artifacts-to-firebase/action.yml @@ -66,13 +66,34 @@ runs: - name: Extracting workflow artifact into Firebase public directory. shell: bash run: | + set -euo pipefail + extractDir="$RUNNER_TEMP/artifact-unpack" + publicDir='${{inputs.firebase-public-dir}}' - mkdir -p '${{inputs.firebase-public-dir}}' + mkdir -p "$publicDir" mkdir -p "$extractDir" unzip unsafe-artifact.zip -d "$extractDir" - tar -xvzf "$extractDir/deploy-artifact.tar.gz" -C '${{inputs.firebase-public-dir}}' + tar -xvzf "$extractDir/deploy-artifact.tar.gz" -C "$publicDir" + + # Defense-in-depth: fail the deploy if the extracted artifact contains + # symlinks. The artifact is attacker-influenced (PR-supplied build + # output); a symlink such as `public/leak -> /proc/self/environ` or + # `public/leak -> ~/.config/gcloud/application_default_credentials.json` + # would otherwise be followed by downstream tooling that walks + # `firebase-public-dir` and reads each entry, ending up on the + # public Firebase Hosting CDN. There is no legitimate reason for a + # `public` artifact destined for static hosting to contain + # symlinks, so refusing them outright is the safest default. + # See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/. + symlinks=$(find "$publicDir" -type l 2>/dev/null || true) + if [ -n "$symlinks" ]; then + echo "::error title=Symlinks rejected in artifact::The deploy artifact contains symlinks, which are not permitted in a Hosting public directory for security reasons (a symlink can leak the contents of files outside the source tree onto the public Firebase Hosting CDN)." + echo "Symlinks found:" + echo "$symlinks" + exit 1 + fi - name: Extracting artifact metadata id: artifact-info