Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8

- 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@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8

- 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@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8

- 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@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8

- 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@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8

- 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@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
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
9 changes: 8 additions & 1 deletion .github/workflows/relative-ci.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
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

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
if: github.event.workflow_run.conclusion == 'success'
steps:
- name: Send bundle stats and build information to RelativeCI (editor)
uses: relative-ci/agent-action@v2
uses: relative-ci/agent-action@fcf45416581928e8dd62eded78ce98c78e5149f8 # v3.2.3
with:
artifactName: relative-ci-artifacts-editor
key: ${{ secrets.RELATIVE_CI_KEY }}
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: GitHub Actions Security Analysis with zizmor

on:
push:
branches: ["main"]
pull_request:
branches: ["**"]

permissions: {}

jobs:
zizmor:
name: Run zizmor
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Run zizmor
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
Loading