You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Fixable CVEs in stirlingtools/stirling-pdf (0 critical, 14 high) #1004
The weekly CVE scan found fixable HIGH or CRITICAL vulnerabilities in stirlingtools/stirling-pdf. This body is rewritten by every scan, and the issue closes itself when a scan finds the pinned digest clean.
The fix is normally the Dependabot bump for this image. If upstream has not released one, the fixed versions below say what to wait for.
Triage, 2026-10-10: waiting on upstream. Nothing to change here, and the next bump won't fix this.
A newer release exists, but it doesn't clear these findings. Stirling-PDF 3.1.0 came out on 2026-10-05, after Dependabot's last run, so its PR should open on Sunday. I scanned stirlingtools/stirling-pdf:3.1.0@sha256:b5b9e400… (the current :latest) with the same trivy invocation scripts/scan-images.sh uses (aquasec/trivy:0.75.0, --severity HIGH,CRITICAL --ignore-unfixed). It reports the same 14 HIGH findings as the pinned 3.0.2: jackson-core/databind 2.21.5 and 3.1.5, jsoup 1.15.3/1.15.4, and Ubuntu's openssl 3.0.13-0ubuntu3.12. Merging that PR won't close this issue. Review it as a feature release on its own merits (it adds Stirling Office Convert, beta).
Exposure while we wait: Stirling has no published port and is reachable only through Caddy, by the household. It runs as uid 1001 with no capabilities and a read-only root filesystem. Every document lives in a memory-backed /tmp, and LibreOffice runs under Landlock and seccomp (STIRLING_LO_SANDBOX=required). The findings are in parsers (Jackson's JSON and jsoup's HTML), so the input that could reach them is what a household member uploads. With 0 critical findings, we're not building and patching our own image. We take upstream's release instead.
This is waiting on upstream. Nothing in the repo needs to change.
The pinned digest is current: stirlingtools/stirling-pdf:3.0.2 (sha256:c1959253…). pin-digests.sh reports no drift, so the tag hasn't been rebuilt since it was pinned.
Docker Hub has 3.1.0, but I scanned it with trivy 0.75.0 and the same flags: the same 14 HIGH.
The fixed versions exist (table above), but no published image includes them yet. Mostly:
com.fasterxml.jackson.core:jackson-databind: 3 findings, fixed in 3 different versions (see the table)
tools.jackson.core:jackson-databind: 3 findings, fixed in 2 different versions (see the table)
com.fasterxml.jackson.core:jackson-core: 2 findings, fixed in 2 different versions (see the table)
org.jsoup:jsoup: 2 findings, fixed in 1.23.2
tools.jackson.core:jackson-core: 2 findings, fixed in 2 different versions (see the table)
libssl3t64: 1 finding, fixed in 3.0.13-0ubuntu3.16
and 1 more across 1 other packages
When upstream rebuilds or ships a release with these fixes, Dependabot should open the bump. Check it with trivy and check_image_pins.py, then deploy. The next scan will then close this issue.
Rechecked 2026-10-10. Upstream hasn't moved: 3.1.0 is still the latest release (digest sha256:b5b9e400…, unchanged), and Stirling-Tools/Stirling-PDF#8252 is still open. Dependabot's #1138 (3.0.2 → 3.1.0) is open with CI green. As triaged above, it doesn't change this issue's findings.
jsoup was the one finding without a known upstream owner. It has one now. Scanning 3.1.0 with our pinned trivy gives its two copies:
Copy
Path in image
Comes from
1.15.3
inside app/lib/jinjava-2.8.4.jar
HubSpot's jinjava shades jsoup (relocated to jinjava.org.jsoup), so no Gradle force in Stirling can replace it
1.15.4
app/lib/jsoup-1.15.4.jar
the unshaded jar, resolved from Stirling's dependency graph
jinjava 2.8.4 is the latest on Maven Central. The fix is HubSpot/jinjava#1337 ("Bump jsoup to 1.23.2"), which is open, along with issues HubSpot/jinjava#1335 and #1342. So jsoup clears only after a jinjava release ships that change and Stirling bumps jinjavaVersion.
Updated wait-list, all already reported upstream, so nothing to file:
Jackson 2: forced to 2.22.3 on Stirling main (#8265). Ships in the next release.
There are no criticals (a fresh trivy 0.75.0 scan of the pinned stirling-pdf:3.0.2@sha256:c1959253… agrees). The 14 HIGH rows are 7 CVEs, because Jackson 2.21.5 and Jackson 3.1.5 each carry the same five. For each advisory I took its precondition and searched the bytecode of Stirling's own jars (app.jar, proprietary-3.0.2-plain.jar, common-3.0.2-plain.jar) and all 410 jars in /app/lib. I also mapped the paths Stirling serves before login (RequestUriUtils, UserAuthenticationFilter). With SECURITY_ENABLELOGIN: "true" set, those are auth/login, auth/refresh, invite/*, webhooks/, workflow/participant/ and some GET info routes.
Result: nothing is reachable without a login or a signing invitation. One Jackson CVE (a CPU DoS) is reachable by a signing participant or by someone who can create a signing workflow.
@JsonTypeInfo(use = NAME, defaultImpl = …) (a fallback for unknown type IDs)
Four Stirling classes use @JsonTypeInfo with NAME (Condition, ConditionInput, Schedule, AiEngineProgressDetail). None sets defaultImpl, so an unknown ID fails instead of being cached.
jackson-core (regex in NumberInput.looksLikeValidNumber)
Jackson coercing a JSON string into a Double/Float/BigDecimal/Number (databind's NumberDeserializers/StdDeserializer, and Jackson 3's StringNode)
Only one request path binds a string into numeric fields: POST /api/v1/workflow/participant/submit-signature. Its wetSignaturesData is read into WetSignatureMetadata, which has Double x, y, width and height. That route is login-exempt, butsubmitSignature first runs ensureSigningEnabled, then findByShareToken(participantToken) (else 404), then rejects expired or completed participants and inactive sessions, and only after that parses the JSON. No other @RequestBody controller binds a DTO with numeric fields. The login DTOs (UsernameAndPassMfa, MfaCodeRequest) have none.
Only with a valid participant token for an active signing session, or for a logged-in user who creates one. The effect is CPU DoS of Stirling, not data access.
jsoup's XML tree builder (XmlTreeBuilder / Parser.xmlParser()) on deeply nested, uniquely-namespaced input
The only class outside jsoup that calls jsoup is flexmark's FlexmarkHtmlConverter (HTML to Markdown), which uses the HTML parser, never xmlParser/XmlTreeBuilder. The jinjava copy is used only by AiDocumentHtmlRenderer, and we run with AIENGINE_ENABLED: "false".
Stirling is Java (JSSE). OpenSSL in the image serves LibreOffice, Python and the CLI tools, none of which speaks DTLS.
No
What that leaves. One CPU-DoS path, open to whoever holds a signing link, or to a household member who can create one. Stirling isn't published and is reachable only through Caddy at pdf.matrix.elysium, and it has a CPU ceiling. This doesn't change the triage above: no rebuild, and take the upstream release that brings Jackson 2.22.3/3.1.7 (Stirling-Tools/Stirling-PDF#8265 is merged and #8252 is open).
What would change this:
Enabling the AI engine, which brings the jinjava/jsoup path back.
Any Stirling release that adds a JSON endpoint with numeric fields before login.
Sending signing links to people outside the household.
To reproduce:
IMG=stirlingtools/stirling-pdf:3.0.2@sha256:c195925341aa35ee5932d9c225edef48a92e46a1280963180d79619cb5f29e6e
# run python3 inside the image (--network none) over /app/app.jar and /app/lib/*.jar, matching class bytes for# createParser(Ljava/io/DataInput;), Ljavax/xml/datatype/(Duration|XMLGregorianCalendar);, JsonIdentityInfo, JsonTypeInfo+defaultImpl,# org/jsoup/parser/XmlTreeBuilder|xmlParser, and the field table of workflow/dto/WetSignatureMetadata
The signing path, traced to the end, 2026-10-10: CVE-2026-89407 isn't reachable either
This follows up the trace above, which left one Jackson CVE as "reachable only with a valid participant token" through POST /api/v1/workflow/participant/submit-signature. I traced that path to the end against the running sensitive-stirling-pdf on trinity. The signing feature is off, so the path closes before any JSON is parsed, for anonymous and logged-in users alike.
The gate.WorkflowSessionService.ensureSigningEnabled() reads storage.enabled and storage.signing.enabled and throws 403 "Group signing is disabled" unless both are true. The live /configs/settings.yml has storage.enabled: false and storage.signing.enabled: false, and no STORAGE_* environment variable overrides them.
Every route hits the gate first. All 21 mapped workflow endpoints call ensureSigningEnabled as their first invocation:
What sits behind it, for when signing is on.submitSignature does these in order:
ensureSigningEnabled;
checks participantToken is present;
findByShareToken (404 otherwise);
rejects an expired participant, a completed participant, or an inactive session;
calls buildSubmissionMetadata.
buildSubmissionMetadata parses wetSignaturesData with Jackson 3 (tools.jackson.databind.ObjectMapper) into WetSignatureMetadata. That has Doublex/y/width/height, which is where a string-to-number coercion would hit the CVE-2026-89407 regex. Creating a session (SigningSessionController) needs a login and the same gate.
Updated verdict for #1004: none of the 14 HIGHs is reachable as deployed. The previous comment's one exception is closed by storage.signing.enabled: false.
What would change this: turning on storage.enabled together with storage.signing.enabled (both marked ALPHA in Stirling's settings). If that's ever wanted, take the Jackson 3.1.7 release first.
Re-traced on 3.1.0, 2026-10-11, with a correction to the 3.0.2 verdict
#1138 moved Stirling-PDF to 3.1.0@sha256:b5b9e400…, and it has been running on trinity since 2026-10-10 22:29 UTC. A fresh trivy 0.75.0 scan gives the same 14 HIGH rows (7 CVEs) as 3.0.2. I re-ran the whole trace against 3.1.0 (own jars app.jar, proprietary-3.1.0-plain.jar, common-3.1.0-plain.jar, plus all of /app/lib), and re-checked the live settings.
Unchanged from 3.0.2:
Not before login.
No createParser(DataInput) (89425), and no javax.xml.datatype fields (68497).
@JsonTypeInfo is only on Condition/ConditionInput/Schedule/AiEngineProgressDetail, none with defaultImpl (91776). No @JsonIdentityInfo (91777).
No jsoup XML parser anywhere (75140). jinjava is only in AiDocumentHtmlRenderer, and AIENGINE_ENABLED=false.
OpenSSL's CVE is DTLS (84782).
The login-exempt paths are the same set. The only JSON controller there binding numeric fields is WorkflowParticipantController, which is still behind ensureSigningEnabled. Live, storage.enabled, storage.signing.enabled and storage.sharing.enabled are all false, with SECURITY_ENABLELOGIN=true.
Correction: CVE-2026-89407is reachable after login, on 3.0.2 and 3.1.0 alike. The earlier comments checked only @RequestBody DTOs, but 94 of Stirling's own classes run ObjectMapper themselves on JSON form fields. Here are the triggers in jackson-databind:
2.x calls looksLikeValidNumber from NumberDeserializer.deserialize and StdDeserializer._parseFloatPrimitive, plus the Double/Float/BigDecimal deserializers.
3.x calls it from those too, and from every StringNode._tryParseAs* (Integer, Long, BigInteger, Float, Double, BigDecimal). So tree-model code reading a JSON string node as any number hits the regex.
Two ordinary tools do that with user input, both on Jackson 3 and identical in 3.0.2:
Form fill (FormFillController, /api/v1/form, then FormPayloadParser) reads numbers from parsed JSON tree nodes.
PDF↔JSON (PdfJsonConversionService) binds user JSON into PdfJson* types with float/double fields.
So a logged-in Stirling user can submit a long digit-run string where a number is expected and burn CPU quadratically in the request thread. This is from the code structure; I didn't exercise it. The other 13 classes found by the same search sit behind features that are off or unconfigured: AI classify and workflows, vector DB and external API integrations, the license check, policy store, account linking, math auditor and portal.
Exposure: a Stirling login is required, and accounts are the household's. Stirling is capped at cpus: 2, so it degrades Stirling only, not trinity. There's no data access. This is the same class of finding as "authenticated users can make the service do expensive work", and it doesn't change the decision: take the upstream Jackson 3.1.7/2.21.7 release when it ships. #1004's open items are still Stirling-Tools/Stirling-PDF#8252 (Jackson 3 and the base image) and a release containing it.
To reproduce:
IMG=stirlingtools/stirling-pdf:3.1.0@sha256:b5b9e400c086e5334a4947a0d7cd589808f743f29267d7f41c18200cf4bc48b4
# python3 in the image (--network none): in jackson-databind-3.1.5.jar, StringNode._tryParseAs* and StdDeserializer._parseFloatPrimitive# reference looksLikeValidNumber; in app.jar, FormFillController (/api/v1/form) and FormPayloadParser use tools/jackson/databind + JsonNode number reads
docker exec sensitive-stirling-pdf sh -c 'env | grep -E "^(AIENGINE_|SECURITY_ENABLELOGIN)"; grep -nE "^ enabled:|^ signing:|^ enabled:" /configs/settings.yml | sed -n 1,6p'
Activity
Gerrrt commentedon Oct 10, 2026
Triage, 2026-10-10: waiting on upstream. Nothing to change here, and the next bump won't fix this.
stirlingtools/stirling-pdf:3.1.0@sha256:b5b9e400…(the current:latest) with the same trivy invocationscripts/scan-images.shuses (aquasec/trivy:0.75.0,--severity HIGH,CRITICAL --ignore-unfixed). It reports the same 14 HIGH findings as the pinned 3.0.2: jackson-core/databind 2.21.5 and 3.1.5, jsoup 1.15.3/1.15.4, and Ubuntu's openssl 3.0.13-0ubuntu3.12. Merging that PR won't close this issue. Review it as a feature release on its own merits (it adds Stirling Office Convert, beta).stacks/sensitivegets merged, homelab-converge-sensitive deploys it within the hour (Nothing converges stacks/sensitive: the tier with the real data is the only one deployed by hand #533), and the next scan closes this issue if the new digest is clean./tmp, and LibreOffice runs under Landlock and seccomp (STIRLING_LO_SANDBOX=required). The findings are in parsers (Jackson's JSON and jsoup's HTML), so the input that could reach them is what a household member uploads. With 0 critical findings, we're not building and patching our own image. We take upstream's release instead.Gerrrt commentedon Oct 10, 2026
This is waiting on upstream. Nothing in the repo needs to change.
stirlingtools/stirling-pdf:3.0.2(sha256:c1959253…).pin-digests.shreports no drift, so the tag hasn't been rebuilt since it was pinned.com.fasterxml.jackson.core:jackson-databind: 3 findings, fixed in 3 different versions (see the table)tools.jackson.core:jackson-databind: 3 findings, fixed in 2 different versions (see the table)com.fasterxml.jackson.core:jackson-core: 2 findings, fixed in 2 different versions (see the table)org.jsoup:jsoup: 2 findings, fixed in 1.23.2tools.jackson.core:jackson-core: 2 findings, fixed in 2 different versions (see the table)libssl3t64: 1 finding, fixed in 3.0.13-0ubuntu3.16When upstream rebuilds or ships a release with these fixes, Dependabot should open the bump. Check it with trivy and
check_image_pins.py, then deploy. The next scan will then close this issue.Gerrrt commentedon Oct 10, 2026
Rechecked 2026-10-10. Upstream hasn't moved: 3.1.0 is still the latest release (digest
sha256:b5b9e400…, unchanged), and Stirling-Tools/Stirling-PDF#8252 is still open. Dependabot's #1138 (3.0.2 → 3.1.0) is open with CI green. As triaged above, it doesn't change this issue's findings.jsoup was the one finding without a known upstream owner. It has one now. Scanning 3.1.0 with our pinned trivy gives its two copies:
app/lib/jinjava-2.8.4.jarjinjava.org.jsoup), so no Gradle force in Stirling can replace itapp/lib/jsoup-1.15.4.jarjinjava 2.8.4 is the latest on Maven Central. The fix is HubSpot/jinjava#1337 ("Bump jsoup to 1.23.2"), which is open, along with issues HubSpot/jinjava#1335 and #1342. So jsoup clears only after a jinjava release ships that change and Stirling bumps
jinjavaVersion.Updated wait-list, all already reported upstream, so nothing to file:
3.0.13-0ubuntu3.12→.16): Pin Netty, Jackson and base image Python deps past the Spring BOM Stirling-Tools/Stirling-PDF#8252.Gerrrt commentedon Oct 10, 2026
The 14 HIGHs, traced, 2026-10-10
There are no criticals (a fresh trivy 0.75.0 scan of the pinned
stirling-pdf:3.0.2@sha256:c1959253…agrees). The 14 HIGH rows are 7 CVEs, because Jackson 2.21.5 and Jackson 3.1.5 each carry the same five. For each advisory I took its precondition and searched the bytecode of Stirling's own jars (app.jar,proprietary-3.0.2-plain.jar,common-3.0.2-plain.jar) and all 410 jars in/app/lib. I also mapped the paths Stirling serves before login (RequestUriUtils,UserAuthenticationFilter). WithSECURITY_ENABLELOGIN: "true"set, those areauth/login,auth/refresh,invite/*,webhooks/,workflow/participant/and some GET info routes.Result: nothing is reachable without a login or a signing invitation. One Jackson CVE (a CPU DoS) is reachable by a signing participant or by someone who can create a signing workflow.
JsonFactory.createParser(DataInput)createParser(DataInput)overload.javax.xml.datatype.Duration/XMLGregorianCalendarfields@JsonTypeInfo(use = NAME, defaultImpl = …)(a fallback for unknown type IDs)@JsonTypeInfowithNAME(Condition,ConditionInput,Schedule,AiEngineProgressDetail). None setsdefaultImpl, so an unknown ID fails instead of being cached.@JsonIdentityInfocollections or maps with forward references@JsonIdentityInfo.NumberInput.looksLikeValidNumber)Double/Float/BigDecimal/Number(databind'sNumberDeserializers/StdDeserializer, and Jackson 3'sStringNode)POST /api/v1/workflow/participant/submit-signature. ItswetSignaturesDatais read intoWetSignatureMetadata, which hasDoublex, y, width and height. That route is login-exempt, butsubmitSignaturefirst runsensureSigningEnabled, thenfindByShareToken(participantToken)(else 404), then rejects expired or completed participants and inactive sessions, and only after that parses the JSON. No other@RequestBodycontroller binds a DTO with numeric fields. The login DTOs (UsernameAndPassMfa,MfaCodeRequest) have none.XmlTreeBuilder/Parser.xmlParser()) on deeply nested, uniquely-namespaced inputFlexmarkHtmlConverter(HTML to Markdown), which uses the HTML parser, neverxmlParser/XmlTreeBuilder. The jinjava copy is used only byAiDocumentHtmlRenderer, and we run withAIENGINE_ENABLED: "false".What that leaves. One CPU-DoS path, open to whoever holds a signing link, or to a household member who can create one. Stirling isn't published and is reachable only through Caddy at
pdf.matrix.elysium, and it has a CPU ceiling. This doesn't change the triage above: no rebuild, and take the upstream release that brings Jackson 2.22.3/3.1.7 (Stirling-Tools/Stirling-PDF#8265 is merged and #8252 is open).What would change this:
To reproduce:
🤖 Traced by Claude Code
Gerrrt commentedon Oct 10, 2026
The signing path, traced to the end, 2026-10-10: CVE-2026-89407 isn't reachable either
This follows up the trace above, which left one Jackson CVE as "reachable only with a valid participant token" through
POST /api/v1/workflow/participant/submit-signature. I traced that path to the end against the runningsensitive-stirling-pdfon trinity. The signing feature is off, so the path closes before any JSON is parsed, for anonymous and logged-in users alike.The gate.
WorkflowSessionService.ensureSigningEnabled()readsstorage.enabledandstorage.signing.enabledand throws403 "Group signing is disabled"unless both are true. The live/configs/settings.ymlhasstorage.enabled: falseandstorage.signing.enabled: false, and noSTORAGE_*environment variable overrides them.Every route hits the gate first. All 21 mapped workflow endpoints call
ensureSigningEnabledas their first invocation:WorkflowParticipantController:getSessionByToken,getParticipantDetails,submitSignature,declineParticipation,getDocument,validateCertificate.SigningSessionController:listSessions,createSession,getSession,deleteSession,addParticipants,removeParticipant,getSessionPdf,finalizeSession,getSignedPdf,listSignRequests,getSignRequestDetail,getSignRequestDocument,signDocument,declineSignRequest,validateCertificate.What sits behind it, for when signing is on.
submitSignaturedoes these in order:ensureSigningEnabled;participantTokenis present;findByShareToken(404 otherwise);buildSubmissionMetadata.buildSubmissionMetadataparseswetSignaturesDatawith Jackson 3 (tools.jackson.databind.ObjectMapper) intoWetSignatureMetadata. That hasDoublex/y/width/height, which is where a string-to-number coercion would hit the CVE-2026-89407 regex. Creating a session (SigningSessionController) needs a login and the same gate.Updated verdict for #1004: none of the 14 HIGHs is reachable as deployed. The previous comment's one exception is closed by
storage.signing.enabled: false.What would change this: turning on
storage.enabledtogether withstorage.signing.enabled(both marked ALPHA in Stirling's settings). If that's ever wanted, take the Jackson 3.1.7 release first.To reproduce:
🤖 Traced by Claude Code
Gerrrt commentedon Oct 11, 2026
Re-traced on 3.1.0, 2026-10-11, with a correction to the 3.0.2 verdict
#1138 moved Stirling-PDF to
3.1.0@sha256:b5b9e400…, and it has been running on trinity since 2026-10-10 22:29 UTC. A fresh trivy 0.75.0 scan gives the same 14 HIGH rows (7 CVEs) as 3.0.2. I re-ran the whole trace against 3.1.0 (own jarsapp.jar,proprietary-3.1.0-plain.jar,common-3.1.0-plain.jar, plus all of/app/lib), and re-checked the live settings.Unchanged from 3.0.2:
createParser(DataInput)(89425), and nojavax.xml.datatypefields (68497).@JsonTypeInfois only onCondition/ConditionInput/Schedule/AiEngineProgressDetail, none withdefaultImpl(91776). No@JsonIdentityInfo(91777).AiDocumentHtmlRenderer, andAIENGINE_ENABLED=false.WorkflowParticipantController, which is still behindensureSigningEnabled. Live,storage.enabled,storage.signing.enabledandstorage.sharing.enabledare allfalse, withSECURITY_ENABLELOGIN=true.Correction: CVE-2026-89407 is reachable after login, on 3.0.2 and 3.1.0 alike. The earlier comments checked only
@RequestBodyDTOs, but 94 of Stirling's own classes runObjectMapperthemselves on JSON form fields. Here are the triggers in jackson-databind:looksLikeValidNumberfromNumberDeserializer.deserializeandStdDeserializer._parseFloatPrimitive, plus the Double/Float/BigDecimal deserializers.StringNode._tryParseAs*(Integer, Long, BigInteger, Float, Double, BigDecimal). So tree-model code reading a JSON string node as any number hits the regex.Two ordinary tools do that with user input, both on Jackson 3 and identical in 3.0.2:
FormFillController,/api/v1/form, thenFormPayloadParser) reads numbers from parsed JSON tree nodes.PdfJsonConversionService) binds user JSON intoPdfJson*types with float/double fields.So a logged-in Stirling user can submit a long digit-run string where a number is expected and burn CPU quadratically in the request thread. This is from the code structure; I didn't exercise it. The other 13 classes found by the same search sit behind features that are off or unconfigured: AI classify and workflows, vector DB and external API integrations, the license check, policy store, account linking, math auditor and portal.
Exposure: a Stirling login is required, and accounts are the household's. Stirling is capped at
cpus: 2, so it degrades Stirling only, not trinity. There's no data access. This is the same class of finding as "authenticated users can make the service do expensive work", and it doesn't change the decision: take the upstream Jackson 3.1.7/2.21.7 release when it ships. #1004's open items are still Stirling-Tools/Stirling-PDF#8252 (Jackson 3 and the base image) and a release containing it.To reproduce:
🤖 Traced by Claude Code