Fix Newman Cloudflare Access Headers in GitHub Actions
Troubleshoot Newman API tests failing behind Cloudflare Access in GitHub Actions. Learn to pass CF-Access-Client-Id and Client-Secret headers using --env-var or fixed environment.json for secure CI/CD automation.
Newman API Tests Failing with Cloudflare Access Headers in GitHub Actions
I’m running Newman (Postman CLI) API tests in a GitHub Actions workflow that requires Cloudflare Access headers (CF-Access-Client-Id and CF-Access-Client-Secret). The tests fail by hitting the Cloudflare Access page (HTML error), indicating the headers are not being passed correctly, despite the setup below.
Postman Collection Headers
{
"key": "CF-Access-Client-Id",
"value": "{{CF_ACCESS_CLIENT_ID}}"
},
{
"key": "CF-Access-Client-Secret",
"value": "{{CF_ACCESS_CLIENT_SECRET}}"
}
Environment File (newman/environment.json)
{
"key": "CF_ACCESS_CLIENT_ID",
"value": "",
"type": "secret",
"enabled": true
},
{
"key": "CF_ACCESS_CLIENT_SECRET",
"value": "",
"type": "secret",
"enabled": true
}
GitHub Actions Step to Update Environment
- name: Update environment file...
# Python script to inject secrets
<<EOF
cf_access_client_id = os.environ.get('CF_ACCESS_CLIENT_ID', '')
cf_access_client_secret = os.environ.get('CF_ACCESS_CLIENT_SECRET', '')
with open('newman/environment.json', 'r') as f:
env_data = json.load(f)
for item in env_data.get('values', []):
key = item.get('key')
if key == 'CF_ACCESS_CLIENT_ID':
item['value'] = cf_access_client_id
elif key == 'CF_ACCESS_CLIENT_SECRET':
item['value'] = cf_access_client_secret
with open('newman/environment.json', 'w') as f:
json.dump(env_data, f, indent=2)
EOF
env:
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}
Question: How to correctly pass Cloudflare Access Client-Id and Client-Secret headers from GitHub secrets to Newman tests? Why are the headers not being read, causing authentication failure?
In GitHub Actions workflows, Newman tests often fail against Cloudflare Access-protected APIs because the CF-Access-Client-Id and CF-Access-Client-Secret headers aren’t making it into requests—despite secrets being set. The root cause? Your environment JSON lacks the proper Postman structure (it needs a top-level "values" array), and the update script assumes it exists, leaving placeholders empty. Switch to Newman’s --env-var flag for direct injection from GitHub secrets, or fix the JSON update—both bypass file mutations entirely.
Contents
- The Problem with Newman and Cloudflare Access in GitHub Actions
- How Cloudflare Access Service Tokens Work
- Why Your Headers Aren’t Being Read
- Quick Fix: Inject Secrets with --env-var
- Alternative: Fix Environment JSON Updates
- Full GitHub Actions Workflow Example
- Debug Newman Requests and Headers
- Sources
- Conclusion
The Problem with Newman and Cloudflare Access in GitHub Actions
Picture this: your Postman collection runs flawlessly in the app, spitting out perfect CF-Access-Client-Id and CF-Access-Client-Secret headers from variables. But fire it up with Newman in a GitHub Actions CI run? Bam—302 redirect to Cloudflare’s login wall, HTML error page instead of JSON. Tests flop.
Why? Cloudflare Access demands those exact headers on every request to bypass interactive auth for service tokens. Cloudflare’s docs spell it out: generate a Client ID and Secret once, then include them as CF-Access-Client-Id and CF-Access-Client-Secret. Miss them, and you’re treated like a sneaky browser.
In your setup, the headers reference {{CF_ACCESS_CLIENT_ID}} and {{CF_ACCESS_CLIENT_SECRET}}—solid. GitHub secrets feed the Python script via env:. But Newman still chokes. Frustrating, right?
How Cloudflare Access Service Tokens Work
Cloudflare Access isn’t your grandma’s login. It’s Zero Trust: every request gets scrutinized. For bots and CI like GitHub Actions Newman runs, service tokens are the way in—no IdP redirects, just headers.
Create one in your dashboard: snag the Client ID (e.g., abc123.def456.access) and Secret. Slap 'em into requests like this curl:
curl -H "CF-Access-Client-Id: abc123.def456.access" \
-H "CF-Access-Client-Secret: your-super-secret" \
https://your-protected-api.example.com
Cloudflare’s blog nails it: Cloudflare validates these internally. No more 403s or dummy.cloudflareaccess.com bounces. But Newman? It resolves {{vars}} from environments or CLI flags—before sending. If vars are blank, headers vanish.
Your Postman headers are set up right. The disconnect happens downstream in GitHub Actions.
Why Your Headers Aren’t Being Read
Headers ghosting in CI? Common culprits from Postman forums and Cloudflare threads:
-
Malformed environment.json: Yours shows flat objects, but Postman exports look like
{"id": "...", "name": "...", "values": [{"key": "CF_ACCESS_CLIENT_ID", "value": ""}, ...]}. Your script huntsenv_data.get('values', [])—if missing, it skips updates. Empty vars → empty headers. -
Newman ignores GitHub env: Newman CLI doesn’t auto-pull from shell. It needs
-e environment.jsonwith populated values or--env-var. -
Secrets masking/logs: GitHub masks secrets in logs (
***), but they’re there. Still, file writes can glitch on JSON parse errors. -
Auth helpers mismatch: Newman issues report pre-request scripts or auth differing from Postman app. Headers might not generate identically.
-
Proxies/stripping: Rare in Actions, but User-Agent omissions trigger Cloudflare blocks too, per Stack Exchange reports.
Short version: vars stay blank, headers don’t ship.
Quick Fix: Inject Secrets with --env-var
Ditch file fiddling. Newman’s --env-var overrides at runtime—perfect for GitHub secrets. No JSON surgery needed.
In your workflow step:
- name: Run Newman with Cloudflare Access
env:
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}
run: |
newman run your-collection.json \
--env-var CF_ACCESS_CLIENT_ID=$CF_ACCESS_CLIENT_ID \
--env-var CF_ACCESS_CLIENT_SECRET=$CF_ACCESS_CLIENT_SECRET \
--reporters cli,json \
--reporter-json-export results.json
Boom. Variables resolve directly. Postman community and API Handyman examples swear by this. Secrets stay masked; no disk writes.
Even better? Dockerized Newman:
uses: docker://postman/newman:alpine
with:
entrypoint: newman
args: run ... --env-var ...
Handles secrets seamlessly, per GitHub secrets docs.
Alternative: Fix Environment JSON Updates
Prefer files? Patch your script. First, export proper Postman env:
{
"id": "env-123",
"name": "CI Env",
"values": [
{
"key": "CF_ACCESS_CLIENT_ID",
"value": "",
"type": "secret",
"enabled": true
},
{
"key": "CF_ACCESS_CLIENT_SECRET",
"value": "",
"type": "secret",
"enabled": true
}
]
}
Fixed Python (use jq for simplicity—faster, no parse fails):
- name: Update env with secrets
env:
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}
run: |
jq --arg cid "$CF_ACCESS_CLIENT_ID" \
--arg secret "$CF_ACCESS_CLIENT_SECRET" \
'.values |= map(if .key == "CF_ACCESS_CLIENT_ID" then .value = $cid else . end | if .key == "CF_ACCESS_CLIENT_SECRET" then .value = $secret else . end)' \
newman/environment.json > newman/environment-updated.json
mv newman/environment-updated.json newman/environment.json
Then newman run collection.json -e environment.json. Stack Overflow echoes this rewrite pattern.
Mask 'em: echo "::add-mask::$CF_ACCESS_CLIENT_ID".
Full GitHub Actions Workflow Example
Here’s a battle-tested YAML snippet. Drop it in .github/workflows/test.yml.
name: API Tests with Newman
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Newman
run: npm install -g newman newman-reporter-htmlextra
- name: Mask secrets (optional log safety)
env:
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}
run: |
echo "::add-mask::$CF_ACCESS_CLIENT_ID"
echo "::add-mask::$CF_ACCESS_CLIENT_SECRET"
- name: Run Newman
env:
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }}
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}
run: |
newman run postman/collection.json \
--env-var CF_ACCESS_CLIENT_ID="$CF_ACCESS_CLIENT_ID" \
--env-var CF_ACCESS_CLIENT_SECRET="$CF_ACCESS_CLIENT_SECRET" \
--reporter-htmlextra \
--reporter-htmlextra-export report.html \
--reporter-htmlextra-showEnvironmentData
- name: Upload results
uses: actions/upload-artifact@v4
with:
path: report.html
Adapts Newman Action patterns. Scales to CD too.
Debug Newman Requests and Headers
Stuck? Debug systematically.
-
Echo vars pre-run:
echo "ID: $CF_ACCESS_CLIENT_ID"(masked:***—good). -
Newman verbose:
--verbose+--reporter-htmlextra-showEnvironmentDatadumps resolved vars/headers. -
CLI reporter:
--reporters clilogs requests. Spot missingCF-Access-*. -
Curl mirror:
curl -v -H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" ...—check 200 vs 302. -
Logs: GitHub Actions artifacts for HTML reports. Cloudflare curl examples help validate.
If redirects persist, nuke/recreate service token—secrets expire or scope mismatch.
Pro tip: Local Newman with .env mimics CI perfectly.
Sources
- How to pass Github secrets to newman run with collections and environments? - Postman Community
- How to pass Github secrets to newman run with collections and environments? - Stack Overflow
- Service tokens · Cloudflare One docs
- Give your automated services credentials with Access service tokens - Cloudflare Blog
- Using secrets in GitHub Actions - GitHub Docs
- Newman not using authentication helpers? · Issue #178 · postmanlabs/newman
- Automate all the things with Github actions, Postman and APIs - API Handyman
- CF-Access-Client-Id not even recognized - Cloudflare Community
- Curl Access with Service Token - Cloudflare Community
- Newman Action - GitHub Marketplace
- Cloudflare blocking requests to StackExchange API when User-Agent header is not specified - Meta Stack Exchange
Conclusion
GitHub Actions Newman woes with Cloudflare Access boil down to missing CF-Access-Client-Id/Client-Secret headers—fixed fastest with --env-var direct from secrets. Your JSON script nearly worked; just wrap values in "values": [...]. Test locally, mask secrets, debug with reporters. Now your CI flies past Zero Trust gates. Scale it: add matrix jobs for envs, upload JUnit for badges. Questions? Tweak the workflow above.