spring-security — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited spring-security (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill is the Spring-specific layer on top of secure-coding and api-security. Spring Security is powerful and exactly therefore foot-gun-rich: small letters in the config decide whether your app is safe or wide open.
Triggers on:
spring-boot-starter-security, spring-security-oauth2-client, SecurityFilterChain beans, @EnableWebSecurity, @PreAuthorize/@PostAuthorize annotations, application.yml with spring.security.* or management.*..permitAll(), .disable() on CSRF/CORS, custom AuthenticationProvider, UserDetailsService, or a JWT filter.security-review or api-security where Spring is in the stack.secure-coding.api-security for conceptual questions (what is IDOR, how do you validate a schema), this skill for Spring-specific implementation.p/java-spring or CodeQL → sast-orchestrator.cve-triage.container-hardening + k8s-security.secrets-scanner.Six phases. Phase 1 (SecurityFilterChain) is where most production bugs live.
Spring Security 6+ uses the Lambda DSL. Each chain decides which auth mode goes with which path and what "open" means.
Common foot-guns:
http.authorizeHttpRequests(auth -> auth.requestMatchers("/api/**").permitAll()) opens the entire API. Look for .permitAll() on wildcards and challenge each one./api/** permitAll above a /api/admin/** authenticated rule overrules the latter. Always go from specific to general..anyRequest().authenticated(), with an exception for a purely public app.@Order. First match wins. A too-broad first chain can render later chains redundant..cors(cors -> cors.configurationSource(source)) with a CorsConfigurationSource that returns wildcards is a standard misconception. allowedOrigins("*") together with allowCredentials(true) does not work (Spring rejects it), but it is a signal that the config flow has not been thought through.Concrete reference config for a stateless JWT API:
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/health").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.oauth2ResourceServer(rs -> rs.jwt(Customizer.withDefaults()))
.csrf(csrf -> csrf.disable()) // correct for stateless API
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}Route-level auth is never enough. Methods with sensitive logic deserve @PreAuthorize or equivalent.
@EnableGlobalMethodSecurity. Without this annotation, @PreAuthorize/@PostAuthorize do not work.api-security API1 BOLA).Common miss: ownership check in the service method but not in the controller, or vice versa. One place is enough, but it must be unambiguous which one.
Spring Boot Actuator exposes operational endpoints. The default config (older) was wide open. Since Boot 2.x the defaults are /health and /info public, the rest authenticated. Reviewer rule: confirm you have not fallen back to the older model.
"*" is wrong in prod. Limit to what Ops actually needs: health,info,prometheus,metrics.ALWAYS/WHEN_AUTHORIZED. /env shows all env vars, including secrets when misconfigured.k8s-security).EndpointRequest.toAnyEndpoint() matcher, role ACTUATOR or equivalent.Spring has three OAuth2 roles: client (consumer), resource-server (you, validating tokens), authorization-server (you, issuing tokens). Each has its own foot-guns.
spring.security.oauth2.resourceserver.jwt.issuer-uri or an explicit JwtDecoder with NimbusJwtDecoder.withIssuerLocation(issuer).OAuth2TokenValidatorFactories.create().andValidate(JwtIssuerValidator).andValidate(JwtAudienceValidator).RS256 / ES256, reject HS256 unless explicitly intended. Algorithm confusion is a classic attack. See also api-security phase 2..setClockSkew(Duration.ofMinutes(2)) is reasonable, not 1 hour.The Spring ecosystem has a few infamous CVEs; each is a pattern to look for.
ServletRequestDataBinder without an allowlist are exposed. Spring Boot's default binder has been patched since the fix release.spring.cloud.function.routing-expression header. Lesson: SpEL evaluation on untrusted input is RCE. Search your code for SpelExpressionParser().parseExpression(userInput).matchers combined with mvcMatchers. Fixed in 5.3.26, 6.0.7. Reviewer rule: mixing antMatchers and mvcMatchers is a foot-gun — use one consistently and prefer requestMatchers (Spring Security 6).AuthenticatedVoter was configured without additional checks. [verify against https://spring.io/security/cve-2024-22257] for the exact patched versions in your context.[verify against https://spring.io/security/] — check the CVE feed on every version bump or review. No invented IDs in findings.SessionAuthenticationStrategy). Do not turn it off without reason.BCryptPasswordEncoder default. Argon2 variant via Argon2PasswordEncoder if the library is included. Never NoOpPasswordEncoder outside tests.Verification-loop: Layer 1 (SecurityFilterChain config coherent? Actuator endpoints explicitly locked down? JWT issuer + audience both validated?), Layer 2 (CVE IDs confirmed via spring.io/security, OAuth flow names correct, no invented Spring annotations in examples).
Spring Security review — <service/module>
Spring Boot: <x.y.z> | Spring Security: <x.y.z> | Version status: <current | N releases behind>
SecurityFilterChain:
Chains present: N
permitAll() matchers: <list + context>
.anyRequest() last: <authenticated | permitAll — FINDING>
CSRF status: <enabled | disabled with context>
CORS config: <scoped | wildcard — FINDING>
Method security:
@EnableMethodSecurity: <yes/no>
@PreAuthorize coverage: <controllers with/without>
Ownership checks: <present on resource endpoints?>
Actuator:
Exposure: <list of endpoints>
/env show-values: <NEVER | WHEN_AUTHORIZED | ALWAYS — FINDING>
/heapdump: <disabled | exposed — FINDING>
Separate port or filter:<yes/no>
OAuth2 / JWT:
Role: <client | resource-server | both>
Issuer validation: <yes/no>
Audience validation: <yes/no>
Algorithm whitelist: <yes/no>
CVE check:
Spring4Shell patched: <yes>
Recent security release:<within N days of upstream?>
cve-triage handoff: <N open>
Findings (severity-sorted, follow security-review format)
Verification-loop: ...~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.