Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,5 @@ updates:
- "react-dom"
- "yjs"
- "y-prosemirror"
cooldown:
default-days: 7
54 changes: 31 additions & 23 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
pull_request:
types: [opened, synchronize, reopened, edited]

permissions:
contents: read

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN }}
Expand All @@ -18,23 +21,24 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 100
persist-credentials: false

- name: Install pnpm
uses: pnpm/action-setup@v5
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5

- uses: nrwl/nx-set-shas@v5
- uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5

- uses: actions/setup-node@v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
node-version-file: ".nvmrc"

- name: Cache NX
uses: actions/cache@v5
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: .nx/cache
key: nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }}
Expand All @@ -59,7 +63,7 @@ jobs:
run: NEXTJS_TEST_MODE=build npx vitest run tests/src/unit/nextjs/serverUtil.test.ts

- name: Upload webpack stats artifact (editor)
uses: relative-ci/agent-upload-artifact-action@v2
uses: relative-ci/agent-upload-artifact-action@a2b5741b4f7e6a989c84ec1a3059696b23c152e5 # v2
with:
webpackStatsFile: ./playground/dist/webpack-stats.json
artifactName: relative-ci-artifacts-editor
Expand All @@ -73,23 +77,24 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 100
persist-credentials: false

- name: Install pnpm
uses: pnpm/action-setup@v5
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5

- uses: nrwl/nx-set-shas@v5
- uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5

- uses: actions/setup-node@v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
node-version-file: ".nvmrc"

- name: Cache NX
uses: actions/cache@v5
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: .nx/cache
key: nx-playwright-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }}
Expand All @@ -105,7 +110,7 @@ jobs:
run: pnpm run build

- name: Upload build artifacts
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-build
path: |
Expand All @@ -127,21 +132,22 @@ jobs:
shardIndex: [1, 2]
shardTotal: [2]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 100
persist-credentials: false

- name: Install pnpm
uses: pnpm/action-setup@v5
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5

- uses: actions/setup-node@v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
node-version-file: ".nvmrc"

- name: Download build artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: playwright-build

Expand All @@ -153,15 +159,15 @@ jobs:
HOME=/root PLAYWRIGHT_CONFIG="--project ${{ matrix.browser }} --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}" pnpm run e2e

- name: Upload blob report
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: ${{ !cancelled() }}
with:
name: blob-report-${{ matrix.browser }}-${{ matrix.shardIndex }}
path: tests/blob-report/
retention-days: 1

- name: Upload HTML report
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ matrix.browser }}-${{ matrix.shardIndex }}
Expand All @@ -174,12 +180,14 @@ jobs:
needs: playwright
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- name: Install pnpm
uses: pnpm/action-setup@v5
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5

- uses: actions/setup-node@v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
Expand All @@ -189,7 +197,7 @@ jobs:
run: pnpm install

- name: Download blob reports
uses: actions/download-artifact@v8
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: tests/all-blob-reports
pattern: blob-report-*
Expand All @@ -200,7 +208,7 @@ jobs:
working-directory: tests

- name: Upload merged HTML report
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-report-merged
path: tests/playwright-report/
Expand Down
35 changes: 24 additions & 11 deletions .github/workflows/fresh-install-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ on:
- cron: "0 2 * * *" # Daily at 02:00 UTC
workflow_dispatch: # Allow manual runs

permissions:
contents: read

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
pnpm_config_store_dir: ./node_modules/.pnpm-store
Expand All @@ -27,14 +30,16 @@ jobs:

steps:
- id: checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- id: install_pnpm
name: Install pnpm
uses: pnpm/action-setup@v5
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5

- id: setup_node
uses: actions/setup-node@v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: ".nvmrc"
# Intentionally no pnpm cache — we want fresh prod dep resolution
Expand Down Expand Up @@ -99,28 +104,36 @@ jobs:
RUN_NUMBER: ${{ github.run_number }}
RUN_ATTEMPT: ${{ github.run_attempt }}
BRANCH: ${{ github.ref_name }}
STEPS_CHECKOUT_OUTCOME: ${{ steps.checkout.outcome }}
STEPS_INSTALL_PNPM_OUTCOME: ${{ steps.install_pnpm.outcome }}
STEPS_SETUP_NODE_OUTCOME: ${{ steps.setup_node.outcome }}
STEPS_INSTALL_DEPENDENCIES_OUTCOME: ${{ steps.install_dependencies.outcome }}
STEPS_UPDATE_PROD_DEPS_OUTCOME: ${{ steps.update_prod_deps.outcome }}
STEPS_DEDUPE_DEPS_OUTCOME: ${{ steps.dedupe_deps.outcome }}
STEPS_BUILD_PACKAGES_OUTCOME: ${{ steps.build_packages.outcome }}
STEPS_RUN_UNIT_TESTS_OUTCOME: ${{ steps.run_unit_tests.outcome }}
run: |
if [ -z "$SLACK_WEBHOOK_URL" ]; then
echo "SLACK_WEBHOOK_URL is not configured; skipping Slack notification."
exit 0
fi

failed_step="Unknown step"
if [ "${{ steps.checkout.outcome }}" = "failure" ]; then
if [ "${STEPS_CHECKOUT_OUTCOME}" = "failure" ]; then
failed_step="Checkout repository"
elif [ "${{ steps.install_pnpm.outcome }}" = "failure" ]; then
elif [ "${STEPS_INSTALL_PNPM_OUTCOME}" = "failure" ]; then
failed_step="Install pnpm"
elif [ "${{ steps.setup_node.outcome }}" = "failure" ]; then
elif [ "${STEPS_SETUP_NODE_OUTCOME}" = "failure" ]; then
failed_step="Setup Node.js"
elif [ "${{ steps.install_dependencies.outcome }}" = "failure" ]; then
elif [ "${STEPS_INSTALL_DEPENDENCIES_OUTCOME}" = "failure" ]; then
failed_step="Install dependencies"
elif [ "${{ steps.update_prod_deps.outcome }}" = "failure" ]; then
elif [ "${STEPS_UPDATE_PROD_DEPS_OUTCOME}" = "failure" ]; then
failed_step="Update prod deps of published packages"
elif [ "${{ steps.dedupe_deps.outcome }}" = "failure" ]; then
elif [ "${STEPS_DEDUPE_DEPS_OUTCOME}" = "failure" ]; then
failed_step="Dedupe transitive dependencies"
elif [ "${{ steps.build_packages.outcome }}" = "failure" ]; then
elif [ "${STEPS_BUILD_PACKAGES_OUTCOME}" = "failure" ]; then
failed_step="Build packages"
elif [ "${{ steps.run_unit_tests.outcome }}" = "failure" ]; then
elif [ "${STEPS_RUN_UNIT_TESTS_OUTCOME}" = "failure" ]; then
failed_step="Run unit tests"
fi

Expand Down
19 changes: 11 additions & 8 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,39 +29,42 @@ jobs:
attestations: write
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ inputs.version }}
fetch-depth: 100
persist-credentials: false
- name: Display version being published
run: |
echo "Publishing version: ${{ inputs.version }}"
echo "Publishing version: ${INPUTS_VERSION}"
env:
INPUTS_VERSION: ${{ inputs.version }}

- run: jq '.packageManager' package.json | tr -d '"pnpm@'
id: package-manager-version

- name: Install pnpm
uses: pnpm/action-setup@v5
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
with:
version: ${{ steps.package-manager-version.outputs.stdout }}

- uses: nrwl/nx-set-shas@v5
- uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5

- uses: actions/setup-node@v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 # zizmor: ignore[cache-poisoning]
with:
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
# No pnpm cache -- correctness over speed for publish workflows
node-version-file: ".nvmrc"

- name: Cache NX
uses: actions/cache@v5
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: .nx/cache
key: nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }}
restore-keys: |
nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-
nx-${{ env.NX_BRANCH }}-
nx-
lookup-only: true

- name: Install Dependencies & Build
run: pnpm install && pnpm build
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/relative-ci.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
name: RelativeCI

on:
# zizmor: ignore[dangerous-triggers] -- workflow_run is the recommended pattern
# for RelativeCI; this workflow only downloads artifacts and reports bundle stats.
Comment on lines +4 to +5

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Check upstream trigger includes pull_request:"
rg -n -C2 '^\s*pull_request:' .github/workflows/build.yml

echo
echo "Check downstream workflow_run and trust guard presence:"
rg -n -C3 'workflow_run:|github\.event\.workflow_run\.event|github\.event\.workflow_run\.conclusion' .github/workflows/relative-ci.yaml

echo
echo "Check downstream job uses secrets:"
rg -n -C2 'secrets\.RELATIVE_CI_KEY|secrets\.GITHUB_TOKEN' .github/workflows/relative-ci.yaml

Repository: TypeCellOS/BlockNote

Length of output: 733


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== relative-ci.yaml ==="
nl -ba .github/workflows/relative-ci.yaml | sed -n '1,200p'

echo
echo "=== build.yml (trigger section) ==="
nl -ba .github/workflows/build.yml | sed -n '1,120p'

Repository: TypeCellOS/BlockNote

Length of output: 150


🌐 Web query:

GitHub Actions workflow_run triggered by pull_request whether downstream workflow gets secrets and how permissions/security context works

💡 Result:

Downstream workflow permissions/secrets behavior when using on: workflow_run after a pull_request 1) Does the downstream workflow get repository secrets? - Yes: a workflow started by workflow_run “is able to access secrets and write tokens, even if the previous workflow was not.” [1][2] - Practical implication: if the upstream workflow was triggered by pull_request (including from a fork), and the downstream is triggered via workflow_run, the downstream runs with elevated access (i.e., it can access repository secrets), which is why workflow_run can be security-sensitive. [1][2] 2) Does the upstream pull_request workflow have access to secrets? - Generally, for pull_request from a fork, GitHub prevents secret passing: “With the exception of GITHUB_TOKEN, secrets are not passed … when a workflow is triggered from a forked repository. The GITHUB_TOKEN has read-only permissions in pull requests from forked repositories.” [1] 3) What’s special about GITHUB_TOKEN in this context? - GITHUB_TOKEN is always included in the secrets context for any workflow run (i.e., it exists as secrets.GITHUB_TOKEN / github.token). [3] - The key nuance is its permissions: for fork-based pull_request, GITHUB_TOKEN is read-only; for workflow_run, GitHub states the workflow_run-triggered workflow can write tokens and access secrets. [1][2][3] 4) Security context / where the code runs (why this matters) - pull_request executes in a way that prevents running unsafe code from the pull request head with access to secrets (it “prevents execution of unsafe code from the head … that could … steal any secrets you use in your workflow”). [4] - workflow_run is explicitly described as able to access secrets and write tokens, even when the prior workflow was not privileged. [2] - Therefore, the security context can “escalate” on the workflow_run hop: downstream code (or steps) can run in a privileged context with secrets, even if the upstream was unprivileged due to fork restrictions. [1][2] 5) How permissions/security should be configured - Always restrict GITHUB_TOKEN permissions explicitly with the permissions key (least privilege). [1][5] - Additionally, treat workflow_run as privileged: avoid checking out/building untrusted PR code directly in the workflow_run workflow, and gate privileged actions based on what triggered the run (e.g., only proceed for trusted events/branches), because untrusted input can be used to poison artifacts that the downstream consumes. [1][2] Answering your exact question succinctly - If your downstream workflow is triggered via on: workflow_run after an upstream pull_request, the downstream workflow can access repository secrets and write tokens, even if the upstream pull_request workflow didn’t. [1][2] - The upstream pull_request workflow (from forks) does not get secrets (except GITHUB_TOKEN, with read-only permissions). [1] - The permissions/security context is therefore not “inherited as unprivileged”; workflow_run creates a privileged second stage. [1][2]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== relative-ci.yaml (show full file if small) ==="
wc -l .github/workflows/relative-ci.yaml
sed -n '1,200p' .github/workflows/relative-ci.yaml | cat -n

echo
echo "=== build.yml (show trigger section) ==="
wc -l .github/workflows/build.yml
sed -n '1,120p' .github/workflows/build.yml | cat -n

Repository: TypeCellOS/BlockNote

Length of output: 5622


Gate RelativeCI’s workflow_run on trusted build runs before using repository secrets.

relative-ci.yaml triggers on workflow_run for all completed build runs (including those originating from pull_request) with no guard on github.event.workflow_run.event/conclusion, and then passes secrets.RELATIVE_CI_KEY and secrets.GITHUB_TOKEN into relative-ci/agent-action. The zizmor dangerous-triggers ignore comment doesn’t create a trust boundary.

Suggested patch
 jobs:
   build:
+    if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' }}
     runs-on: ubuntu-latest
     steps:
       - name: Send bundle stats and build information to RelativeCI (editor)
         uses: relative-ci/agent-action@38328454d6a23942175eba485fca4fbb807b1f03 # v2
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/relative-ci.yaml around lines 4 - 5, The workflow
currently runs on workflow_run for all completed build runs and unconditionally
supplies secrets.RELATIVE_CI_KEY and secrets.GITHUB_TOKEN to
relative-ci/agent-action; add a trust boundary by gating the job (or the step
that uses relative-ci/agent-action and the secrets) with an if condition that
checks github.event.workflow_run.workflow.name == 'build' AND
github.event.workflow_run.conclusion == 'success' AND
github.event.workflow_run.event == 'push' (or another trusted event), so the
secrets are only passed when the originating build run is a successful trusted
push build.

workflow_run:
workflows: ["build"]
types:
- completed

permissions:
actions: read
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Send bundle stats and build information to RelativeCI (editor)
uses: relative-ci/agent-action@v2
uses: relative-ci/agent-action@38328454d6a23942175eba485fca4fbb807b1f03 # v2
with:
artifactName: relative-ci-artifacts-editor
key: ${{ secrets.RELATIVE_CI_KEY }}
Expand Down
Loading