Do not open a public GitHub issue for security problems.
- For plugins hosted under the Jenkins project, report via the Jenkins security process:
https://www.jenkins.io/security/ (email
jenkinsci-cert@googlegroups.com). - Otherwise, email the maintainer: Darniss
<darniss.mail@gmail.com>.
Please include a description, affected versions, reproduction steps, and impact. We aim to acknowledge within 5 business days.
The plugin is a permission-checked surface over paused pipelines. Its guarantees:
- Permissions split answer and abort. Answering a question requires
Item/Buildon the source job — or, when asubmitterFilteris set, membership in that user/group set (with the standardOverall/Administerbypass). Aborting a question aborts the run and requiresItem/Cancel(or submitter membership when a filter is set).Item/Buildalone is not enough to abort. Viewing requiresItem/Read. Run-tabdoIndexredirects also callgetRun().checkPermission(Item.READ)before sending the 302. - The REST root is an
UnprotectedRootActiononly so the/healthprobe is reachable; every other endpoint performs its own explicit permission check. GET /questions/{id}returns 404 whether the question is missing or the caller lacksItem/Read— no existence/enumeration leak.- The global list (
GET /questions?all=true) requiresOverall/Administer; the default list is scoped to what the caller may answer.
- All mutating endpoints (
answer,abort,preview, view comments/edit/decision) are@RequirePOST, so Jenkins' crumb filter (CrumbFilter) is enforced at the framework level. The bell fetches a crumb fromcrumbIssuerand attaches it to every write. Read-only endpoints (questions, views, health, run-tab redirects) are@GET— Stapler-enforced GET-only, no CSRF token required.
- Server-side sanitisation.
contextMarkdownand free-text are rendered bycommonmarkconfigured withescapeHtml(true)+sanitizeUrls(true)+percentEncodeUrls(true). Raw HTML is escaped (e.g.<script>→<script>), andjavascript:/other unsafe URL schemes are stripped. Verified live: a<script>payload round-trips as inert text. - Client-side discipline. In
bell.js, all untrusted fields (prompt, choicelabel/why,jobFullName) are inserted viatextContent. Only server-sanitised HTML (contextHtmland the/previewresponse) is inserted viainnerHTML. - Rendered HTML review documents (isolated, never inlined). An
interactiveViewHTML snapshot is pipeline-generated and therefore untrusted, so it is never inserted into a Jenkins page. It is served byGET /views/{id}/renderedastext/htmlunderContent-Security-Policy: sandbox allow-scripts; base-uri 'none'; form-action 'none'; frame-ancestors 'self'and displayed in an<iframe sandbox="allow-scripts">. Withholdingallow-same-originis the boundary: the document gets a unique opaque origin, so its scripts may run (a self-contained report such as a Robot Frameworklog.htmlis entirely script-driven and shows nothing without them) but cannot read the embedding page, the session cookie,localStorage, or a CSRF crumb — Jenkins sends no CORS headers, so a cross-origin read is refused.allow-forms,allow-popupsandallow-top-navigationare withheld too. Sending the policy as a header (not only as the frame attribute) means opening the URL directly is equally contained. The endpoint isItem.READ-gated (404 no-leak), refuses any non-HTML document, and is disabled entirely by thehtmlRenderingfeature flag. Residual, accepted risk ofallow-scripts: outbound requests and misleading content inside the frame; the content originates from the operator's own pipeline, and an operator who declines that trade turns the flag off. - Sanitisation is not an option for these files. An allowlist sanitiser strips
<script>, which for a generated report removes all of its content (a Robotlog.htmldegrades to its "JavaScript disabled" error). Isolation, not sanitisation, is therefore the control for HTML documents; markdown continues to be sanitised and inlined as before.
- Answers must reference a declared choice id (or the
__deny__sentinel); free text is only accepted when the question setallowFreeText: true. Invalid →400, unauthorised →403, already-settled →409.
- Questions persist to
$JENKINS_HOME/interactive-input/questions.xmlviaXmlFile/XStream (same mechanism Jenkins uses for its own config), inside$JENKINS_HOME(not web-served). - The store is a
ConcurrentHashMap; each question's transitions are serialised viasynchronized(question), so concurrent answer/abort/expire cannot double-settle.
- The bell polls at a configurable cadence with a hard floor of 5s to protect the controller.
- The
inputStepBridgescan is bounded (newestMAX_BUILDS_PER_JOBbuilds, gated onisBuildingand presence of anInputAction) and only runs when explicitly enabled.
-
Every capability is a feature flag; the bridge and dashboard tile are off by default. Operators enable only what they need, via UI or JCasC.
-
Outbound notify-only channels (email / Teams / Slack) send job metadata and a Jenkins URL. Webhook URLs live in Secret-text credentials (never on the job XML). Email uses Mailer SMTP. Dispatch is off-thread (
jenkins.util.Timer); credential dropdowns checkItem/Configure. Payload is metadata + link only (no review file, no question context markdown). Recipients are an operator-configured list (same model as the Mailer publisher). There is no inbound answering from Slack/Teams/email.
- Answering parameterised native inputs in-modal (they deep-link to the native form).
- Push notifications (SSE/WebSocket) — polling only.
- Answering a question or review from Slack, Teams, or email.