skip to content

A script that POSTs to a Jenkins controller's REST API is rejected with HTTP 403 and "No valid crumb was included in the request". What is Jenkins' crumb, and how should the script satisfy it?

level: middleimportance: nice to knowfreq 38%

answer

  1. CSRF token, per user and session
  2. fetch from the crumb issuer endpoint
  3. reuse the same session cookie
  4. API token authentication is exempt
  5. never the answer: turn it off

basics

~20 s

The crumb is Jenkins' CSRF token: state-changing requests must carry a value fetched from the controller's crumb issuer, sent in a request header on the same session. Scripts should either fetch the crumb per session or authenticate with a user API token, which is exempt in current Jenkins versions.

solid answer

~50 s

Jenkins protects against cross-site request forgery by requiring a **crumb** on every state-changing request. The crumb is derived per user and session, so a third-party page cannot forge one even though the victim's browser would happily attach their Jenkins cookie. A script fetches it from `/crumbIssuer/api/json`, which returns both the value and the field name to use — normally the `Jenkins-Crumb` header — and must send it on the POST **using the same session**, so keep the cookie jar between the two calls. In current Jenkins versions, requests authenticated with a **user API token** over HTTP Basic auth are exempt from the crumb requirement, which is why token-based automation usually never sees this error; that is the cleaner path for a script. What you should not do is turn CSRF protection off — in modern releases it is on by default and no longer switchable from the UI, precisely because the attack it blocks lets a lured administrator's browser trigger builds or reach the script console.

go deeper

for a junior

Know that Jenkins requires a CSRF crumb on state-changing requests, that you fetch it from the crumb issuer endpoint, and that turning the protection off is not the fix.

for a middle

Explain that the crumb is bound to the user's session so the fetch and the POST must share cookies, read the field name from the issuer response, and know that API-token auth is exempt in current versions.

for a senior

Show why the exemption is sound — CSRF exploits ambient cookies, not deliberately supplied tokens — and insist that automation gets its own least-privileged account rather than a person's token.

for a principal

Own the controller-hardening baseline this belongs to: real authentication, a real authorization strategy, CSRF on, and a policy on which integrations may hold API tokens against your instance at all.

## The attack being blocked Jenkins authenticates browsers with a session cookie. Without CSRF protection, any web page an authenticated user visits could contain a form or a fetch that POSTs to `https://jenkins.example.com/job/deploy/build`, and the browser would attach the session cookie automatically. The request would run **as that user**. On a Jenkins controller the consequences are unusually severe, because an administrator's session can reach the script console, create jobs, or read configuration. The defence is a token the attacker's page cannot know: a **crumb**, derived from the user's identity and session, that must accompany any request that changes state. ## How a client obtains one The crumb issuer exposes it over the API: ```bash JENKINS=https://jenkins.example.com CRUMB=$(curl -s -c cookies.txt -u "$USER:$API_TOKEN" \ "$JENKINS/crumbIssuer/api/json" | jq -r .crumb) curl -s -b cookies.txt -u "$USER:$API_TOKEN" \ -H "Jenkins-Crumb: $CRUMB" \ -X POST "$JENKINS/job/deploy/build" ``` Two details cause most of the failures people hit: - **The response tells you the field name.** The JSON contains both `crumb` and `crumbRequestField`; read the field name rather than hardcoding it, since it has not always been `Jenkins-Crumb`. - **The crumb is bound to the session.** Fetching it on one connection and posting on a fresh one — no shared cookie jar — yields a crumb the controller will not accept. `-c`/`-b`, or a session object in your HTTP client, is not optional. ## The simpler route for automation In current Jenkins versions, a request authenticated with a **user API token** in HTTP Basic auth does not require a crumb. The reasoning is that CSRF exploits ambient credentials — cookies the browser attaches on its own — while an API token has to be supplied deliberately by the caller, so an attacker's page cannot cause one to be sent. Generate a token from the user's configuration page, use `-u user:token`, and the crumb dance disappears. This is also the right answer for a second reason: it keeps a human password out of scripts, and a token can be revoked individually without disrupting the person's login. Give automation its **own** Jenkins user with only the permissions it needs, rather than an engineer's token. ## What not to do "Just disable CSRF protection" is the failing answer. It has been on by default for years and modern releases removed the UI toggle; any advice to turn it off is out of date. The same applies to broad workarounds such as a proxy that injects a crumb for everyone. A related genuine problem is a **reverse proxy that alters the request**. Historically the crumb calculation incorporated the client address, which broke behind proxies that changed it; the modern issuer avoids that, but a proxy that strips custom headers will remove `Jenkins-Crumb` and produce the same 403. When a crumb-carrying request fails, check on the controller whether the header arrived at all before blaming the algorithm. ## Where this sits in instance hardening CSRF protection assumes the identity behind the session is meaningful, which means it only helps once you have real authentication and a real authorization strategy. It is one of three settings people check on a controller review: the security realm is not "anyone can do anything", the authorization strategy is matrix or role-based rather than "logged-in users can do anything", and CSRF protection is on. On its own it prevents a specific browser-mediated attack; it says nothing about who may configure jobs or bind credentials.

  • Why is a request authenticated with a Jenkins API token exempt from the crumb requirement?
    CSRF works by exploiting ambient credentials — a cookie the browser attaches automatically to a request the user never intended. An API token in an Authorization header is not ambient: the caller must supply it deliberately, and a third-party page cannot make the victim's browser add it. With no way to forge the authentication, there is nothing left for the crumb to defend against.
  • A script fetches a crumb successfully but the POST still returns 403. What is the most likely cause?
    The crumb was not used within the same session. It is bound to the user's session, so the fetch and the POST must share a cookie jar; a fresh connection presents a crumb the controller rejects. The other common cause is a reverse proxy stripping the custom header, which you confirm by checking whether the header reached the controller at all.
  • Should automation use an engineer's API token?
    No. Give the automation its own Jenkins account with only the permissions it needs, and issue a token for that account. It can then be revoked or rotated without disturbing a person's access, its actions are attributable to the system rather than an individual, and its permission set can be far narrower than any engineer's.

saying these in an interview costs you the question

  • Disabling CSRF protection to make the script work
  • Hardcoding the header name instead of reading crumbRequestField
  • Fetching the crumb without reusing the session cookie
  • Believing the crumb authenticates the caller
  • Sharing one engineer's API token across every automation

context