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