Skip to content

ci: auto-upload contributor images to Cloudflare#388

Merged
bearpong merged 2 commits intomainfrom
ci/upload-to-cloudflare
Apr 23, 2026
Merged

ci: auto-upload contributor images to Cloudflare#388
bearpong merged 2 commits intomainfrom
ci/upload-to-cloudflare

Conversation

@bearpong
Copy link
Copy Markdown
Collaborator

Summary

  • Adds a CI pipeline that uploads contributor-added images under src/assets/{tokens,vaults,validators}/ to Cloudflare Images, so validate:images no longer fails for PRs from contributors who don't have Cloudflare credentials.
  • Gated behind the cloudflare-uploads GitHub Environment with required reviewers — every asset-changing PR pauses on "Waiting for review" before any secret is exposed.
  • Re-runs image validation automatically after upload, so the images check turns green in the same workflow run.

How it works

New jobs in .github/workflows/validate.yml:

  • detect-changes — uses dorny/paths-filter@v3 to set assets-changed output. No secrets.
  • upload-assetsneeds: [schema, detect-changes], if: assets-changed == 'true', environment: cloudflare-uploads. Runs pnpm upload:assets ./head. For JSON-only PRs this is skipped before the environment prompt is ever requested.
  • images — reordered: now needs: [schema, upload-assets] with if: always() && schema == success && (upload-assets == success || skipped). This makes image validation re-run after upload, and still runs immediately for JSON-only PRs (skipped dep counts as satisfied).

The new scripts/uploadAssets.ts:

  1. Resolves changed src/assets/** files via gh api /repos/{repo}/pulls/{pr}/files --paginate (base-branch script against PR head bytes — untrusted PR code is never executed).
  2. Per-file validates path, extension, filename regex, EIP-55 checksum (tokens/vaults), 1024×1024 dimensions, PNG non-transparency, and 5 MB cap — reusing helpers extracted to scripts/utils/_imageChecks.ts.
  3. Uploads with id = "{type}/{filename}" matching addVaultLogo.ts convention.
  4. On Cloudflare error 5409 ALREADY_EXISTS: DELETE then retry POST once (overwrite mode).
  5. Writes a markdown summary to $GITHUB_STEP_SUMMARY, posts it as a PR comment, and exits non-zero on any failure.

Security model (defense in depth)

  1. GitHub Environment cloudflare-uploads with required reviewers — the sole secrets gate. Secrets live on the environment, not the repo, so no other job ever sees them.
  2. No PR-authored code is executed — base branch is checked out at root and runs pnpm install + scripts/uploadAssets.ts; PR head is checked out to ./head and only image bytes are read from there. PR head is pinned to pull_request.head.sha (frozen at event time) to prevent a force-push race between approval and checkout.
  3. Per-file static validation before any Cloudflare call — path allowlist, filename regex, checksum, dimensions, transparency, size cap. A malicious PR cannot get a file uploaded that doesn't pass all checks.
  4. Workflow permissions stay contents: read; only upload-assets gets pull-requests: write for its summary comment.

One-time repo setup

Needs to happen in repo Settings before the workflow is useful:

  1. Create GitHub Environment cloudflare-uploads with required reviewers = maintainers allowed to approve uploads. Optionally restrict to main branch.
  2. Add environment secrets CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_IMAGES_API_TOKEN (token scoped to Cloudflare Images: Edit only).

Test plan

  • On a test fork PR that adds src/assets/tokens/0x<checksum>.png (1024×1024, no transparency): confirm detect-changes outputs assets-changed=true, upload-assets pauses on "Waiting for review", approving unlocks the upload, and images then runs green in the same run.
  • Push a new commit to the same PR replacing the image: confirm the 5409 → DELETE → re-POST path runs and the summary shows the file under overwritten.
  • Push a commit that mutates ./head/scripts/uploadAssets.ts: confirm the workflow still executes the base-branch script (no untrusted code runs).
  • Submit a 512×512 image or a file with a non-checksum address: confirm the upload step rejects it before any Cloudflare call, and images subsequently fails with HEAD 404.
  • Open a JSON-only PR: confirm upload-assets is skipped (no environment prompt) and images runs immediately and passes.

🤖 Generated with Claude Code

Comment on lines +160 to +161
- uses: actions/checkout@v4
- uses: actions/checkout@v4
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.

is this duplication intentional?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

yes, bc we checkout both head and base branch

Comment on lines +22 to +44
if (buffer.slice(0, 8).toString("hex") === "89504e470d0a1a0a") {
const width = buffer.readUInt32BE(16);
const height = buffer.readUInt32BE(20);
return { width, height };
}

// JPG: ff d8, scan for SOF marker (0xC0-0xC3) to extract dimensions
if (buffer.slice(0, 2).toString("hex") === "ffd8") {
let i = 2;
while (i < buffer.length) {
const segmentLength = buffer.readUInt16BE(i + 2);
if (
buffer[i] === 0xff &&
buffer[i + 1] >= 0xc0 &&
buffer[i + 1] <= 0xc3
) {
const height = buffer.readUInt16BE(i + 5);
const width = buffer.readUInt16BE(i + 7);
return { width, height };
}
i += segmentLength + 2;
}
}
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.

pls add some comments here

@Timbearlake
Copy link
Copy Markdown
Contributor

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eda30f0d0c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/uploadAssets.ts Outdated
Comment on lines +174 to +177
if (!/^0x[0-9a-f]{40}$/i.test(basename)) {
return {
ok: false,
reason: "filename must be 0x + 40 hex chars (address)",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Allow default token image names in upload validation

validatePath now hard-requires tokens/vaults filenames to be 0x... addresses, so a PR that updates src/assets/tokens/default.png (an existing asset) will fail in upload-assets before images can run. This is a regression from scripts/validateImages.ts, which explicitly exempts default-named images, and it blocks legitimate default-logo refreshes even though they were previously valid.

Useful? React with 👍 / 👎.

@bearpong bearpong merged commit 4551b62 into main Apr 23, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants