JCU/fix(security): stop advertising Express and mark cookies Secure on HTTPS - #1436
Open
Kasinhou wants to merge 1 commit into
Open
JCU/fix(security): stop advertising Express and mark cookies Secure on HTTPS#1436Kasinhou wants to merge 1 commit into
Kasinhou wants to merge 1 commit into
Conversation
…n HTTPS Two of the three findings in M7 live in this repo: * Every response carried `X-Powered-By: Express`. That is fingerprinting material for an attacker and useful to nobody else, so the header is now disabled on the SSR server. * Cookies written by the UI - `XSRF-TOKEN`, `dsLanguage`, the Orejime consent cookie, `dsAccessibility`, `dsCorrelationId`, the redirect and impersonation cookies - were written without the `Secure` attribute, so they are sent back over plaintext if the user is ever downgraded to HTTP. ClientCookieService now adds `Secure` whenever the page itself was loaded over HTTPS. The decision is taken from the page protocol rather than from `ui.ssl`, because TLS is usually terminated by a reverse proxy and `ui.ssl` is false on HTTPS-only sites; reading the protocol also keeps `http://localhost` development working. The third finding (missing HSTS / CSP / nosniff / Referrer-Policy / Permissions-Policy headers) belongs to the nginx in front of the app and cannot be fixed from here - a ready-to-apply snippet is in the PR description. Fixes the in-repo part of M7 from dataquest-dev/dspace-customers#853. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DO NOT MERGED FOR NOW
Fixes the part of M7 (dataquest-dev/dspace-customers#853) that lives in this repository.
M7 lists three things. Two of them are ours, one is nginx's:
X-Powered-By: Expresson every responseserver.tsXSRF-TOKENcookie withoutSecureClientCookieServiceX-Content-Type-Options/X-Frame-Options/Referrer-Policy/Permissions-PolicyConfirmed, today, on production
and, read in the browser on the live HTTPS origin:
X-Powered-Byserver.disable('x-powered-by')right after the Express app is created. Express adds the header fromits own default middleware, so this removes it everywhere — SSR pages, the 404 page,
/robots.txt,static assets and
/app/health— without touching any route.proxy_hide_header X-Powered-Byin nginx would also work, but only for traffic that goes throughthat particular nginx. Turning it off at the source means every deployment of this branch gets it.
SecurecookiesClientCookieService.set()passed the caller's options straight tojs-cookie, and no caller eversets
secure. So every cookie the UI writes lacks the attribute, not justXSRF-TOKEN:XSRF-TOKENXsrfInterceptor,UploaderComponentdsLanguageLocaleServiceorejime-*BrowserOrejimeService(cookie-consent choices)dsAccessibilityCookieAccessibilitySettingsServiceCORRELATION-IDCorrelationIdServicehasAgreedEndUserEndUserAgreementServicedsRedirectUrl,dsImpersonatingEPersonAuthServiceThe service now fills in
securewhen the caller did not:Three decisions worth stating, since they are the reviewable part:
ui.ssl. In this deployment (and in most DSpace deployments)nginx terminates TLS and the Node server behind it speaks plain HTTP, so
ui.sslisfalseon asite that is HTTPS-only.
location.protocolis what the browser actually used, which is exactlythe condition under which a
Securecookie is accepted and useful.true. ASecurecookie set on a plain-HTTP page is dropped by thebrowser, which would break local development and any HTTP-only test instance. Deriving the flag
means nothing changes for those.
httpOnly. M7 also noteshttpOnly=falseonXSRF-TOKEN. That one is by design and muststay: Angular's
HttpXsrfTokenExtractorreads this cookie from JavaScript and echoes it into theX-XSRF-TOKENheader. The cookie that must not be script-readable isDSPACE-XSRF-COOKIE, and thebackend already sets it
httpOnly=true; secure=true; sameSite=None.sameSiteis likewise leftalone — the browser default (
Lax) is already what we want and setting it explicitly would onlyadd a way to get it wrong later.
Verified
Production build (
npm run build:prod) of this branch against the local Docker 9.3 backend.No
X-Powered-Byon/home,/robots.txt,/assets/config.json,/app/healthor the 404 page.New spec,
client-cookie.service.spec.ts— 5/5 SUCCESS:Neighbouring suites still pass:
core/services+core/xsrf+shared/cookies+correlation-id+core/locale96/96,core/auth123/123.npm run lint→ 0 errors.Browser check on the running local stack (HTTP, so
Secureis deliberately not applied): cookiesare still written and the app behaves normally —
Honest limitation: the
Secureattribute can only be observed over HTTPS, and the local stack isHTTP-only (making it HTTPS end-to-end means re-pointing
dspace.server.url,dspace.ui.urland theREST base URL at a TLS terminator — more moving parts than the four-line change deserves). The HTTPS
branch is therefore covered by the unit tests above; the flag will be visible in DevTools →
Application → Cookies on dev-6 as soon as this is deployed, and I'm happy to confirm it there.
Still open — nginx, needs server access
I cannot reach the JCU nginx; there is no
bits/nginx/for JCU undercustomer-specific/indataquest-dev/dspace-customers(ZCU and SAV have theirs there). This is the part that needs doing onthe host:
Two notes for whoever applies it:
Referrer-Policy: strict-origin-when-cross-originis safe for us specifically because it stillsends the full URL on same-origin requests, and DSpace reads
Refererfor download statistics(
SolrLoggerServiceImpl,ExportEventProcessor,GoogleAsyncEventListener). A blanketno-referrerwould silently degrade usage reporting.add_headerinsidelocationblocks: anadd_headerin a nested block discards allinherited ones. Put these at
serverlevel and don't add anyadd_headerin alocationwithoutrepeating them.
Verify afterwards with
curl -sIand https://securityheaders.com.Upstream / other customers
server.tsandclient-cookie.service.tsare byte-for-byte identical toupstream/dspace-9_x, andneither
x-powered-bynor asecurecookie attribute appears anywhere indspace-9_x,dspace-10_xormain— so this is not a JCU regression, it is upstream behaviour. There is noupstream issue for either (searched "x-powered-by", "secure cookie", "security headers"). Both changes
are small and generic and are worth offering upstream.
None of our other customer branches has them either (
customer/{mendelu,TUL,sav,lindat,uk,vsb-tuo,zcu-pub,zcu-data}),and
X-Powered-By: Expressis live ondspace.zcu.czas well — so this is a fleet-wide finding, nota JCU one.
Evidence
Screenshots:
M7-1-before-prod-x-powered-by-and-insecure-xsrf-cookie.pngM7-2-after-no-stack-header-and-secure-cookies.png