See [JENKINS-76263](https://issues.jenkins.io/browse/JENKINS-76263).
This PR …implements the `Content-Security-Policy` header in core. Previously, users had to install the [Content Security Policy Plugin](https://plugins.jenkins.io/csp/) to get this protection.
This feature is **opt-in** for now, given it is a major change and _will_ break some plugins. Their compatibility is tracked in [JENKINS-60865](https://issues.jenkins.io/browse/JENKINS-60865). Per [this tracking spreadsheet](https://docs.google.com/spreadsheets/d/1nSx4gQ2YUl-sKdY5u6biXmyDnkQ26wTaMBLSR8gr_-A/edit?gid=0#gid=0), all popular maintained plugins are expected to be compatible. Some plugins (those with "avatar" icons) are being adapted to be compatible using the new APIs introduced in this PR, see below for details.
Additionally, this removes the `X-Webkit-CSP` and `X-Content-Security-Policy` headers from `DirectoryBrowserSupport`. Those have been obsolete since about 2013, so even backward compatibility is no longer really a relevant concern.
Closely related PRs:
* User and developer documentation updates: https://github.com/jenkins-infra/jenkins.io/pull/8529
* Rewrite/adaptation of CSP plugin: https://github.com/jenkinsci/csp-plugin/pull/63
* Avatar compatibility (using the new API to prevent broken folder or user icons):
- https://github.com/jenkinsci/github-branch-source-plugin/pull/917
- https://github.com/jenkinsci/oic-auth-plugin/pull/681
- https://github.com/jenkinsci/keycloak-plugin/pull/27
- https://github.com/jenkinsci/gravatar-plugin/pull/166
* Adapt `CspRule` in ATH: https://github.com/jenkinsci/acceptance-test-harness/pull/2269
Loosely related PRs:
* Make it easier to identify the source of some `script` tag violations: https://github.com/jenkinsci/stapler/pull/612
## Open PR Tasks
Optionally fine-tune wording on the `CspRecommendation/index` page, but for a weekly it seems fine as is IMO.
## Overview
### Deciding which header to set
<details>
There are four basic configurations/states controlling which header (CSP or CSP-Report-Only) is set. These are checked in order. See `CspHeaderDecider` for details.
1. The system property `jenkins.security.csp.CspHeader.headerName` is set to either of the two values. This hides/disables the configuration UI and sets the configured header.
<img width="2108" height="281" alt="Screenshot 2025-11-10 at 18 48 15" src="https://github.com/user-attachments/assets/ec30cf68-bc7e-45a6-880f-68683c8713ef" />
(ignore the outdated header name on the screenshot)
---
2. `Main#isUnitTest` or `Main#isDevelopment` are true. This works similarly to the previous one, just a different message on the placeholder UI, and will always set `CSP`. Disable by setting `jenkins.security.csp.impl.DevelopmentHeaderDecider.DISABLED` to `true`. The idea here is that developers should be the first to encounter compatibility problems, ideally fixing them rather than ignoring them / disabling this behavior.
<img width="2093" height="267" alt="Screenshot 2025-11-10 at 18 48 49" src="https://github.com/user-attachments/assets/629512ef-f93e-408a-8832-5a15cc2e5a3a" />
---
3. The regular configuration, which either sets the `CSP` header (checked, "enforcing") or `CSP-Report-Only` (unchecked), if configuration is enabled.
<img width="3134" height="257" alt="Screenshot 2025-11-10 at 18 53 55" src="https://github.com/user-attachments/assets/5d8d85b1-ef48-45ab-8e82-f5e9070520a2" />
---
4. Lastly, the "fallback" is to set `CSP-Report-Only` and to show a placeholder UI that recommends enabling CSP configuration. This is what most users of Jenkins see first (besides the admin monitor).
<img width="2124" height="228" alt="Screenshot 2025-11-10 at 18 50 04" src="https://github.com/user-attachments/assets/dd055881-b06c-409f-b1c6-03e60c5b9e54" />
---
There are two different terms here: One is that _configuration is enabled_, the other that it is _enforcing CSP_. The latter just means we set the CSP header, not CSP-Report-Only. The former means that an admin chose to configure this. This two step process is needed to support the admin monitor showing while an admin has not made a choice yet; otherwise "blindly" submitting _Manage Jenkins » Security_ would look like the admin opting out of protection.
Now the usual journey to enabling this option is as follows:
1. Admin sees admin monitor or the placeholder text from fallback on `/configureSecurity/`
<img width="2488" height="166" alt="Screenshot 2025-11-10 at 18 50 12" src="https://github.com/user-attachments/assets/70b1d9d9-eb25-4d83-9dc1-37a632c22666" />
<img width="2124" height="228" alt="Screenshot 2025-11-10 at 18 50 04" src="https://github.com/user-attachments/assets/55407abb-04b4-4a2c-ac86-834ebca31b18" />
---
2. They choose the button/link that takes them to the `CspRecommendation/index.jelly` view that explains the functionality and caveats.
<img width="2061" height="539" alt="Screenshot 2025-11-10 at 18 50 39" src="https://github.com/user-attachments/assets/2102fea6-2f7d-49ca-b47b-9a6917287823" />
---
3. They click _Set up Content-Security-Policy_ which enables the configuration by setting it to disabled (but not yet saving it), and taking them to `/configureSecurity/`.
<img width="2089" height="315" alt="Screenshot 2025-11-10 at 18 50 45" src="https://github.com/user-attachments/assets/93874317-e843-4d96-82d0-5bb94149b972" />
---
4. Special form validation tells them that just saving will leave it not enforced, they need to check the box themselves. That may also recommend to set up Resource Root URL it it's not currently configured.
<img width="2100" height="315" alt="Screenshot 2025-11-10 at 18 51 08" src="https://github.com/user-attachments/assets/b5ec133f-56df-4289-9b1c-c228a2232133" />
---
5. Once they check the box and save the form, it's now permanently enforced.
At each step, there's a way to opt out:
* They can choose to "Dismiss" the admin monitor or ignore the placeholder text on `/configureSecurity/`
* They can click "Cancel" on the separate page explaining caveats.
* They can leave protection disabled (explicitly), which will basically disable the admin monitor but otherwise behave as before.
* They can just not submit the form before restarting Jenkins, which resets it to the default state. `/configureSecurity/` submission is needed to persist the state from "enabling" the configuration.
</details>
## Interaction with Resource Root URL
Unfortunately, serving user-generated resource files from the same origin is a problem. `script-src 'self'` is satisfied by `/job/whatever/1/artifacts/lol.js`. So users who want good protection definitely need to set up Resource Root URL, which is why this is called out in the form validation, and on https://github.com/jenkins-infra/jenkins.io/pull/8529/files#diff-2fdabed902fd1d2d7e1ee4cafa034e3c3733979b50b0cd949bc1f4d1589b84a1R42-R44.
## CasC support
See https://github.com/daniel-beck/csp-plugin/blob/v2/README.adoc#configuration-as-code for what it looks like with the CSP plugin installed (otherwise there's just `enforce`).
## `DirectoryBrowserSupport` and Resource Root URL
The header is not being set for RRURL requests, same as CSP plugin 1.x.
Otherwise, DBS's more restrictive (e.g., `sandbox`) CSP takes precedence, and if disabled through `hudson.model.DirectoryBrowserSupport.CSP` system property will also remove the regular header. See `ContentSecurityPolicyTest` for where all of this is being tested.
## Known potential problems
Besides the previously known problems of inline scripts etc. that need code restructuring to make work, the following more general features are inherently affected:
* The extension point `UserAvatarContributor` is likely to result in broken image references unless the basic implementation in this PR takes care of everything.
* Similarly, `AvatarMetadataAction` in `scm-api` defines a similar feature for org folders, and either that will need to be adapted generally, or implementations need to add support for these new core APIs.
These plugins need to implement `Contributor`, or call `AvatarContributor#allow` (or locally cache avatar images). This is newly being documented by https://github.com/jenkins-infra/jenkins.io/pull/8529.
Current status of plugins known to implement these extension points:
### `UserAvatarResolver`
🚧 https://plugins.jenkins.io/oic-auth/ - https://github.com/jenkinsci/oic-auth-plugin/pull/681
✅ https://plugins.jenkins.io/azure-ad/ - [caches avatar images locally](https://github.com/jenkinsci/azure-ad-plugin/blob/0ac48a9179b34b86d48bd0e45e20766cdeca7c1f/src/main/java/com/microsoft/jenkins/azuread/avatar/EntraAvatarProperty.java#L34-L36)
🚧 https://plugins.jenkins.io/keycloak/ - https://github.com/jenkinsci/keycloak-plugin/pull/27
🚧 https://plugins.jenkins.io/gravatar/ - https://github.com/jenkinsci/gravatar-plugin/issues/166
✅ https://plugins.jenkins.io/avatar/ - [only renders locally uploaded files](https://github.com/jenkinsci/avatar-plugin/blob/2e722a1e67ea55155cd2ea1e57d8bd1ae284c26a/src/main/java/net/hurstfrost/jenkins/avatar/user/AvatarProperty.java#L49-L5)
### `AvatarMetadataAction` (`scm-api`)
🚧 https://plugins.jenkins.io/github-branch-source/ - https://github.com/jenkinsci/github-branch-source-plugin/pull/917
✅ https://plugins.jenkins.io/cloudbees-bitbucket-branch-source/ - appears to only render locally cached avatars
✅ https://plugins.jenkins.io/gitlab-branch-source/ - appears to only render locally cached avatars
✅ https://plugins.jenkins.io/gitea/ - appears to only render locally cached avatars
✅ https://plugins.jenkins.io/gerrit-code-review/ - renders bundled avatar icons
❓ https://plugins.jenkins.io/tuleap-git-branch-source/ - looks incomplete: [this](https://github.com/jenkinsci/tuleap-git-branch-source-plugin/blob/3e97d54b114f01adbe2aff31c11b1083204cc94f/src/main/java/org/jenkinsci/plugins/tuleap_git_branch_source/TuleapProjectMetadataAction.java) does not implement any of [that](https://github.com/jenkinsci/scm-api-plugin/blob/master/src/main/java/jenkins/scm/api/metadata/AvatarMetadataAction.java).
## Testing done
Autotests, and some manual tests:
* Regular `DirectoryBrowserSupport` has custom CSP header if enforced
* Resource Root URL has no CSP header
## Delivery plan
<details>
### Step 1 – Opt in core feature (~Nov 2025)
**Gate: [Core PR](https://github.com/jenkinsci/jenkins/pull/11269) done, [CSP plugin PR](https://github.com/jenkinsci/csp-plugin/pull/63) (for customization) done.**
Disabled by default even for new installations.
An admin monitor encourages admins to enable it (extra visibility). Can be disabled through the UI (for all users).
During development and testing, it’s always enabled to give an early warning to developers.
Planned for a weekly soon, so that it’s in the next LTS.
Set up new issue tracking (labels, Epic, separate reporting GH repo) perhaps to understand compatibility?
### Step 2 – Advertise to developers (~Nov 2025)
**Gate: Core PR is in weekly.**
Encourage plugin maintainers to adapt their plugins. Doesn’t need new core dependency, can try with CSP plugin v1.
Maybe also publish a blog post to advertise further, including the plan?
### Step 3 – Advertise to LTS users (late Jan 2026)
**Gate: Core PR is in LTS.**
Mention it in some detail in the upgrade guide.
Perhaps a blog post?
### Step 4 – Opt out core feature (Q2/2026, maybe?)
**Gate: Plugins are sufficiently adapted (TBD threshold etc.)**
Switch the core feature to opt out (via UI).
At this point we’re basically “done” – removing the UI option to opt out can be done whenever we want.
</details>
### Proposed changelog entries
- Allow configuring Content-Security-Policy protection for the Jenkins UI. Introduce an API for plugins to relax (or further tighten) the rules around various resources. If you have Content Security Policy Plugin (`csp`) installed, update it to version 2.x.
### Proposed changelog category
/label major-rfe,developer
### Proposed upgrade guidelines
#### Content Security Policy support in Jenkins core
Jenkins now allows enforcing Content Security Policy. This is a security mechanism that can reduce or eliminate the impact of web security vulnerabilities like cross-site-scripting (XSS). Jenkins core and most popular Jenkins plugins are compatible with Jenkins's default rule set, but for backwards compatibility, CSP enforcement is disabled by default.
See [the Content Security Policy documentation](https://www.jenkins.io/doc/book/security/csp/) for more information.
Users of [Content Security Policy Plugin](https://plugins.jenkins.io/csp/) are should disable it or update it to version 2.x.
### Submitter checklist
- [x] The Jira issue, if it exists, is well-described.
- [x] The changelog entries and upgrade guidelines are appropriate for the audience affected by the change (users or developers, depending on the change) and are in the imperative mood (see [examples](https://github.com/jenkins-infra/jenkins.io/blob/master/content/_data/changelogs/weekly.yml)). Fill in the **Proposed upgrade guidelines** section only if there are breaking changes or changes that may require extra steps from users during upgrade.
- [x] There is automated testing or an explanation as to why this change has no tests.
- [x] New public classes, fields, and methods are annotated with `@Restricted` or have `@since TODO` Javadocs, as appropriate.
- [x] New deprecations are annotated with `@Deprecated(since = "TODO")` or `@Deprecated(forRemoval = true, since = "TODO")`, if applicable.
- [x] New or substantially changed JavaScript is not defined inline and does not call `eval` to ease future introduction of Content Security Policy (CSP) directives (see [documentation](https://www.jenkins.io/doc/developer/security/csp/)).
- [x] For dependency updates, there are links to external changelogs and, if possible, full differentials.
- [x] For new APIs and extension points, there is a link to at least one consumer.
### Desired reviewers
@mention
Before the changes are marked as `ready-for-merge`:
### Maintainer checklist
- [ ] There are at least two (2) approvals for the pull request and no outstanding requests for change.
- [ ] Conversations in the pull request are over, or it is explicit that a reviewer is not blocking the change.
- [ ] Changelog entries in the pull request title and/or **Proposed changelog entries** are accurate, human-readable, and in the imperative mood.
- [ ] Proper changelog labels are set so that the changelog can be generated automatically.
- [ ] If the change needs additional upgrade steps from users, the `upgrade-guide-needed` label is set and there is a **Proposed upgrade guidelines** section in the pull request title (see [example](https://github.com/jenkinsci/jenkins/pull/4387)).
- [ ] If it would make sense to backport the change to LTS, a Jira issue must exist, be a _Bug_ or _Improvement_, and be labeled as `lts-candidate` to be considered (see [query](https://issues.jenkins.io/issues/?filter=12146)).