Cloudflare Setup: A Read-Only API Token

This guide sets up a read-only Cloudflare API token for the LeftSize scan. The token is stored as a GitHub secret in your repository and is only used on your GitHub runner.

Why an API token and not OIDC

On AWS and Azure, LeftSize signs in with OpenID Connect (OIDC), so no secret is stored. Cloudflare has no OIDC or workload identity federation for API tokens, so a read-only API token stored as a GitHub secret is the only way to sign in.

The token never reaches LeftSize. The action reads it on your runner, calls the Cloudflare API with it and sends LeftSize only the findings, the same as with AWS and Azure credentials. See Security and Data.

Prerequisites

  • A Cloudflare account with zones or other resources to scan
  • Permission to create account API tokens: Super Administrator, or API Token Provisioning on the account
  • A GitHub repository with LeftSize installed

Step 1: Create an account-owned, read-only token

Use an account-owned token (an account API token). It isn’t tied to a person, so the scan keeps working when the person who created it leaves the team.

  1. Open the LeftSize token template). It opens Create account API token in the Cloudflare dashboard with the name LeftSize (read-only) and the permissions below filled in. The onboarding page at leftsize.com gives you the same link.
  2. If you have access to more than one account, pick the account to scan.
  3. Under the zone resources, include all zones of the account, so new zones are scanned too.
  4. Optionally set a TTL (end date); see Rotating the token.
  5. Click Continue to summary, check the permissions, then Create Token.
  6. Copy the token. Cloudflare shows it only once.

To create the token by hand instead, go to Manage Account > Account API tokens > Create Token, choose a custom token and add these permissions, all set to Read:

Token permissions

Scope Permission Used for
Zone Zone Listing zones, their plan and status. The action uses it to check the token before it scans; without it, the zone rules don’t run (see Verifying the setup).
Zone Zone Settings SSL/TLS mode, minimum TLS version, Always Use HTTPS, HSTS, Auto Minify
Zone DNS DNSSEC status
Zone SSL and Certificates Origin CA and custom certificates
Account Account Settings Two-factor enforcement and members without two-factor authentication
Account Account Analytics Worker request counts, Workers AI usage per model
Account Account API Tokens Account API tokens: last use, permissions, expiry, IP restrictions
Account Workers Scripts Workers and their settings
Account Workers R2 Storage R2 buckets: r2.dev access, CORS, lifecycle rules
Account Access: Apps and Policies Access applications and their policies
Account Access: Service Tokens Access service tokens
Account Access: Audit Logs Zero Trust users and their seats (the permission Cloudflare’s users API requires)
Account Access: Organizations, Identity Providers, and Groups The seat expiration setting
Account Cloudflare Tunnel Tunnels and their connection status
Account Load Balancing: Monitors and Pools Load balancer pools

All of these are read permissions. With them LeftSize can’t change anything in your account.

Page Rules and Registrar: a user API token

Cloudflare doesn’t support Page Rules and Registrar with account-owned tokens. Three rules need them:

  • leftsize-cloudflare-page-rules-deprecated needs Page Rules Read (zone)
  • leftsize-cloudflare-domain-expiring and leftsize-cloudflare-domain-transfer-unlocked need Registrar Domains Read (account)

With an account-owned token these three rules don’t run; the rest of the scan does. To include them, create a user API token instead (My Profile > API Tokens > Create Token) with the permissions in the table above plus Page Rules Read and Registrar Domains Read. A user API token can do everything the scan needs. The trade-off: it belongs to a person and acts with that person’s access, so it stops working when that person loses access to the account.

Step 2: Add the secret to GitHub

In your repository, go to Settings > Secrets and variables > Actions.

Secrets

Secret Value
CLOUDFLARE_API_TOKEN The API token from Step 1
LEFTSIZE_INSTALLATION_ID Provided during LeftSize onboarding
LEFTSIZE_REPOSITORY_TOKEN Provided during LeftSize onboarding

Variables (optional)

Variable Value
CLOUDFLARE_ACCOUNT_IDS Account IDs to scan, comma-separated. Leave it empty to scan every account the token can read.

Step 3: Add the workflow

The onboarding page generates this workflow. Commit it as .github/workflows/leftsize.yml:

name: LeftSize Cloudflare Scan

on:
  schedule:
    - cron: '17 6 * * *'
  workflow_dispatch:

permissions:
  contents: read

jobs:
  leftsize-cloudflare:
    runs-on: ubuntu-latest
    steps:
      - name: Run LeftSize Scan
        id: leftsize
        uses: leftsize/leftsize-action@v1
        with:
          installation-id: ${{ secrets.LEFTSIZE_INSTALLATION_ID }}
          repository-token: ${{ secrets.LEFTSIZE_REPOSITORY_TOKEN }}
          cloud-provider: cloudflare
          cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          cloudflare-account-ids: ${{ vars.CLOUDFLARE_ACCOUNT_IDS }}
          verbose: false

      - name: View Results Summary
        if: always()
        run: |
          echo "LeftSize Cloudflare scan complete!"
          echo "Found ${{ steps.leftsize.outputs.findings-count }} findings"
          echo "Results submitted: ${{ steps.leftsize.outputs.findings-submitted }}"

There is no login step: the action signs in with the token itself. The job needs no id-token: write permission, because it doesn’t use OIDC. To scan AWS or Azure from the same workflow, add a job per cloud; see Scanning several clouds from one workflow.

Scanning some accounts only

Without cloudflare-account-ids, the scan covers every account the token can read. To limit it, set the CLOUDFLARE_ACCOUNT_IDS variable to one or more account IDs, comma-separated. An account ID is 32 hexadecimal characters. In the dashboard, copy it from the API section of a domain’s Overview page, or from Account Details under Workers & Pages. An invalid ID stops the step with Invalid Cloudflare account ID(s): expected 32 hex characters.

Findings are grouped per Cloudflare account: each rule gets one issue per account, so issues from different accounts never mix.

Rotating the token

The token is a long-lived credential stored in GitHub. Limit what a leaked token can do:

  • Set a TTL. Give the token an end date when you create it, and create a new one before it expires. When it has expired, the scan fails with an authentication error until you update the CLOUDFLARE_API_TOKEN secret.
  • Roll the token when someone who had access to it leaves, or when you think it leaked. Rolling gives the token a new value with the same permissions and invalidates the old value, so update the secret right after.
  • Client IP filtering is not practical with GitHub-hosted runners, because their IP addresses change. Use it only with self-hosted runners that have fixed addresses.

LeftSize itself flags account API tokens that can make changes and have no expiry and no IP restriction (leftsize-cloudflare-api-token-unrestricted). The LeftSize token has read permissions only, so it isn’t flagged.

Verifying the setup

  1. Go to your repository’s Actions tab
  2. Select LeftSize Cloudflare Scan
  3. Click Run workflow
  4. Check that the job summary lists the scan results

Before it scans, the action checks that the token can list zones. If it can’t, the action asks Cloudflare whether the token is active: a user token is checked directly, an account-owned token only for the accounts in cloudflare-account-ids. An active token without Zone Read still scans the account-level rules (Workers, R2, API tokens, Zero Trust and so on); the zone rules are listed as not run. If the token isn’t confirmed active either, the step stops with Cloudflare authentication failed: the API token can't list zones (it needs Zone Read) and isn't confirmed active. For an account-owned token, also set cloudflare-account-ids. Check that:

  • The CLOUDFLARE_API_TOKEN secret holds the token value, not its name or ID
  • The token is active and hasn’t expired
  • The token has Zone Read and includes the zones of the account, or, for an account-level scan only, that cloudflare-account-ids is set when you use an account-owned token

Missing permissions

A missing permission stops only the rules that need it. The rest of the scan runs. Those rules are listed under “Incomplete scan” in the job summary and in the failed-policies output; set LEFTSIZE_DEBUG: true on the scan step to see the error of each (see Troubleshooting).

Some Cloudflare list APIs don’t return an error without permission, but an empty list. This applies to Workers, Access applications, Access service tokens and tunnels. The rule then finds nothing, and the step log shows a note such as:

  • No Workers listed for account <id>. If it has Workers, the API token is missing the Workers Scripts Read permission.
  • No <kind> found in account <id> (if there should be, check the API token's permissions)

If the account does have such resources, add the permission from the table above to the token.

What isn’t scanned

  • Logpush jobs. Cloudflare requires the Logs Write permission for every Logpush API call, including listing jobs. LeftSize stays read-only, so it doesn’t check Logpush.
  • Firewall Rules and the previous rate limiting rules. Cloudflare already converted them to WAF custom rules and rate limiting rules, and their API endpoints no longer work. leftsize-cloudflare-zone-legacy-features checks Auto Minify instead.
  • Page Rules and Registrar domains with an account-owned token; see Page Rules and Registrar.

What LeftSize reads

  • Configuration of zones, Workers, R2 buckets, API tokens, Zero Trust, tunnels, load balancer pools and certificates through the Cloudflare API
  • Worker request counts and Workers AI usage per model (request counts only, never prompts or responses) through Cloudflare’s GraphQL Analytics API
  • Never creates, changes or deletes anything
  • Never reads the data inside your resources (no R2 objects, no KV or D1 contents), Worker secrets, or private keys
  • Some API responses include more than a rule needs: Worker settings include the values of plain-text variables, and certificate details include the certificate (the public part, not the key). The action reads these on the runner, keeps only what the rules need, and never puts the values or certificates in findings, so they aren’t sent to LeftSize. For plain-text variables that look like secrets, only the variable names are sent

Was this article helpful?

© 2026 LeftSize. Cloud cost optimization for GitHub teams.