From b96733c1f54a45e151fa15dc19518e195a3a6a94 Mon Sep 17 00:00:00 2001 From: Harshita Gupta Date: Fri, 17 Apr 2026 01:19:32 -0700 Subject: [PATCH 1/3] workflows: swap softprops for gh CLI, add CloudFront reachability check, remove --acl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three follow-up corrections to PR #17: 1. Remove `--acl public-read` from `aws s3 cp`. The bucket has `disable_confusing_acls = true` (BucketOwnerEnforced), which disables ACLs entirely. `BlockPublicAcls` + `IgnorePublicAcls` provide additional coverage. The ACL flag is silently ignored. The IAM role (`S3_ACCESS_MODE.PUT`) also doesn't grant `PutObjectAcl`. Reads go via CloudFront OAC, not public-S3. 2. Replace `softprops/action-gh-release` with GitHub's first-party `gh` CLI. `gh release upload` is pre-installed on GitHub-hosted runners, removes a third-party (single-maintainer) supply-chain dependency, and behaves equivalently with `--clobber`. 3. Add a post-upload CloudFront reachability check (`curl -fI`). If the CloudFront path_patterns allowlist doesn't include the key's prefix, Mac Bazel builds will silently 403. Failing the workflow here surfaces the issue before consumers hit it. S3 path stays `node-gyp/*` (this PR no longer changes it — see codez PR #390222 which adds `node-gyp/*` to CloudFront's path_patterns in system_packages.tf). Action pinning: tag-pinned per codez convention (100% of codez workflows use tags, not SHAs). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-node-packages.yml | 42 +++++++++++++++++++---- stage_for_s3.bash | 5 +-- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-node-packages.yml b/.github/workflows/build-node-packages.yml index 8b3cd4a7087318..be6cca542eb397 100644 --- a/.github/workflows/build-node-packages.yml +++ b/.github/workflows/build-node-packages.yml @@ -73,13 +73,17 @@ jobs: tar --hard-dereference -cvzf packages_${{matrix.arch}}.tar.gz bcrypt@5.1.0 cld@2.9.1 unix-dgram@2.0.6 "@datadog+pprof@5.8.0" - name: Upload archive to release - uses: softprops/action-gh-release@v1 - with: - name: node-${{ env.NODE_VERSION }}-LATEST - tag_name: node-${{ env.NODE_VERSION }}-release - files: packages_${{matrix.arch}}.tar.gz + # Use `gh release upload` (first-party GitHub CLI, pre-installed on runners) + # instead of softprops/action-gh-release (one-maintainer third-party action). + # Behavior: --clobber overwrites an existing asset with the same name, matching + # softprops's default. The release must already exist (created by build-node.yml). env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload "node-${{ env.NODE_VERSION }}-release" \ + "packages_${{ matrix.arch }}.tar.gz" \ + --clobber \ + --repo "${{ github.repository }}" # S3 upload is restricted to the protected main branch only. The IAM role # (push_node_gyp_packages) trusts only refs/heads/main via OIDC. To upload @@ -94,12 +98,38 @@ jobs: - name: Upload packages to S3 if: github.ref == 'refs/heads/main' run: | + # Upload to s3://asana-oss-cache/node-gyp/... (CloudFront path_patterns entry + # added in codez PR #390222 — that must be merged + applied via Spacelift + # before this workflow can successfully publish fetchable objects). + # + # No --acl public-read: the bucket has BucketOwnerEnforced + # (disable_confusing_acls = true), which disables ACLs entirely. + # BlockPublicAcls + IgnorePublicAcls provide additional coverage. + # Reads come via CloudFront OAC. NODE_MAJOR=$(echo "${{ env.NODE_VERSION }}" | sed 's/^v//' | cut -d. -f1) SHA256=$(sha256sum "packages_${{ matrix.arch }}.tar.gz" | awk '{print $1}') SHORT_HASH=${SHA256:0:8} S3_KEY="node-gyp/packages_${{ matrix.bazel_arch }}_node${NODE_MAJOR}-${SHORT_HASH}.tar.gz" echo "Uploading packages_${{ matrix.arch }}.tar.gz to s3://asana-oss-cache/${S3_KEY}" - aws s3 cp "packages_${{ matrix.arch }}.tar.gz" "s3://asana-oss-cache/${S3_KEY}" --acl public-read + aws s3 cp "packages_${{ matrix.arch }}.tar.gz" "s3://asana-oss-cache/${S3_KEY}" + echo "S3_KEY=${S3_KEY}" >> "$GITHUB_ENV" + echo "SHA256=${SHA256}" >> "$GITHUB_ENV" + echo "NODE_MAJOR=${NODE_MAJOR}" >> "$GITHUB_ENV" + + - name: Verify upload is reachable via CloudFront + if: github.ref == 'refs/heads/main' + run: | + # Mac Bazel builds rewrite asana-oss-cache.s3.us-east-1.amazonaws.com/* + # to asana-oss-cache.asana.biz/* (CloudFront). If the S3 key prefix isn't + # allowlisted in CloudFront's path_patterns, Bazel fetches will 403. + # Fail fast here rather than after someone tries to build. + URL="https://asana-oss-cache.asana.biz/${S3_KEY}" + echo "Checking ${URL}" + curl -fsSI "${URL}" || { echo "CloudFront returned an error for ${URL}. Check path_patterns in system_packages.tf."; exit 1; } + + - name: Print tools_repositories.bzl stanza + if: github.ref == 'refs/heads/main' + run: | echo "" echo "=== Update tools_repositories.bzl in codez ===" echo " name = \"node_gyp_packages_${{ matrix.bazel_arch }}_node${NODE_MAJOR}\"," diff --git a/stage_for_s3.bash b/stage_for_s3.bash index 8d9cfdb7c216bc..22ee4c9c62f05c 100755 --- a/stage_for_s3.bash +++ b/stage_for_s3.bash @@ -16,14 +16,15 @@ gh release download -p "*.xz" echo "" echo "=== Native packages (node-gyp) ===" echo "These are uploaded to s3://asana-oss-cache/node-gyp/ by the build-node-packages.yml workflow" -echo "with content-hashed S3 keys. Each build produces an immutable artifact." +echo "(triggered via workflow_dispatch from main) with content-hashed S3 keys." +echo "Each build produces an immutable artifact." for pkg in packages_*.tar.gz; do if [ -f "$pkg" ]; then echo " $pkg: sha256=$(sha256sum "$pkg" | awk '{print $1}')" rm "$pkg" fi done -echo "No manual action needed for packages — they are already in S3." +echo "No manual action needed for packages if you've already dispatched build-node-packages.yml from main." echo "" curl "https://asana-oss-cache.s3.us-east-1.amazonaws.com/node-fibers/fibers-5.0.4.pc.tgz" --output fibers-5.0.4.tar.gz From 31f3d546aa06796d1f333ee97147912e29eef709 Mon Sep 17 00:00:00 2001 From: Harshita Gupta Date: Tue, 21 Apr 2026 15:58:46 -0700 Subject: [PATCH 2/3] Update .github/workflows/build-node-packages.yml Co-authored-by: Eli Skeggs <1348991+skeggse@users.noreply.github.com> --- .github/workflows/build-node-packages.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-node-packages.yml b/.github/workflows/build-node-packages.yml index be6cca542eb397..8b2f185cd6434b 100644 --- a/.github/workflows/build-node-packages.yml +++ b/.github/workflows/build-node-packages.yml @@ -123,9 +123,12 @@ jobs: # to asana-oss-cache.asana.biz/* (CloudFront). If the S3 key prefix isn't # allowlisted in CloudFront's path_patterns, Bazel fetches will 403. # Fail fast here rather than after someone tries to build. - URL="https://asana-oss-cache.asana.biz/${S3_KEY}" - echo "Checking ${URL}" - curl -fsSI "${URL}" || { echo "CloudFront returned an error for ${URL}. Check path_patterns in system_packages.tf."; exit 1; } + URL="https://asana-oss-cache.asana.biz/$S3_KEY" + echo "Checking $URL" + if ! curl -fsSI "$URL"; then + echo "CloudFront returned an error for $URL. Check path_patterns in system_packages.tf." + exit 1 + fi - name: Print tools_repositories.bzl stanza if: github.ref == 'refs/heads/main' From bfb9c59d3badcd65796637ce641548931299a6c0 Mon Sep 17 00:00:00 2001 From: Harshita Gupta Date: Tue, 21 Apr 2026 16:06:48 -0700 Subject: [PATCH 3/3] workflows: hoist matrix values to job-level env, drop expression substitution in run: blocks Eli's review flagged `${{ matrix.arch }}` in a run: block as an injectable pattern even though the matrix values are hardcoded and not truly exploitable. Apply the pattern consistently across the whole workflow: - Hoist PLATFORM, ARCH, BAZEL_ARCH, REPO to job-level env so each step can reference them as shell variables ($ARCH etc.) rather than GitHub Actions expressions (${{ matrix.arch }}). Job-level env evaluates matrix context since the job is instantiated per matrix combination, so this DRYs up the per-step env blocks. - Rewrite every `run:` block to reference the job-level env vars. No more `${{ ... }}` expressions inside shell scripts. - Secret references (GITHUB_TOKEN) remain step-scoped per least-privilege. - Minor cleanup: collapse three separate `echo ... >> $GITHUB_ENV` lines into a single `{ ...; } >> "$GITHUB_ENV"` block. Addresses Eli's inline comment on line 114 of the pre-hoist file. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-node-packages.yml | 51 +++++++++++++---------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build-node-packages.yml b/.github/workflows/build-node-packages.yml index 8b2f185cd6434b..1b01f2147b81e9 100644 --- a/.github/workflows/build-node-packages.yml +++ b/.github/workflows/build-node-packages.yml @@ -30,6 +30,10 @@ jobs: env: NODE_VERSION: v22.21.1 + PLATFORM: ${{ matrix.platform }} + ARCH: ${{ matrix.arch }} + BAZEL_ARCH: ${{ matrix.bazel_arch }} + REPO: ${{ github.repository }} steps: - name: Checkout repository @@ -37,17 +41,18 @@ jobs: - name: Debug Matrix Values run: | - echo "Matrix platform: ${{ matrix.platform }}" - echo "Matrix arch: ${{ matrix.arch }}" + echo "Matrix platform: $PLATFORM" + echo "Matrix arch: $ARCH" - name: Download Node archive - run: | - gh release download node-${{ env.NODE_VERSION }}-release \ - --repo asana/node \ - --pattern "node-${{ env.NODE_VERSION }}-${{ matrix.platform }}-${{ matrix.arch }}-LATEST.tar.xz" - mv node-${{ env.NODE_VERSION }}-${{ matrix.platform }}-${{ matrix.arch }}-LATEST.tar.xz node.tar.xz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ASSET="node-${NODE_VERSION}-${PLATFORM}-${ARCH}-LATEST.tar.xz" + gh release download "node-${NODE_VERSION}-release" \ + --repo asana/node \ + --pattern "$ASSET" + mv "$ASSET" node.tar.xz - name: Execute the Dockerfile run: | @@ -70,7 +75,7 @@ jobs: mv node_modules/cld ./cld@2.9.1/node_modules/ mv node_modules/unix-dgram ./unix-dgram@2.0.6/node_modules/ mv "node_modules/@datadog/pprof" "./@datadog+pprof@5.8.0/node_modules/@datadog/" - tar --hard-dereference -cvzf packages_${{matrix.arch}}.tar.gz bcrypt@5.1.0 cld@2.9.1 unix-dgram@2.0.6 "@datadog+pprof@5.8.0" + tar --hard-dereference -cvzf "packages_${ARCH}.tar.gz" bcrypt@5.1.0 cld@2.9.1 unix-dgram@2.0.6 "@datadog+pprof@5.8.0" - name: Upload archive to release # Use `gh release upload` (first-party GitHub CLI, pre-installed on runners) @@ -80,10 +85,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release upload "node-${{ env.NODE_VERSION }}-release" \ - "packages_${{ matrix.arch }}.tar.gz" \ + gh release upload "node-${NODE_VERSION}-release" \ + "packages_${ARCH}.tar.gz" \ --clobber \ - --repo "${{ github.repository }}" + --repo "$REPO" # S3 upload is restricted to the protected main branch only. The IAM role # (push_node_gyp_packages) trusts only refs/heads/main via OIDC. To upload @@ -106,15 +111,17 @@ jobs: # (disable_confusing_acls = true), which disables ACLs entirely. # BlockPublicAcls + IgnorePublicAcls provide additional coverage. # Reads come via CloudFront OAC. - NODE_MAJOR=$(echo "${{ env.NODE_VERSION }}" | sed 's/^v//' | cut -d. -f1) - SHA256=$(sha256sum "packages_${{ matrix.arch }}.tar.gz" | awk '{print $1}') + NODE_MAJOR=$(echo "$NODE_VERSION" | sed 's/^v//' | cut -d. -f1) + SHA256=$(sha256sum "packages_${ARCH}.tar.gz" | awk '{print $1}') SHORT_HASH=${SHA256:0:8} - S3_KEY="node-gyp/packages_${{ matrix.bazel_arch }}_node${NODE_MAJOR}-${SHORT_HASH}.tar.gz" - echo "Uploading packages_${{ matrix.arch }}.tar.gz to s3://asana-oss-cache/${S3_KEY}" - aws s3 cp "packages_${{ matrix.arch }}.tar.gz" "s3://asana-oss-cache/${S3_KEY}" - echo "S3_KEY=${S3_KEY}" >> "$GITHUB_ENV" - echo "SHA256=${SHA256}" >> "$GITHUB_ENV" - echo "NODE_MAJOR=${NODE_MAJOR}" >> "$GITHUB_ENV" + S3_KEY="node-gyp/packages_${BAZEL_ARCH}_node${NODE_MAJOR}-${SHORT_HASH}.tar.gz" + echo "Uploading packages_${ARCH}.tar.gz to s3://asana-oss-cache/$S3_KEY" + aws s3 cp "packages_${ARCH}.tar.gz" "s3://asana-oss-cache/$S3_KEY" + { + echo "S3_KEY=$S3_KEY" + echo "SHA256=$SHA256" + echo "NODE_MAJOR=$NODE_MAJOR" + } >> "$GITHUB_ENV" - name: Verify upload is reachable via CloudFront if: github.ref == 'refs/heads/main' @@ -135,6 +142,6 @@ jobs: run: | echo "" echo "=== Update tools_repositories.bzl in codez ===" - echo " name = \"node_gyp_packages_${{ matrix.bazel_arch }}_node${NODE_MAJOR}\"," - echo " urls = [\"https://asana-oss-cache.s3.us-east-1.amazonaws.com/${S3_KEY}\"]," - echo " sha256 = \"${SHA256}\"," + echo " name = \"node_gyp_packages_${BAZEL_ARCH}_node${NODE_MAJOR}\"," + echo " urls = [\"https://asana-oss-cache.s3.us-east-1.amazonaws.com/$S3_KEY\"]," + echo " sha256 = \"$SHA256\","