diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index ad818689..cd4b307a 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -29,9 +29,6 @@ jobs: BOS_STORAGE_ORIGIN: ${{ vars.BOS_STORAGE_ORIGIN || 'https://upload.everything.dev' }} BOS_STORAGE_API_KEY: ${{ secrets.BOS_STAGING_STORAGE_API_KEY }} RAILWAY_TOKEN: ${{ secrets.RAILWAY_STAGING_TOKEN }} - RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_STAGING_API_TOKEN }} - RAILWAY_PROJECT_ID: ${{ vars.RAILWAY_STAGING_PROJECT_ID }} - RAILWAY_ENVIRONMENT_ID: ${{ vars.RAILWAY_STAGING_ENVIRONMENT_ID }} steps: - name: Checkout code uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 (Node 24) diff --git a/scripts/check-workflows.test.ts b/scripts/check-workflows.test.ts index 1c322647..c7f277a9 100644 --- a/scripts/check-workflows.test.ts +++ b/scripts/check-workflows.test.ts @@ -49,6 +49,19 @@ test("a staging workflow on production's Railway token is refused", () => { ]); }); +test("a staging workflow with a workspace-scoped Railway token is refused", () => { + const directory = workflows({ + "staging.yml": `env: + RAILWAY_TOKEN: \${{ secrets.RAILWAY_STAGING_TOKEN }} + RAILWAY_API_TOKEN: \${{ secrets.RAILWAY_STAGING_API_TOKEN }} + RAILWAY_PROJECT_ID: \${{ vars.RAILWAY_STAGING_PROJECT_ID }} +`, + }); + assert.deepEqual(checkWorkflows(directory), [ + `staging.yml: sets RAILWAY_API_TOKEN, a workspace-scoped Railway token that reaches every project in the workspace, production included`, + ]); +}); + test("our workflows pass", () => { const directory = workflows({ "staging.yml": `env: diff --git a/scripts/check-workflows.ts b/scripts/check-workflows.ts index 0d90b7f6..12e8ff71 100644 --- a/scripts/check-workflows.ts +++ b/scripts/check-workflows.ts @@ -17,6 +17,11 @@ export function checkWorkflows(directory: string): string[] { if (file === "staging.yml" && /secrets\.RAILWAY_TOKEN\b/.test(text)) { problems.push(`${file}: reads secrets.RAILWAY_TOKEN, production's Railway token`); } + if (file === "staging.yml" && /^\s*RAILWAY_API_TOKEN\s*:/m.test(text)) { + problems.push( + `${file}: sets RAILWAY_API_TOKEN, a workspace-scoped Railway token that reaches every project in the workspace, production included`, + ); + } for (const match of text.matchAll( /^\s*uses:\s*([^\s#]+\/\.github\/workflows\/[^\s#@]+)@([^\s#]+)/gm, )) { @@ -33,6 +38,6 @@ if (import.meta.url === `file://${resolve(process.argv[1] ?? "")}`) { for (const problem of problems) console.error(problem); if (problems.length > 0) process.exit(1); console.log( - "Workflows keep staging on its own token, pass no inherited secrets, and pin by SHA.", + "Workflows keep staging on its own project token, pass no inherited secrets, and pin by SHA.", ); }