Skip to content

Conversation

@munishchouhan
Copy link
Member

Overview

This PR implements HTTP security headers for all Wave service responses to improve security posture and protect against common web vulnerabilities.

Changes

1. HTTP Security Headers Implementation

New Files Created:

  • src/main/groovy/io/seqera/wave/configuration/SecurityHeadersConfig.groovy
  • src/main/groovy/io/seqera/wave/filter/SecurityHeadersFilter.groovy
  • src/test/groovy/io/seqera/wave/filter/SecurityHeadersFilterTest.groovy

Modified Files:

  • src/main/groovy/io/seqera/wave/filter/FilterOrder.groovy
  • src/main/resources/application.yml

Summary:
Implemented a new HTTP filter that adds industry-standard security headers to all Wave service responses. The filter runs with the highest priority (-120) to ensure security headers are always present.

Security Headers Added:

Header Default Value Purpose
Strict-Transport-Security max-age=31536000; includeSubDomains Enforces HTTPS connections
X-Frame-Options DENY Prevents clickjacking attacks
X-Content-Type-Options nosniff Prevents MIME type sniffing
Referrer-Policy strict-origin-when-cross-origin Controls referrer information
Permissions-Policy camera=(), microphone=(), geolocation=() Restricts browser features
Content-Security-Policy default-src 'self'; frame-ancestors 'none' Prevents XSS and injection attacks

Architecture:

SecurityHeadersConfig (Configuration)
         �
SecurityHeadersFilter (@ServerFilter, Order: -120)
         �
@ResponseFilter (adds headers to all responses)

Key Features:

  • � Fully configurable via application.yml
  • � Can be disabled with wave.security.headers.enabled: false
  • � Individual headers can be customized
  • � Implements Ordered interface for proper filter execution order

Configuration:

Default configuration added to application.yml:

wave:
  security:
    headers:
      enabled: true
      hsts:
        max-age: 31536000
        include-sub-domains: true
      frame-options: "DENY"
      content-type-options: "nosniff"
      referrer-policy: "strict-origin-when-cross-origin"
      permissions-policy: "camera=(), microphone=(), geolocation=()"
      csp: "default-src 'self'; frame-ancestors 'none'"

Filter Order:

SECURITY_HEADERS = -120  (Highest priority - NEW)
DENY_CRAWLER = -110
DENY_PATHS = -100
RATE_LIMITER = -50
PULL_METRICS = 10

Security Benefits

  1. HSTS: Prevents protocol downgrade attacks and cookie hijacking
  2. X-Frame-Options: Protects against clickjacking
  3. X-Content-Type-Options: Prevents MIME confusion attacks
  4. Referrer-Policy: Limits information leakage via referrer header
  5. Permissions-Policy: Restricts access to sensitive browser APIs
  6. CSP: Mitigates XSS and data injection attacks

Signed-off-by: munishchouhan <[email protected]>
@munishchouhan
Copy link
Member Author

testing locally:

Testing Wave Security Headers...
================================

Testing: /service-info
---
✓ Strict-Transport-Security: PRESENT
✓ X-Frame-Options: PRESENT
✓ X-Content-Type-Options: PRESENT
✓ Referrer-Policy: PRESENT
✓ Permissions-Policy: PRESENT
✓ Content-Security-Policy: PRESENT

Testing: /v2/
---
✓ Strict-Transport-Security: PRESENT
✓ X-Frame-Options: PRESENT
✓ X-Content-Type-Options: PRESENT
✓ Referrer-Policy: PRESENT
✓ Permissions-Policy: PRESENT
✓ Content-Security-Policy: PRESENT

Test complete!

@munishchouhan
Copy link
Member Author

Check all headers:

%   curl -i http://localhost:9090/service-info | grep "X-Frame-Options"

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    58  100    58    0     0  17144      0 --:--:-- --:--:-- --:--:-- 19333
X-Frame-Options: DENY
%   curl -si http://localhost:9090/service-info | grep -E "^(Strict-Transport|X-Frame|X-Content|Referrer|Permissions|Content-Security)"

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'
% curl -si http://localhost:9090/service-info | grep "Strict-Transport-Security"
Strict-Transport-Security: max-age=31536000; includeSubDomains

%  curl -si http://localhost:9090/service-info | grep "X-Frame-Options"
X-Frame-Options: DENY

@munishchouhan munishchouhan self-assigned this Dec 23, 2025
Signed-off-by: munishchouhan <[email protected]>
@munishchouhan
Copy link
Member Author

tested in dev:

Testing environment: https://wave.dev-seqera.io


>>> Test 1: Endpoint Reachability
✓ /service-info is reachable (HTTP 200)
✓ /v2/ is reachable (HTTP 200)

>>> Test 2: Security Headers Presence
  ℹ Testing endpoint: /service-info
✓ /service-info - Strict-Transport-Security matches expected value
✓ /service-info - X-Frame-Options matches expected value
✓ /service-info - X-Content-Type-Options matches expected value
✓ /service-info - Referrer-Policy matches expected value
✓ /service-info - Permissions-Policy matches expected value
✓ /service-info - Content-Security-Policy matches expected value
  ℹ Testing endpoint: /v2/
✓ /v2/ - Strict-Transport-Security matches expected value
✓ /v2/ - X-Frame-Options matches expected value
✓ /v2/ - X-Content-Type-Options matches expected value
✓ /v2/ - Referrer-Policy matches expected value
✓ /v2/ - Permissions-Policy matches expected value
✓ /v2/ - Content-Security-Policy matches expected value

>>> Test 3: HSTS Header Validation
✓ HSTS max-age is set to 31536000 (1 year)
✓ HSTS includeSubDomains directive is present

>>> Test 4: X-Frame-Options Validation
✓ X-Frame-Options is set to DENY (prevents clickjacking)

>>> Test 5: Content-Security-Policy Validation
✓ CSP contains frame-ancestors directive
✓ CSP contains default-src directive

>>> Test 6: Service Info API Response
✓ Service info returns valid JSON
✓ Service info contains 'serviceInfo' field

>>> Test 7: Error Pages Have Security Headers
✓ 404 error page includes security headers

>>> Test 8: Response Time Check
  ℹ Response time: 0.164170s (164ms)
✓ Response time is acceptable (< 5s)

>>> Test 9: Multiple Concurrent Requests
  ℹ Sending 10 concurrent requests...
200
200
200
200
200
200
200
200
200
200
✓ Service handles concurrent requests
  ℹ Note: Full concurrent test requires parallel execution

>>> Test 10: Security Headers on Different HTTP Methods
✓ GET requests include security headers
✓ HEAD requests include security headers
✓ OPTIONS requests include security headers

>>> Test 11: SSL/TLS Configuration
✓ SSL/TLS connection established
  ℹ TLS version: UNDEF


========================================
Test Summary
========================================

Environment: https://wave.dev-seqera.io
Timestamp:   2025-12-26 15:45:06

Total Tests:  28
Passed:       28
Failed:       0
Skipped:      0

Pass Rate:    100.00%

========================================
✓ ALL TESTS PASSED!
========================================


@munishchouhan
Copy link
Member Author

tested augmentation:

% wave -i ubuntu --config-file https://fusionfs.seqera.io/releases/v2.5.0-amd64.json  --wave-endpoint https://wave.dev-seqera.io/
wave.dev-seqera.io/wt/28851c3b97bc/library/ubuntu:latest
 % docker pull wave.dev-seqera.io/wt/28851c3b97bc/library/ubuntu:latest
latest: Pulling from wt/28851c3b97bc/library/ubuntu
Digest: sha256:bca3a2fdc3b293cfed293f53e8ee34424cf62cf5f101a54b29e29a4c55364fae
Status: Downloaded newer image for wave.dev-seqera.io/wt/28851c3b97bc/library/ubuntu:latest
wave.dev-seqera.io/wt/28851c3b97bc/library/ubuntu:latest

Signed-off-by: munishchouhan <[email protected]>
@pditommaso
Copy link
Collaborator

@claude review

@claude
Copy link

claude bot commented Jan 7, 2026

Claude finished @pditommaso's task —— View job


Code Review Complete ✅

Todo List:

  • Read and analyze all changed files
  • Review SecurityHeadersConfig implementation
  • Review SecurityHeadersFilter implementation
  • Review test coverage and quality
  • Check configuration approach
  • Provide comprehensive review feedback

Overall Assessment

This is a well-implemented, secure, and production-ready HTTP security headers feature. The implementation follows Micronaut best practices and Wave's coding conventions.

✅ Strengths

Excellent Architecture

  • Clean separation of concerns between SecurityHeadersConfig and SecurityHeadersFilter
  • Proper use of Micronaut's dependency injection and configuration binding
  • Appropriate filter ordering with highest priority (-120) to ensure headers are always applied
  • Conditional loading using @Requires annotation for clean enable/disable functionality

Comprehensive Security Coverage

All critical security headers implemented with sensible defaults:

  • HSTS: 1-year max-age with subdomain inclusion prevents protocol downgrade attacks
  • X-Frame-Options: DENY prevents clickjacking
  • X-Content-Type-Options: nosniff prevents MIME confusion attacks
  • CSP: Restrictive policy with frame-ancestors 'none' strengthens XSS protection
  • Referrer-Policy: Balanced approach limiting information leakage
  • Permissions-Policy: Disables sensitive browser APIs by default

Robust Configuration

  • All headers configurable via application.yml with sensible defaults
  • Global enable/disable switch with wave.security.headers.enabled
  • Individual header customization possible
  • Proper null handling prevents empty headers from being added

Excellent Test Coverage

The test suite (SecurityHeadersFilterTest) is comprehensive with 6 test methods covering:

  • Filter ordering verification (src/test/groovy/io/seqera/wave/filter/SecurityHeadersFilterTest.groovy:51-56)
  • Default header application (src/test/groovy/io/seqera/wave/filter/SecurityHeadersFilterTest.groovy:58-72)
  • Multiple endpoint coverage (src/test/groovy/io/seqera/wave/filter/SecurityHeadersFilterTest.groovy:74-88)
  • Disable functionality (src/test/groovy/io/seqera/wave/filter/SecurityHeadersFilterTest.groovy:90-116)
  • Custom configuration (src/test/groovy/io/seqera/wave/filter/SecurityHeadersFilterTest.groovy:118-147)
  • Null value handling (src/test/groovy/io/seqera/wave/filter/SecurityHeadersFilterTest.groovy:149-178)
  • HSTS value building logic (src/test/groovy/io/seqera/wave/filter/SecurityHeadersFilterTest.groovy:180-198)

Production Quality Code

  • Proper error handling and null safety
  • Appropriate logging at trace level in filter
  • Good documentation with JavaDoc comments
  • Follows Wave's established patterns and conventions

📝 Minor Observations

Code Quality Notes

  1. Trace Logging: The trace log in SecurityHeadersFilter.groovy:96 could include response details for debugging:

    log.trace "Added security headers to response for ${request?.path}"
  2. HSTS Method: The getHstsValue() method in SecurityHeadersConfig.groovy:106-115 is well-designed with proper null handling and conditional subdomain inclusion.

  3. Filter Pattern: Using MATCH_ALL_PATTERN ensures headers are applied to all responses, including error pages.

Security Best Practices Verified

  • ✅ Headers applied to all responses (including errors)
  • ✅ No hardcoded sensitive values
  • ✅ Configurable for different environments
  • ✅ Proper CSP with restrictive defaults
  • ✅ HSTS with long max-age and subdomain inclusion

Testing Notes

The tests properly verify:

  • Integration with Micronaut's HTTP stack
  • Filter disable functionality with separate ApplicationContext
  • Custom configuration handling
  • Edge cases with empty/null values

🔍 Code-Specific Comments

SecurityHeadersConfig.groovy

  • Line 40: Good use of @Requires with default value for conditional loading
  • Lines 52-94: All configuration properties have sensible defaults and proper typing
  • Lines 96-101: Logging configuration values at startup is helpful for debugging
  • Lines 106-115: Excellent HSTS value builder with null safety

SecurityHeadersFilter.groovy

  • Line 42: Implements Ordered correctly for filter precedence
  • Lines 64-97: Clean implementation with null-safe header addition
  • Line 54: Proper type checking before casting to MutableHttpResponse

FilterOrder.groovy

  • Line 30: SECURITY_HEADERS at -120 ensures highest priority execution

🎯 Recommendation

APPROVE - This implementation is production-ready and significantly improves Wave's security posture. The code quality is excellent, test coverage is comprehensive, and the security headers chosen are appropriate for a container provisioning service.

The extensive local and dev environment testing demonstrates due diligence, and the configurable approach allows for environment-specific customization if needed.


Comment on lines +52 to +53
@Value('${wave.security.headers.hsts.max-age:31536000}')
Long hstsMaxAge
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How this value has been chosen (please clarify it in the javadoc)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are number of seconds in one year
i will add that in the javadocs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*/
@Nullable
@Value('${wave.security.headers.csp:default-src \'self\'; frame-ancestors \'none\'}')
String csp
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth expanding to contentSecurityPolicy

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants