- references
- https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html
- https://cwe.mitre.org/data/definitions/532.html
- https://errorprone.info/bugpattern/RestrictedApi
- https://logback.qos.ch/manual/layouts.html
- https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config/interceptors.html
- https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config.typesafe-configuration-properties
- goals of this workshop
- introduction to secure logging in Spring Boot applications
- understanding why secrets should be protected at API, domain and logging layers
- showing how to sanitize request headers before logging
- showing how to use Logback masking as defense in depth
- writing regression tests that prove honeypot secrets do not appear in logs
- workshop plan
-
run tests
mvn test
-
inspect API boundary types
CreateRequestSecretApiInput- verify that raw
apiKeyis not exposed bytoString
-
inspect domain secret handling
Secret- verify that raw value is never returned from
toString - verify that
revealrequires documented intent
-
inspect request logging
RequestLoggingInterceptorHeaderSanitizer- verify that sensitive headers are replaced with
[REDACTED]
-
inspect Logback masking
MaskingPatternLayoutSensitiveDataMasker- verify that accidental raw logs are masked before printing
-
run application
mvn spring-boot:run
-
call endpoint
curl -i -X POST http://localhost:8080/api/test \ -H "Content-Type: application/json" \ -H "Authorization: Bearer SECRET_TEST_123" \ -H "X-API-KEY: API_KEY_HEADER_TEST_789" \ -d '{"username":"alice","apiKey":"API_KEY_TEST_456"}' -
verify logs
- should contain
[REDACTED] - should not contain
SECRET_TEST_123 - should not contain
API_KEY_TEST_456 - should not contain
API_KEY_HEADER_TEST_789
- should contain
-
- sensitive data in logs is a security problem
- logs are often copied to external systems
- observability platforms
- support tools
- incident archives
- logs usually have different access rules than production databases
- once a secret is written to logs, rotation is usually required
- logs are often copied to external systems
- examples of values that should not be logged
- passwords
- api keys
- bearer tokens
- cookies
- session identifiers
- authorization headers
- secure logging should be implemented as layers
- avoid logging request and response bodies by default
- make sensitive API fields safe to render
- make domain secret objects safe to render
- sanitize headers before logging
- mask known patterns in final log output
- test that known honeypot values never appear in logs
SecretApiInput- wraps incoming secret value at the API edge
- uses delegating JSON creator
- payload can use a simple string value
- overrides
toString- returns
[REDACTED]
- returns
CreateRequest- contains username and api key
- generated record
toStringis safe becauseSecretApiInput.toStringis safe
- endpoint intentionally logs request object
- purpose: prove that DTO rendering does not leak raw api key
Secret- wraps raw secret material
- overrides
toString- returns
[REDACTED]
- returns
- exposes raw value only through
reveal
reveal- is marked with Error Prone
@RestrictedApi - allowed only for code annotated with
@SecretAccessPurpose - makes raw secret access explicit at call site
- is marked with Error Prone
- this does not replace review or testing
- it makes accidental usage harder
- it documents intentional usage
-
RequestLoggingInterceptor- logs method
- logs path
- logs status
- logs sanitized headers
-
it does not log request or response body
- bodies often contain credentials, tokens and personal data
-
HeaderSanitizer- receives names of headers that must be masked
- compares names case-insensitively
- replaces configured header values with
[REDACTED]
-
default masked headers
AuthorizationCookieSet-CookieX-API-KEY
-
configuration
app: logging: masked-headers: - Authorization - Cookie - Set-Cookie - X-API-KEY
MaskingPatternLayout- extends Logback
PatternLayout - delegates normal formatting to Logback
- applies
SensitiveDataMaskerbefore log line is emitted
- extends Logback
SensitiveDataMasker- masks key-value patterns
apiKey=...token=...password=...
- masks JSON patterns
"apiKey":"...""token":"...""password":"..."
- masks selected header patterns
AuthorizationCookieSet-CookieX-API-KEY
- masks key-value patterns
- masking is defense in depth
- it should not be the first protection layer
- it protects against accidental logging paths
-
SecureLoggingIntegrationTest- captures Spring Boot logs
- sends requests with honeypot secret values
- verifies that raw values do not appear in output
- verifies that
[REDACTED]appears instead
-
important test cases
- secret from
Authorizationheader must be masked - secret from JSON
apiKeymust be masked - accidental raw
Secret.reveal()logging must still be masked
- secret from
-
example assertion
assertThat(logs).doesNotContain(secret); assertThat(logs).doesNotContain(apiKey); assertThat(logs).contains("[REDACTED]");
- break one layer intentionally
- remove
SecretApiInput.toString - run tests
- restore it after observing failure
- remove
- secure logging is not one mechanism
- safe API and domain types prevent common accidental leaks
- sanitized request logging avoids header leaks
- Logback masking catches remaining accidental output
- regression tests should contain realistic honeypot values