mediumIntermediateSymptom-first#crumb#csrf#api#403

Jenkins API calls fail - CSRF crumb missing or expired

POST to Jenkins API returns 403 with 'No valid crumb was included in the request'

Likely source:configurationsecurity

Focused incident mode — strips away everything but this runbook.

Symptoms

  • POST to Jenkins API returns 403 with 'No valid crumb was included in the request'
  • curl-based Jenkins API calls fail with CSRF validation error
  • Jenkins CLI commands fail with '403 Forbidden' on POST requests

Always fetch a fresh crumb immediately before making a POST request. Stale crumbs expire silently and cause 403 errors that are hard to diagnose.

Evidence to grab

Collect these artifacts before you change anything — they prove (or rule out) each cause.

  • Jenkins CSRF configuration

    curl -s -u <USER>:<TOKEN> '<JENKINS_URL>/manage' | grep -i 'crumb'
  • Crumb issuer endpoint

    curl -s -u <USER>:<TOKEN> '<JENKINS_URL>/crumbIssuer/api/json'

Diagnostic flow

Run the steps in order, marking each claim confirmed or refuted. The evidence you gather feeds the ranked causes below.

Guided decision tree is ready.

Steps with commands, expected output, and pass/fail branches.

Ranked root causes

Evidence checklist

  • POST request does not include the Jenkins-Crumb header
  • Jenkins CSRF protection is enabled (default since Jenkins 2.0)

Confirm before fixing

curl -s -u <USER>:<TOKEN> '<JENKINS_URL>/crumbIssuer/api/json'
READ

Fetch the crumb

Fix

CRUMB=$(curl -s -u <USER>:<TOKEN> '<JENKINS_URL>/crumbIssuer/api/json' | python3 -c 'import sys,json; print(json.load(sys.stdin)["crumb"])') && curl -s -u <USER>:<TOKEN> -H 'Jenkins-Crumb: '$CRUMB -X POST '<JENKINS_URL>/job/<JOB>/build'
CHANGE

Include the crumb in the request header

Resolution runbook

Apply from top to bottom after the cause is confirmed. Review every command before running it — risk tags are shown on each.

1
curl -s -u <USER>:<TOKEN> '<JENKINS_URL>/crumbIssuer/api/json'
READ

Fetch a fresh crumb

2
CRUMB=$(curl -s -u <USER>:<TOKEN> '<JENKINS_URL>/crumbIssuer/api/json' | python3 -c 'import sys,json; print(json.load(sys.stdin)["crumb"])')
READ

Extract the crumb value

3
curl -s -u <USER>:<TOKEN> -H 'Jenkins-Crumb:'$CRUMB -X POST '<JENKINS_URL>/job/<JOB>/build'
CHANGE

Trigger a build with the crumb

Prevention

  • Include the crumb fetch in a single shell command chain: CRUMB=$(curl ... | jq -r .crumb) && curl -H 'Jenkins-Crumb:'$CRUMB ...
  • Use Jenkins API tokens instead of passwords for scripted API access.
  • Document the crumb requirement in your Jenkins API usage guides.
  • Consider using the Jenkins CLI for complex API operations - it handles crumb fetching automatically.

Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.