Skip to content
This repository was archived by the owner on Oct 12, 2026. It is now read-only.
Merged
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
38 changes: 32 additions & 6 deletions .github/workflows/deploy-public-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ name: Deploy public site
# fixes (H2-after-H1 on /promptly/support/, JSON-LD on /contact/, real 404s,
# portfolio-only homepage).
#
# Required secrets (fail-closed when absent):
# Required secrets (fail-closed when present):
# CLOUDFLARE_API_TOKEN - Cloudflare API token with Cloudflare Pages:Edit
# on the tinystudio.in account
# CLOUDFLARE_ACCOUNT_ID - f670a698e17bf160c8e4679823e68916
# The fleet Workers token (fleet-console/cf.env) does NOT have Pages:Edit;
# provisioning a Pages-scoped token is a one-time dashboard step. The lane
# prints the exact steps when the secret is missing or lacks Pages:Edit.
# provisioning a Pages-scoped token is a one-time dashboard step.
#
# While BOTH secrets are absent the lane runs a seconds-long "dormant"
# notice step that prints the exact provisioning steps and exits green, so
# main merges are not blocked by the missing credential. The moment both
# secrets exist, every main merge runs the full publish-and-verify pipeline
# and any failure fails the run loudly (fail-closed). The nightly
# live-site-check.yml is the loud staleness alarm while the site is stale.

on:
push:
Expand All @@ -33,24 +39,44 @@ concurrency:
jobs:
publish:
runs-on: [self-hosted, linux, x64, vps-verify]
# Secrets are hoisted to the job env because the `secrets` context is not
# available in step-level `if:` conditions; the `env` context is.
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
Comment on lines +44 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the Pages token scoped to the publishing step

When the credentials are provisioned, this job-level env exposes the Pages:Edit token to every action and command in the job—including checkout, setup-node, the repository test suite, and bundle preparation—whereas the previous workflow exposed it only to the publisher. Any compromised action or newly merged test code could read the token and use it to replace the production Pages deployment; derive a non-secret readiness output in a narrowly scoped step, then continue passing the actual credentials only to the publish step.

Useful? React with 👍 / 👎.

Comment on lines +44 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

workflow=.github/workflows/deploy-public-site.yml

# Expect: raw Cloudflare credentials appear only in the publish step's env map.
rg -n -C 3 'CLOUDFLARE_API_TOKEN|CLOUDFLARE_ACCOUNT_ID|HAS_CLOUDFLARE' "$workflow"

Repository: nish3451/tinystudio-in

Length of output: 3164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- workflow ---'
cat -n .github/workflows/deploy-public-site.yml

printf '%s\n' '--- Cloudflare credential references ---'
rg -n -C 4 'CLOUDFLARE_API_TOKEN|CLOUDFLARE_ACCOUNT_ID|secrets\.' .github/workflows scripts package.json

printf '%s\n' '--- publish script environment access ---'
rg -n -C 5 'process\.env|CLOUDFLARE|deploy|prepare-only' scripts/publish-public-site.mjs

Repository: nish3451/tinystudio-in

Length of output: 19418


🌐 Web query:

GitHub Actions workflow syntax job-level env step-level env secrets context if condition env context documentation

💡 Result:

In GitHub Actions, environment variables (env) and secrets can be used throughout your workflows using specific syntax and scoping rules [1][2][3]. Env Scope and Precedence You can define environment variables at the workflow, job, or step level [2][4]. When the same variable name is defined at multiple levels, the most specific definition overrides the others [2][5]. - Step-level env overrides job-level and workflow-level env [2][5]. - Job-level env overrides workflow-level env [2][5]. - Workflow-level env acts as a default for all jobs [5][4]. Context Syntax - Env context: Use ${{ env.VAR_NAME }} to access environment variables in your workflow expressions (such as if conditions) [1][6]. - Secrets context: Use ${{ secrets.SECRET_NAME }} to access your repository secrets [1][3]. - Expressions: Use ${{... }} syntax to evaluate expressions [7]. In if conditional clauses, the ${{ }} syntax is optional [7]. If Conditions and Secrets Secrets cannot be directly referenced in if conditional statements for security reasons [3]. To use a secret in a conditional: 1. Map the secret to a job-level environment variable [3]. 2. Reference that environment variable in your step's if conditional using the env context [6][3]. Example: jobs: example-job: runs-on: ubuntu-latest env: # Map secret to a job-level env var MY_SECRET_VAR: ${{ secrets.MY_SECRET }} steps: - name: Conditional step # Access the env var via the env context if: ${{ env.MY_SECRET_VAR!= '' }} run: echo "Secret is available"

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json
import re
from pathlib import Path

workflow = Path(".github/workflows/deploy-public-site.yml").read_text()
package = json.loads(Path("package.json").read_text())

for name in ("CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"):
    matches = [
        (i + 1, line)
        for i, line in enumerate(workflow.splitlines())
        if name in line and "secrets." in line
    ]
    print(f"{name}:")
    for line_no, line in matches:
        print(f"  line {line_no}: {line.strip()}")

print("\nRelevant package scripts:")
for name, command in package.get("scripts", {}).items():
    if name == "check" or "publish-public-site" in command:
        print(f"  {name}: {command}")

print("\nCurrent workflow commands:")
for i, line in enumerate(workflow.splitlines(), 1):
    if re.search(r"\brun:\s*(npm run check|node scripts/publish-public-site\.mjs)", line):
        print(f"  line {i}: {line.strip()}")
PY

Repository: nish3451/tinystudio-in

Length of output: 803


Restrict Cloudflare credentials to the publish step.

The job-level env exposes both credentials to actions/checkout@v7, npm run check (npm test), and bundle preparation. Keep only non-secret presence flags at job scope. Define the raw credentials in Publish to Cloudflare Pages and verify live.

🤖 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/deploy-public-site.yml around lines 44 - 46, Move
CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID out of the job-level env and
define the raw secrets only within the “Publish to Cloudflare Pages and verify
live” step. Keep job scope limited to non-secret presence flags, ensuring
checkout, checks, tests, and bundle preparation cannot access the credentials.

steps:
- uses: actions/checkout@v7
with:
persist-credentials: false

- name: Release lane dormant - Pages token not provisioned yet
if: env.CLOUDFLARE_API_TOKEN == '' || env.CLOUDFLARE_ACCOUNT_ID == ''
run: |
echo "::warning title=Release lane dormant::CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID are not set in repo secrets, so the publish step is skipped. The lane will deploy automatically on the next main merge once both secrets are provisioned; until then the live site stays on its current bundle and the nightly live-site-check.yml reports it."
echo "One-time provisioning (dashboard, ~2 minutes):"
echo " 1. https://dash.cloudflare.com/profile/api-tokens -> Create Token"
echo " 2. Use the 'Cloudflare Pages: Edit' template, scope it to the account (account id f670a698e17bf160c8e4679823e68916), create, copy the token."
echo " 3. gh secret set CLOUDFLARE_API_TOKEN -R nish3451/tinystudio-in"
echo " 4. gh secret set CLOUDFLARE_ACCOUNT_ID -R nish3451/tinystudio-in -b f670a698e17bf160c8e4679823e68916"
echo " 5. Re-run this lane (or wait for the next main merge); no code change needed."

- uses: actions/setup-node@v7
if: env.CLOUDFLARE_API_TOKEN != '' && env.CLOUDFLARE_ACCOUNT_ID != ''
with:
node-version: 20
cache: npm

- run: npm ci --ignore-scripts
if: env.CLOUDFLARE_API_TOKEN != '' && env.CLOUDFLARE_ACCOUNT_ID != ''

- name: Pre-deploy repository gate
if: env.CLOUDFLARE_API_TOKEN != '' && env.CLOUDFLARE_ACCOUNT_ID != ''
run: npm run check

- name: Prepare the filtered deploy bundle
if: env.CLOUDFLARE_API_TOKEN != '' && env.CLOUDFLARE_ACCOUNT_ID != ''
run: node scripts/publish-public-site.mjs --prepare-only --output "$RUNNER_TEMP/tinystudio-public-bundle"

- name: Publish to Cloudflare Pages and verify live
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
if: env.CLOUDFLARE_API_TOKEN != '' && env.CLOUDFLARE_ACCOUNT_ID != ''
run: node scripts/publish-public-site.mjs --deploy --bundle "$RUNNER_TEMP/tinystudio-public-bundle"
Loading