Skip to content

Conversation

@scode2277
Copy link
Collaborator

Frameworks PR Checklist

This is the setup to make the github bot upload the assets to our s3 automatically. This is how it works:

  1. Contributors will add the images to the PR description or a comment (images added in the PRs/issues are not stored in the git history but hosted by github and part of URL is a temporary token (it changes every 5-10min))
  2. A maintainer checks the image, copies the URL and makes a comments in the PR with '/img-bot' + the URL(s).
  3. The bot firstly reacts to the comment with an emoji to show that it started (the reaction is needed because, natively, workflows triggered by comments do not provide the processing status in the PR)
  4. Than processes and uploads the image(s) to the bucket
  5. And at the end, it comments in the PR with the permanent s3 hosted links that the contributors can use in their content

Safety checks:

  • this workflow can be triggered only from collaborators with either admin, maintain or triage permissions
  • github urls change every 5-10 min
  • to prevent the risk of overwriting, the name with which these files will be stored in the bucket is a mix of: the timestamp of the comment that triggers the upload, a random unique identifier generated with uuid, the original name of the image and a hash of the original URL of the image
  • the urls are validated: only github hosted urls are parsed
  • files have a restricted size and dimensions check
  • formats can be only .jpeg, .jpg or .png

This is an example of URL of an image hosted by github (taken from a comment in a PR - by the time you are reading this it could be expired) and then the URL of that image uploaded on the bucket:

@scode2277 scode2277 requested a review from mattaereal November 13, 2025 01:19
@scode2277 scode2277 added local setup Improvements or additions to the local setup dependencies Pull requests that update a dependency file labels Nov 13, 2025
@vercel
Copy link

vercel bot commented Nov 13, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
frameworks Ready Ready Preview Comment Dec 10, 2025 5:48pm

@scode2277 scode2277 linked an issue Nov 13, 2025 that may be closed by this pull request
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Nov 20, 2025

Deploying frameworks with  Cloudflare Pages  Cloudflare Pages

Latest commit: f80b14c
Status: ✅  Deploy successful!
Preview URL: https://aa8398b3.frameworks-573.pages.dev
Branch Preview URL: https://feat-s3-upload-workflow.frameworks-573.pages.dev

View logs

@scode2277
Copy link
Collaborator Author

Note:
made it a draft as pulling from dev created an error in the vercel deployment (but not in the CF - have to check why) + we need to add the credentials to the repo first

@scode2277
Copy link
Collaborator Author

@mattaereal this is ready for review as the problem with Vercel is solved (it was failing as the new version of vocs we are using needed a higher version of node).

Anyway, to make the workflow work we still need to add proper credentials just for this workflow (will ask @davidthegardens to help with this)

@scode2277
Copy link
Collaborator Author

Update on this:
Im going to add the credentials today and let you know so this can be merged @mattaereal

@mattaereal
Copy link
Collaborator

I'm reading the code and pair-auditing it with AI. My prompt was:
Having this PR as a context, with this description. I need to audit @.github/scripts/process-images.js @.github /workflows/workflows/s3-upload.yml. Tell me how a security expert or state actor could exploit this somehow.

In general terms, leaving aside AI output, I think these three are a way forward:

  1. Filtering should be stricter.
    if (url.includes('private-user-images.githubusercontent.com') || url.includes('user-images.githubusercontent.com')) {

The following could be bypassed by using something like private-user-images.githubusercontent.com.attacker.domain.com, I see this all the time in the certsentry project we created.

  1. Instead of using the AWS credentials, we should move to temporary OIDC in case someone manages to exfiltrate the keys in the process. This should be kind of a standard right now, is what we suggest projects do when publishing an npm package

The s3 client initialization only has AWS_REGION, which we won't mind if it gets leaked. Then the configuration can be something like this:

      - name: Validate AWS secrets
        run: |
          if [ -z "${{ secrets.AWS_ROLE_ARN }}" ]; then
            echo "::error::AWS_ROLE_ARN secret is not configured."
            exit 1
          fi
....
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@ff717079ee2060e4bcee96c4779b553acc87447c # v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ${{ secrets.AWS_REGION }}
          role-session-name: img-bot-${{ github.run_id }}

....
  1. We're using a lot of GitHub actions without being pinned (from their tags), and we are prone to supply chain attacks (now more than ever with what is going on).
# replace this
uses: actions/github-script@v7
#with this
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7

Next steps

Since I don't have access to AWS, I asked Codex to explain to me how it would implement OIDC in this context

Prereqs: you already have AWS_REGION and AWS_S3_BUCKET secrets set. Keep them. You will add AWS_ROLE_ARN after creating the role.

Create (or reuse) the GitHub OIDC IdP in AWS IAM:

Create a least-privilege IAM role for web identity (trusting that IdP):

  • In IAM → Roles → Create role → Web identity → Choose the GitHub OIDC provider from above → Audience sts.amazonaws.com.

  • Name: something like github-img-bot-upload.

  • Attach a policy limited to your bucket and key prefix. Example (replace YOUR-BUCKET and keep the images/ prefix we use in the workflow):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:PutObjectAcl"
      ],
      "Resource": "arn:aws:s3:::YOUR-BUCKET/images/*"
    }
  ]
}

(PutObjectAcl is needed because the script sets CacheControl/ContentType; if you don’t need ACLs, you can omit it.)

  • In the Trust policy, lock it to your repo so only that workflow can assume it. Minimal example (replace OWNER/REPO):
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Federated": "arn:aws:iam::YOUR-AWS-ACCOUNT-ID:oidc-provider/token.actions.githubusercontent.com" },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:OWNER/REPO:ref:refs/heads/main"
        }
      }
    }
  ]
}
  • Adjust sub if you want to allow other branches (ref:refs/heads/*) or specific environments; keep it as tight as possible.

Save the role ARN:

  • After creating, copy the role ARN (looks like arn:aws:iam::123456789012:role/github-img-bot-upload).

  • In your GitHub repo settings → Secrets and variables → Actions → Repository secrets, add AWS_ROLE_ARN with that value. (You already have AWS_REGION/AWS_S3_BUCKET.)

  • Optional hardening:

  • Add s3:PutObjectTagging if you later tag uploads.

  • Add StringLike on token.actions.githubusercontent.com:sub for repo:OWNER/REPO:pull_request if you only want PR-triggered jobs.

  • Consider adding a condition on token.actions.githubusercontent.com:iss and aud (already in example) and on token.actions.githubusercontent.com:repository_owner.

After this, the workflow’s configure-aws-credentials step will assume the role using OIDC, and the Node script will pick up temporary creds automatically. Trigger a test PR comment (/img-bot ) to verify.

@mattaereal mattaereal marked this pull request as draft December 17, 2025 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file local setup Improvements or additions to the local setup

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement a process that safely uploads images to an S3 bucket

3 participants