diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bbfe8da57..e2f765234 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -69,7 +69,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" - name: run mvn clean package run: ./mvnw clean package -Ddependency-check.skip=true -Dmaven.test.skip=true - name: Perform CodeQL Analysis diff --git a/.github/workflows/container_test.yml b/.github/workflows/container_test.yml index 5538f4976..a19d84c51 100644 --- a/.github/workflows/container_test.yml +++ b/.github/workflows/container_test.yml @@ -23,7 +23,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: Navigate to test script and run run: cd .github/scripts && bash docker-create.sh -t diff --git a/.github/workflows/dast-zap-test.yml b/.github/workflows/dast-zap-test.yml index 885ff5f64..b842e3e46 100644 --- a/.github/workflows/dast-zap-test.yml +++ b/.github/workflows/dast-zap-test.yml @@ -18,7 +18,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" - name: Clean install run: ./mvnw --no-transfer-progress clean install -DskipTests -Ddependency-check.skip -Dcyclonedx.skip=true -Dexec.skip - name: Start wrongsecrets diff --git a/.github/workflows/java_swagger_doc.yml b/.github/workflows/java_swagger_doc.yml index 63ee2e900..903e778f9 100644 --- a/.github/workflows/java_swagger_doc.yml +++ b/.github/workflows/java_swagger_doc.yml @@ -18,7 +18,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" - name: Clean install run: ./mvnw --no-transfer-progress clean install -DskipTests -Ddependency-check.skip -Dcyclonedx.skip=true -Dexec.skip - name: Compile javadoc diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ee4634e48..e51497624 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: checkstyle with Maven run: ./mvnw --no-transfer-progress checkstyle:check @@ -43,7 +43,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: spotbugs with Maven run: ./mvnw --no-transfer-progress package -DskipTests spotbugs:check @@ -59,7 +59,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: Test with Maven run: ./mvnw --no-transfer-progress test diff --git a/.github/workflows/master-container-publish.yml b/.github/workflows/master-container-publish.yml index c14d7437a..b18b97c93 100644 --- a/.github/workflows/master-container-publish.yml +++ b/.github/workflows/master-container-publish.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: Extract version from pom.xml diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 950cc0d10..b4aa0c1f0 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -29,7 +29,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: Extract version from pom.xml @@ -249,7 +249,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: Extract PR version @@ -278,7 +278,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: Extract main version diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index fdbda4ea2..3fc0e584f 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -31,7 +31,7 @@ jobs: cache: "npm" - uses: actions/setup-java@v4 with: - distribution: "oracle" + distribution: "temurin" java-version: "23" - name: Install npm dependencies run: npm install diff --git a/.github/workflows/version-sync-check.yml b/.github/workflows/version-sync-check.yml index ab2bec66c..0935efc45 100644 --- a/.github/workflows/version-sync-check.yml +++ b/.github/workflows/version-sync-check.yml @@ -20,7 +20,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: Validate version consistency diff --git a/.github/workflows/visual-diff.yml b/.github/workflows/visual-diff.yml index e7e001f6b..54c269ba5 100644 --- a/.github/workflows/visual-diff.yml +++ b/.github/workflows/visual-diff.yml @@ -25,7 +25,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: Extract PR version @@ -53,7 +53,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: "23" - distribution: "oracle" + distribution: "temurin" cache: "maven" - name: Extract main version diff --git a/config/zap/rule-config.tsv b/config/zap/rule-config.tsv index 080e43759..c245744a8 100644 --- a/config/zap/rule-config.tsv +++ b/config/zap/rule-config.tsv @@ -1,14 +1,14 @@ 10027 IGNORE (Information Disclosure - Suspicious Comments) 10031 IGNORE (Informational User Controllable HTML Element Attribute (Potential XSS)) -10049 IGNORE (Non-Storable Content) -10054 IGNORE (Cookie without SameSite Attribute) -10055 IGNORE (CSP: Wildcard Directive) +10049 IGNORE (Non-Storable Content - Fixed with cache control headers) +10054 IGNORE (Cookie without SameSite Attribute - Fixed with SameSite=strict) +10055 IGNORE (CSP: Wildcard Directive - Fixed with restrictive CSP) 10055 IGNORE (CSP: script-src unsafe-inline) 10055 IGNORE (CSP: style-src unsafe-inline) -10063 IGNORE (Permissions Policy Header Not Set) +10063 IGNORE (Permissions Policy Header Not Set - Fixed with permissions policy header) 10109 IGNORE (Modern Web Application) 10110 IGNORE (Dangerous JS Functions) -90033 IGNORE (Loosely Scoped Cookie) +90033 IGNORE (Loosely Scoped Cookie - Fixed with secure cookie settings) 10096 IGNORE (Timestamp Disclosure - Unix) 10112 IGNORE Session Management Response Identified 10105 IGNORE Authentication Credentials Captured diff --git a/src/main/java/org/owasp/wrongsecrets/SecurityConfig.java b/src/main/java/org/owasp/wrongsecrets/SecurityConfig.java index 195c5c67d..3aa349cdd 100644 --- a/src/main/java/org/owasp/wrongsecrets/SecurityConfig.java +++ b/src/main/java/org/owasp/wrongsecrets/SecurityConfig.java @@ -29,6 +29,12 @@ public SecurityFilterChain security( configureHerokuHttps(http, portMapperProvider.getIfAvailable(PortMapperImpl::new)); configureBasicAuthentication(http, auths); configureCsrf(http); + // Disable default security headers since we handle them in SecurityHeaderAddingFilter + http.headers( + headers -> + headers + .frameOptions(frameOptions -> frameOptions.sameOrigin()) + .contentTypeOptions(Customizer.withDefaults())); return http.build(); } diff --git a/src/main/java/org/owasp/wrongsecrets/SecurityHeaderAddingFilter.java b/src/main/java/org/owasp/wrongsecrets/SecurityHeaderAddingFilter.java index 8e2e19b83..848628530 100644 --- a/src/main/java/org/owasp/wrongsecrets/SecurityHeaderAddingFilter.java +++ b/src/main/java/org/owasp/wrongsecrets/SecurityHeaderAddingFilter.java @@ -14,12 +14,25 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha throws IOException, ServletException { HttpServletResponse res = (HttpServletResponse) response; res.addHeader("Server", "WrongSecrets - Star us!"); - res.addHeader("X-Frame-Options", "SAMEORIGIN"); - res.addHeader("X-Content-Type-Options", "nosniff"); - res.addHeader( + res.setHeader("X-Frame-Options", "SAMEORIGIN"); // Override Spring Security's default DENY + res.setHeader("X-Content-Type-Options", "nosniff"); + + // Improved Content Security Policy - more restrictive than wildcard + res.setHeader( "Content-Security-Policy", - "default-src * 'self'; script-src * 'self' 'unsafe-inline'; style-src * 'self'" - + " 'unsafe-inline'; img-src data:"); + "default-src 'self'; script-src 'self' 'unsafe-inline' https://buttons.github.io" + + " https://api.github.com; style-src 'self' 'unsafe-inline'" + + " https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src" + + " 'self' data: https:; connect-src 'self' https://api.github.com"); + + // Add Permissions Policy header + res.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=()"); + + // Add cache control headers to prevent caching of sensitive content + res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + res.setHeader("Pragma", "no-cache"); + res.setHeader("Expires", "0"); + chain.doFilter(request, res); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9711af294..c2fe00f4c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -49,6 +49,8 @@ K8S_ENV=DOCKER APP_VERSION=@project.version@ logging.level.root=INFO server.servlet.session.tracking-modes=COOKIE +server.servlet.session.cookie.http-only=true +server.servlet.session.cookie.same-site=strict asciidoctor.enabled=false hints_enabled=true ctf_enabled=false diff --git a/src/test/java/org/owasp/wrongsecrets/SecurityHeaderTest.java b/src/test/java/org/owasp/wrongsecrets/SecurityHeaderTest.java new file mode 100644 index 000000000..883cbac2a --- /dev/null +++ b/src/test/java/org/owasp/wrongsecrets/SecurityHeaderTest.java @@ -0,0 +1,72 @@ +package org.owasp.wrongsecrets; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = {"K8S_ENV=k8s"}) +@AutoConfigureMockMvc +class SecurityHeaderTest { + + @Autowired private MockMvc mvc; + + @Test + void shouldHaveXFrameOptionsHeader() throws Exception { + mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(header().string("X-Frame-Options", "SAMEORIGIN")); + } + + @Test + void shouldHaveXContentTypeOptionsHeader() throws Exception { + mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(header().string("X-Content-Type-Options", "nosniff")); + } + + @Test + void shouldHaveContentSecurityPolicyHeader() throws Exception { + mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(header().exists("Content-Security-Policy")); + } + + @Test + void shouldHavePermissionsPolicyHeader() throws Exception { + mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect( + header().string("Permissions-Policy", "geolocation=(), microphone=(), camera=()")); + } + + @Test + void shouldHaveCacheControlHeaders() throws Exception { + mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(header().string("Cache-Control", "no-cache, no-store, must-revalidate")) + .andExpect(header().string("Pragma", "no-cache")) + .andExpect(header().string("Expires", "0")); + } + + @Test + void shouldNotHaveWildcardInCSP() throws Exception { + mvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect( + result -> { + String csp = result.getResponse().getHeader("Content-Security-Policy"); + if (csp != null && csp.contains("default-src *")) { + throw new AssertionError( + "CSP should not contain wildcard directive 'default-src *'"); + } + }); + } +}