The Jenkins HTML report with no CSS
You installed the HTML Publisher plugin, pointed it at
htmlcov/index.html, clicked the link on the build page — and got the raw
document. No stylesheet, no charts, no tabs. Just headings and links stacked
down the page like it's 1994.
Nothing is broken. Jenkins serves artifact files through
DirectoryBrowserSupport, and that path sets a deliberately strict
Content-Security-Policy — the default blocks inline styles, external
stylesheets and all JavaScript. It exists because build artifacts are
attacker-writable in a lot of pipelines, and Jenkins serving scripted HTML
from its own origin is a session-stealing primitive.
The sanctioned fix, and its actual price
The property is hudson.model.DirectoryBrowserSupport.CSP. Runtime version,
from Manage Jenkins → Script Console:
System.setProperty(
"hudson.model.DirectoryBrowserSupport.CSP",
"sandbox allow-scripts; default-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline';"
)
Persistent version, as a JVM argument in your service definition or container spec:
-Dhudson.model.DirectoryBrowserSupport.CSP="sandbox allow-scripts; default-src 'none'; img-src 'self' data:; style-src 'self' 'unsafe-inline';"
Two things people learn the hard way:
- The Script Console version doesn't survive a restart. It's an in-memory system property. If the fix isn't in the JVM args, the reports go unstyled again the next time the controller cycles — usually at the worst moment.
- It's controller-wide. There is no per-job scope. Loosening it for your
trusted coverage report also loosens it for every other job's artifacts,
including any pipeline that builds a fork, a dependency bump, or third-party
code. Plenty of teams set it to
""(fully disabled) and never revisit it.
If your Jenkins only ever builds first-party code and every contributor is trusted, the narrow policy above is a reasonable trade. Just make it a deployment-config change, not a console click, and write down why.
The option that doesn't touch Jenkins security
Serve the report from somewhere that isn't the Jenkins origin. Then the CSP that protects your Jenkins session is irrelevant, because your report isn't running inside it.
Store a scoped token (reports:write only) as a Jenkins
credential and publish from the pipeline:
pipeline {
agent any
stages {
stage('Test') {
steps { sh 'pytest --cov --cov-report=html' }
}
}
post {
always {
withCredentials([string(credentialsId: 'comma-api-token', variable: 'COMMA_API_TOKEN')]) {
sh '''
curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$COMMA_REPORT_ID" \
-H "Authorization: Bearer $COMMA_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile html htmlcov/index.html \
--arg title "Coverage — build ${BUILD_NUMBER}" \
'{title: $title, html: $html}')"
'''
}
echo "Report → https://commareports.com/p/${COMMA_REPORT_ID}"
}
}
}
post { always { … } } is the Jenkins equivalent of publishing on red builds
too — which are the ones people actually want to read.
Comma renders the HTML in a sandboxed frame in its own origin and applies its own sanitizing on write, so styling survives and your Jenkins session never enters the picture.
What changes besides the CSS
- The link outlives the build. Jenkins build retention eventually discards
old builds and their archived artifacts.
PATCHing one report id appends a revision per build at a URL that doesn't rotate — and any two revisions can be diffed. - Readers don't need a Jenkins account. This is usually the real constraint: the people who should read the report (PM, security reviewer, customer, auditor) are exactly the people you don't want to give a Jenkins login. Comma reports use identity-based visibility — private, team, domain-gated, or a link.
- Feedback lands on the report. Anchored comment threads on the exact row or number being questioned, instead of a Slack screenshot of an unstyled table. See commenting on HTML.
- Builds can announce themselves. A webhook on
revision.createdposts the new revision to Slack or Discord.
Caveats, stated plainly
- HTML body cap is 5 MB; images, videos and archives attach as assets at 25 MB per file.
- Scripts are stripped on write. A report that is a JavaScript application — Allure, Playwright's HTML reporter — should be published as a static digest with the full archive attached. Same trade Jenkins forces on you by default, minus the unstyled part.
- Keep HTML Publisher installed if you also want the in-Jenkins link. The two coexist; one is for the build page, one is for the humans.
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision
history. Add the post block above and stop debating the CSP property in
your infra channel.
Related
- Publish from CI — the general pipeline pattern
- GitHub Actions HTML reports
- GitLab CI HTML reports
- Share a pytest report