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:

  1. 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.
  2. 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.created posts 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.

Create your first report →

Related