Conversation
…security headers on every chain (#7644) Every chain disabled CSRF and no client sends a token, so a page of another site could post a form to a generated create endpoint with the logged in user's session cookie. - CrossSiteRequestFilter, in the CSRF filter's slot of every chain via the new BrowserSecurityConfigurator: a POST/PUT/PATCH/DELETE carrying an ambient credential (session id, or non-Bearer HTTP authentication) is refused with 403 when Sec-Fetch-Site is not same-origin/none, or - without fetch metadata - when Origin is null or names another host. Server-side clients, bearer tokens and CORS origins that name a host pass. No client change. DIRIGIBLE_SECURITY_CROSS_SITE_PROTECTION=false is the escape hatch. - Session cookie SameSite=Lax; HttpOnly (DIRIGIBLE_SESSION_COOKIE_SAMESITE, DIRIGIBLE_SESSION_COOKIE_SECURE). - Headers decided in one place instead of per chain: X-Frame-Options SAMEORIGIN (was disabled on basic, cognito, github, snowflake), Referrer-Policy strict-origin-when-cross-origin, HSTS auto|always|off, opt-in CSP (report-only by default) and Permissions-Policy. - ZAP full scans authenticate as the default basic user (still report-only). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| @Bean | ||
| SecurityFilterChain chain(HttpSecurity http) throws Exception { | ||
| // the shape of every platform chain: tokens off, frames left open | ||
| http.csrf(csrf -> csrf.disable()) |
| .csrf(csrf -> csrf.disable()) // if enabled, some functionalities will not work - like creating a project | ||
| // no client sends a CSRF token - BrowserSecurityConfigurator refuses forged cross-site requests and | ||
| // sets the frame options | ||
| .csrf(csrf -> csrf.disable()) |
| + " these origins are trusted with the sessions of logged in users.", origins); | ||
| LOGGER.warn("Cross-origin requests from {} may carry the session cookie. No CSRF token is asked for and the" | ||
| + " cross-site request filter lets them through, so these origins are trusted with the sessions of logged in users.", | ||
| origins); |
Comment on lines
+96
to
+101
| LOGGER.warn( | ||
| "Refused a cross-site [{}] on [{}] from origin [{}] ({} [{}]) carrying the user's session or browser credentials." | ||
| + " A page of another site cannot act in a logged in user's name; a trusted front end belongs in [{}], a" | ||
| + " server-side client authenticates without a browser.", | ||
| request.getMethod(), request.getRequestURI(), request.getHeader(HttpHeaders.ORIGIN), SEC_FETCH_SITE, | ||
| request.getHeader(SEC_FETCH_SITE), DirigibleConfig.CORS_ALLOWED_ORIGINS.getKey()); |
This branch has not been deployed
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.
Cause
Every security chain (basic, cognito, keycloak, github, snowflake) runs
csrf.disable(), and no IDE or Harmonia client sends a token. The session cookie had noSameSite. So a page of another site that a logged-in user visited could post a form to a generatedPOST/PUT/DELETE .../gen/.../api/...endpoint, and the browser would attach the user's session.X-Frame-Optionswas disabled on four of the five chains.Change
1. Cross-site writes are refused, with no token and no client change.
CrossSiteRequestFiltersits in the CSRF filter's slot on every chain, so it runs after CORS and before logout and authentication. It refuses with 403 aPOST/PUT/PATCH/DELETEthat carries an ambient credential when either:Sec-Fetch-Siteis anything other thansame-originornone(same-sitecounts as cross-site, because a sibling subdomain may belong to another tenant), orOriginisnullor names a host other thanX-Forwarded-Host/Host.An ambient credential is a session id, or HTTP authentication other than
Bearer. Browser-remembered Basic credentials count.These pass: a request with neither header (a server-side client), a bearer token, and an origin from
DIRIGIBLE_CORS_ALLOWED_ORIGINSthat names a host (the wildcard trusts nobody). The escape hatch isDIRIGIBLE_SECURITY_CROSS_SITE_PROTECTION=false.2. Session cookie
SameSite=Lax; HttpOnly, set inapplication-common.properties:DIRIGIBLE_SESSION_COOKIE_SAMESITEoverridesSameSite.Secureon any request it sees as secure.DIRIGIBLE_SESSION_COOKIE_SECURE=trueforces it behind a proxy that does not forward the scheme.3. Headers are decided in one place.
BrowserSecurityConfiguratoris aCustomSecurityConfigurator, so it reaches every chain, and the per-chainframeOptionslines are gone:X-Frame-OptionsSAMEORIGINDIRIGIBLE_SECURITY_FRAME_OPTIONS=SAMEORIGIN/DENY/DISABLEDReferrer-Policystrict-origin-when-cross-originDIRIGIBLE_SECURITY_REFERRER_POLICYStrict-Transport-SecurityDIRIGIBLE_SECURITY_HSTS=auto/always/offContent-Security-PolicyDIRIGIBLE_SECURITY_CONTENT_SECURITY_POLICY, report-only by default (..._REPORT_ONLY)Permissions-PolicyDIRIGIBLE_SECURITY_PERMISSIONS_POLICYX-Content-Type-Options: nosniffstays from Spring's defaults. No CSP is sent by default: I have no policy that is proven against Monaco and the Harmonia shells, and report-only violations would show up as console errors in downstream UI suites.4. ZAP: the full scans in
build.ymlandrelease.ymlnow authenticate as the default basic user (ZAP_AUTH_HEADER*). They stay report-only, as the issue asks for the first release.Verified
CrossSiteRequestFilterTest(15 cases) andBrowserSecurityConfiguratorTest(7 cases, MockMvc against a real chain shaped like the platform's).EndpointAuthorizationITgains two cases:Sec-Fetch-Site: cross-site, or a foreignOriginwithout fetch metadata, POSTed to a generated create endpoint returns 403, and the log showsCrossSiteRequestFilterrefusing it. The same post withsame-origingets past the filter.Set-CookiecarriesHttpOnly; SameSite=Lax. Anonymous and authenticated responses carryX-Frame-Options: SAMEORIGIN,Referrer-Policyandnosniff.EndpointAuthorizationIT,SecurityIT,ExternalFrontendIT(38 tests, keycloak and CORS with an external front end),StompCrossOriginCredentialsIT(56/56 together). Also the browser-drivenCreateNewProjectIT,CreateNewFileIT,ModelGenerationITandGeneratedShellTenantUsersITin real headless Chrome. Those cover the IDE flows the old snowflake comment said CSRF would break, and no request was refused.formatter:validategreen with the cache wiped.Not verified / left open
DIRIGIBLE_SECURITY_FRAME_OPTIONS=DISABLED.fail_actionon High, and a baseline scan on PRs.Fixes #7644
🤖 Generated with Claude Code