Spring Boot is not secure by default.
Adding
spring-boot-starter-security
to your
pom.xml
is the beginning of the work, not the end of it. Without deliberate configuration, Actuator endpoints expose internal state publicly, the Security Filter Chain may permit unauthenticated access to routes you assumed were protected, CORS defaults allow all origins, and secrets end up in
application.properties
in plaintext. This checklist covers the 10 security checks I run on every Spring Boot application before it goes to production — each one takes under a minute, and every one of them catches real issues in real systems.
Every week I look at Spring Boot applications built by experienced engineers at companies that take security seriously. The same misconfigurations appear. Not because the engineers are careless — but because Spring Boot's defaults optimise for development convenience, not production hardening. What ships to production is often what was configured in the first sprint and never revisited.
This is not a tutorial. It is a production audit. Here is what I check, what I find, and what you should fix.
Over the last two years, the Spring Boot security issues I reviewed in client projects were rarely exotic zero-day exploits or framework-level bugs. They were almost always ordinary production oversights: an exposed
/actuator/env
, a JWT filter wired incorrectly, a native query accepting raw input, a Git repository containing plaintext database credentials, or a dependency scanner that nobody ever configured. None of these looked urgent during development. Every one of them became urgent after deployment.
Spring Boot Security Reality in 2026
A06:2021
Vulnerable & Outdated Components is still in the OWASP Top 10 — most Spring apps ship with dependency drift
Thousands monthly
Public GitHub repositories still expose
application.properties
secrets — hardcoded credentials remain common
Pentest reports
Misconfigured Actuator and CORS endpoints continue appearing in enterprise penetration testing reports — defaults plus assumptions create exposure
The 10-Minute Spring Boot Security Audit Dashboard
Before the detail: here is the full audit at a glance. Use this table as your quick reference. The checks below explain each line in depth.
Check #1 — Patch Spring Boot and Spring Security Versions First
This is not glamorous. Teams skip it because it feels like maintenance, not security. But outdated Spring Boot and Spring Security versions are the most common source of exploitable CVEs in Java applications I review. A vulnerability in your framework version affects every endpoint, every request, every user — without a single line of your own code being wrong.
pom.xml · check and pin your Spring Boot parent version explicitly
<parent>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-parent
</artifactId>
<version>3.3.5
</version>
<!-- pin explicitly — never use LATEST or RELEASE -->
</parent>
<!-- Spring Security version — override if transitive resolution pulls older -->
<properties>
<spring-security.version>6.3.4
</spring-security.version>
</properties>
Shell · check what versions are actually on the classpath
# Maven — print effective dependency tree filtered to security libs
mvn dependency:tree | grep -E "spring-boot|spring-security|tomcat|jackson"
# Gradle
./gradlew dependencies | grep -E "spring-boot|spring-security"
Teams running Spring Boot 2.x in 2026 without a migration plan. Spring Boot 2.x reached end-of-life in November 2023. Every month it stays in production accumulates unpatched CVEs. The effort to stay on 2.x and patch vulnerabilities manually exceeds the effort to migrate to 3.x.
Check #2 — Lock Down Every
/actuator/**
Endpoint Before Production
Spring Boot Actuator is incredibly useful in development. In production, without explicit lockdown, it hands attackers a complete map of your application: environment variables including secrets, full heap dumps, all bean definitions, all URL mappings, and your running configuration. I find publicly accessible Actuator endpoints in production more often than I find properly locked-down ones.
application.yml · production-safe Actuator configuration
management:
# Only expose health and info — nothing else
endpoints:
web:
exposure:
include: health, info
endpoint:
health:
show-details: never # never expose DB status or disk space to public
info:
enabled: true
# Run Actuator on a separate internal port — never expose 8081 publicly
server:
port: 8081
SecurityFilterChain · lock Actuator routes even on the internal port
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN") // everything else needs ADMIN
.anyRequest().authenticated()
);
/actuator/env
returning database passwords.
/actuator/heapdump
accessible with a single GET request — a heap dump contains session tokens, in-memory secrets, and user data in plain text.
/actuator/mappings
giving attackers a complete list of all routes to probe.
In multiple production audits,
/actuator/env
and
/actuator/heapdump
were the two endpoints teams forgot to disable because they assumed obscurity was protection. Internal URLs leak faster than teams expect — through application logs, browser history, developer screenshots, reverse proxy access logs, shared Postman collections, and internal documentation. Restrict by configuration, not by assumption.
Check #3 — Verify the Spring Security Filter Chain Actually Protects All Routes
This is the check that makes engineers uncomfortable when I raise it — because they assumed Spring Security was protecting everything. The assumption is wrong. The default Spring Security configuration in 3.x requires all requests to be authenticated, which sounds correct. But the moment you write a custom
SecurityFilterChain
bean, you override that default. Your custom chain protects exactly what you tell it to protect. Everything else may fall through.
Java · SecurityFilterChain — the difference between safe and broken
// DANGEROUS — anyRequest().permitAll() at the end
// This means: if a route is not explicitly listed above, allow anyone in.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().permitAll() // ← EVERY unlisted route is publicly accessible
);
return http.build();
}
// CORRECT — anyRequest().authenticated() as the catch-all
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")
.anyRequest().authenticated() // ← default: must be authenticated
);
return http.build();
}
A developer adds
.anyRequest().permitAll()
to stop a test failing during development. The PR merges. The admin endpoints are now publicly accessible. I have found
/api/admin/users
returning full user lists on production systems because of exactly this pattern.
The Most Dangerous Spring Boot Security Mistake: Assuming the Starter Did It For You
One of the most repeated phrases I hear from teams during internal reviews is: "We already added Spring Security."
The dependency being present does not mean the application is secure. I still regularly see APIs where:
- custom filters are registered in the wrong order in the filter chain,
/api/admin/**is accidentally excluded from the matcher pattern,- OPTIONS preflight requests bypass authentication unexpectedly,
- Swagger UI, H2 console, and internal admin routes remain publicly reachable,
- or legacy controllers sit outside the intended request matcher pattern entirely.
Spring Boot security failures are rarely missing libraries. They are incorrect assumptions about what the library is actually protecting.
Check #4 — Remove Hardcoded Secrets and Plain Credentials from Configuration Files
Hardcoded secrets in
application.properties
or
application.yml
committed to version control is the most common, most preventable, and most damaging misconfiguration I encounter. Once a secret is in git history, it is compromised permanently — even if you delete the file later. Git history is forever, and GitHub secret scanners, Gitleaks, and attacker tooling are specifically designed to find these.
BAD — secrets hardcoded in application.properties
spring.datasource.password=MyProd$ecret123
jwt.secret=supersecretjwtkey
spring.security.oauth2.client.registration.google.client-secret=GOCSPX-abc123
GOOD — secrets from environment variables or a secrets manager
# application.yml — reference env vars, never inline values
spring:
datasource:
password: ${DB_PASSWORD} # injected at runtime from env or vault
jwt:
secret: ${JWT_SECRET}
spring:
security:
oauth2:
client:
registration:
google:
client-secret: ${GOOGLE_CLIENT_SECRET}
# For production: use AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault
# Spring Cloud Vault integrates directly with Spring Boot's property loading
Shell · scan for hardcoded secrets before committing
# Gitleaks — scans current repo for leaked secrets
gitleaks detect --source=. --verbose
# Trufflehog — scans git history, not just current state
trufflehog git file://. --only-verified
Rotate it immediately — the secret is already compromised. Then use
git filter-branch
or BFG Repo Cleaner to remove it from history. Add
application-*.properties
with actual values to
.gitignore
. Use
application-local.properties
for development only, never committed.
Hardcoded secrets are rarely discovered by attackers through your running application first. They are discovered through source control leaks, CI build logs, Docker image layer inspection, shared config archives, and copied staging environment files. Once a credential is committed to git — even in a private repository — assume it is eventually recoverable by anyone who gains repository access, now or in the future.
Check #5 — Run a 60-Second SQL Injection Audit Across Repositories and Native Queries
Spring Data JPA repository derived methods are safe. The moment you write a custom
@Query
with string concatenation, or build JPQL dynamically, or use native queries without named parameters — you have introduced SQL injection. I cover this in detail in our
SQL Injection in Spring Boot guide
. For the audit, here is the 60-second check.
Shell · grep audit — find string concatenation in query construction
# Find any + operator near query-related strings in your codebase
grep -rn "createQuery\|createNativeQuery\|nativeQuery" src/ | grep '".*+\|+.*"'
# Find @Query annotations with string interpolation (should return nothing safe)
grep -rn '@Query' src/ | grep '".*\+\|+.*"'
# Run Semgrep java.sql.injection family against entire src directory
semgrep --config "p/java" --include="*.java" src/
Every
@Query(nativeQuery=true)
should use
:namedParam
binding, not string concatenation. Every
EntityManager.createQuery()
call should use
.setParameter("name", value)
. Add
Semgrep
java.sql.injection
rules to your CI pipeline — it catches these on every PR automatically.
When reviewing complex queries visually, a browser-based SQL formatter (such as
LearnHubly's SQL Formatter
) makes concatenation risks immediately visible — a
+
operator adjacent to a variable stands out clearly in a formatted query.
Check #6 — Validate Every Request Parameter, DTO, and User Input Before Business Logic
Bean Validation (JSR 380) exists in every Spring Boot project via
spring-boot-starter-validation
. Developers add it, annotate a few fields, and then forget to add
@Valid
on the controller parameter — which means the annotations run, but their failures are silently ignored. The data enters business logic unchecked.
Java · common validation misconfiguration — @Valid missing from controller
// BAD — @Valid is missing. All @NotBlank, @Email, @Size annotations
// on CreateUserRequest are completely ignored. No exception thrown.
@PostMapping("/users")
public ResponseEntity
<User> createUser(@RequestBody CreateUserRequest request) {
return ResponseEntity.ok(userService.create(request));
}
// GOOD — @Valid triggers constraint validation before method body executes
@PostMapping("/users")
public ResponseEntity
<User> createUser(@RequestBody @Valid CreateUserRequest request) {
return ResponseEntity.ok(userService.create(request));
}
// The DTO
public record CreateUserRequest(
@NotBlank(message = "Name is required")
@Size(max = 100)
String name,
@Email(message = "Valid email required")
@NotBlank
String email,
@Pattern(regexp = "^(?=.*[A-Z])(?=.*\\d).{8,}$",
message = "Password must be 8+ chars with uppercase and digit")
String password
) {}
Java · global exception handler for validation failures
@RestControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity
<Map
handleValidation(
MethodArgumentNotValidException ex) {
Map
<String, String> errors = ex.getBindingResult()
.getFieldErrors().stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage
));
return ResponseEntity.badRequest().body(errors);
// Returns: {"email": "Valid email required", "name": "Name is required"}
// Never expose internal exception details — just field + message
}
}
Check #7 — Review JWT, Session, and Authentication Misconfigurations
JWT misconfiguration in Spring Boot applications takes three consistent forms: using HS256 with a weak or hardcoded secret, setting access token expiry to 24 hours or more, and storing refresh tokens in localStorage where they are accessible to JavaScript and therefore vulnerable to XSS. I covered this in full in our JWT vs Session Cookies guide . The audit version:
Java · Spring Security JWT configuration audit points
// CHECK 1 — Are you using RS256? (asymmetric) or HS256 with a shared secret?
// HS256 requires every service to share the signing key — one leak compromises all.
// RS256: sign with private key, verify with public key — services only need public key.
// SecurityConfig — enforce RS256 decoder, not symmetric key
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder
.withPublicKey(rsaPublicKey()) // RSA public key from config
.signatureAlgorithm(SignatureAlgorithm.RS256) // hardcode the algorithm
.build();
}
// CHECK 2 — Token expiry in your JwtEncoder / token service
// Access token: max 15 minutes
// Refresh token: max 7-30 days, stored in HttpOnly cookie
// CHECK 3 — Are you accepting alg:none?
// Never use: Jwts.parserBuilder().setSigningKey(key).build()
// (trusts the token's own alg header)
// Always use: explicit decoder with hardcoded algorithm (as above)
application.yml · token expiry configuration
jwt:
access-token-expiry: 900 # 15 minutes in seconds — max acceptable
refresh-token-expiry: 604800 # 7 days in seconds
algorithm: RS256 # never HS256 for distributed services
# private-key-path and public-key-path from environment variables, not hardcoded
Check #8 — Restrict CORS and Cross-Origin Browser Access Explicitly
CORS misconfiguration is the security issue that looks harmless in development and is dangerous in production.
allowedOrigins("*")
means any website can make authenticated requests to your API from a user's browser using their existing session. Combined with CSRF, this enables cross-site request forgery attacks from any origin.
Java · Spring Boot CORS — explicit allowlist, never wildcard in production
// BAD — allows any origin, any method, any header
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*"); // ← never in production
config.addAllowedMethod("*");
config.addAllowedHeader("*");
// ...
}
// GOOD — explicit allowlist per environment
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
// Read origins from environment variable — different per environment
config.setAllowedOrigins(List.of(
"https://app.yourdomain.com",
"https://admin.yourdomain.com"
));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-Request-ID"));
config.setAllowCredentials(true); // required for cookies/auth headers
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
Test your CORS headers and preflight responses directly in the browser using a REST client (such as
LearnHubly's REST API Tester
). Send an
OPTIONS
request to your endpoint and inspect the
Access-Control-Allow-Origin
response header — it should return your specific domain, not
*
.
Check #9 — Add Security Headers and Disable Verbose Error Leakage
Security headers are a 10-minute configuration change that protect against clickjacking, MIME-type sniffing, and XSS with almost zero application impact. Error leakage — stack traces, SQL error messages, and Spring exception details in API responses — gives attackers a map of your internals. Both are consistently missing in production systems I review.
Java · Spring Security headers + error leakage prevention
http.headers(headers -> headers
// X-Frame-Options: DENY — prevent clickjacking
.frameOptions(frame -> frame.deny())
// X-Content-Type-Options: nosniff — prevent MIME sniffing
.contentTypeOptions(Customizer.withDefaults())
// Strict-Transport-Security — force HTTPS for 2 years
.httpStrictTransportSecurity(hsts -> hsts
.maxAgeInSeconds(63072000)
.includeSubDomains(true)
.preload(true))
// Content-Security-Policy — restrict resource loading
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; frame-ancestors 'none'"))
);
application.yml · suppress Spring Boot error detail in production
server:
error:
include-message: never # never expose exception messages
include-binding-errors: never # never expose field binding errors
include-stacktrace: never # absolutely never expose stack traces
include-exception: false # never expose exception class name
# Custom error controller overrides /error endpoint
# Return only: { "status": 400, "error": "Bad Request" } — nothing else
Check #10 — Run Dependency and Container Vulnerability Scans Before Deployment
Your own code is not the only attack surface. Every dependency in your
pom.xml
or
build.gradle
, every transitive dependency it pulls, and the base Docker image you deploy on — all of these have CVE databases maintained against them. A single unpatched dependency can expose your application to a critical vulnerability that has nothing to do with your code.
Maven · OWASP Dependency-Check in pom.xml
<plugin><groupId>org.owasp
</groupId><artifactId>dependency-check-maven
</artifactId><version>9.0.7
</version><configuration><failBuildOnCVSS>7
</failBuildOnCVSS><format>HTML
</format><suppressionFile>dependency-check-suppressions.xml
</suppressionFile></configuration><executions><execution><goals><goal>check
</goal></goals></execution></executions></plugin>Shell · Trivy container scan before pushing to registry
# Scan your Docker image — fail if HIGH or CRITICAL CVEs found
trivy image --exit-code 1 --severity HIGH,CRITICAL
your-app:latest
# Scan filesystem for dependency CVEs (no image needed)
trivy fs --exit-code 1 --severity HIGH,CRITICAL .
# Snyk — alternative with fix suggestions
snyk test --severity-threshold=high
These scans should run on every build — not quarterly. New CVEs are disclosed daily. A dependency clean on Monday may be critical by Friday. Integrate OWASP Dependency-Check or Snyk into your CI pipeline and fail the build on HIGH+ findings. The Spring Boot dependency version management documentation lists every managed dependency — use it to understand what version is active in your classpath.
Real Spring Boot Security Misconfigurations I Keep Seeing in Production Projects
The ten checks above are systematic. These are the patterns that appear so consistently they deserve their own section — because they slip through even when teams think they have covered the basics.
1. H2 console enabled in production
spring.h2.console.enabled=true
in
application.properties
with no profile guard. The H2 web console is a full database interface — accessible in a browser, no authentication required by default. Teams enable it for local development and forget to disable it. Profile your configuration: development-only settings belong in
application-dev.yml
, never in the base file.
2. Spring Security @EnableWebSecurity without method security
Route-level security in
SecurityFilterChain
protects URL patterns. It does not protect service methods called from scheduled tasks, event listeners, or internal service-to-service calls that bypass HTTP entirely. Add
@EnableMethodSecurity(prePostEnabled=true)
and annotate critical service methods with
@PreAuthorize
.
3. session fixation and CSRF disabled without replacement
http.csrf(csrf -> csrf.disable())
is correct for stateless JWT APIs — but teams disable it for session-based applications where it is still necessary, or they forget to verify their API is truly stateless before disabling. If you are using server-side sessions, never disable CSRF protection without a replacement strategy.
4. Logging sensitive request data
logging.level.org.springframework.web=DEBUG
in production logs full request and response bodies including Authorization headers, tokens, and request parameters containing user data. Debug logging should never be active in production. Use structured logging with explicit field selection.
5. Docker image running as root
The default Spring Boot Docker image runs as root inside the container. If the container is compromised, the attacker has root access to the container filesystem. Always add a non-root user in your Dockerfile and switch to it before the entrypoint.
Dockerfile · run Spring Boot as non-root user
FROM eclipse-temurin:21-jre-alpine
# Create non-root user
RUN addgroup -S spring && adduser -S spring -G spring
# Copy application as non-root
COPY --chown=spring:spring target/app.jar /app/app.jar
# Switch to non-root before entrypoint
USER spring:spring
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Common False Security Assumptions That Keep Spring Boot Apps Vulnerable
Before the checklist — the six assumptions I hear most often in project reviews that turn out to be wrong:
- "We use Spring Security, so our endpoints are protected."
— Not if your
SecurityFilterChainends withanyRequest().permitAll()or a route was accidentally excluded from the matcher pattern. - "Actuator is only reachable internally." — Internal URLs leak through logs, screenshots, proxies, browser history, and shared documentation. "Only internal" is not access control.
- "Hibernate prevents all SQL injection automatically." — Only when you use parameterised queries. Native queries with concatenation, dynamic JPQL, and sort parameters bypass Hibernate's protection entirely.
- \n
application.ymlinside a private repo is safe enough." — Repository access spreads: contractors, CI systems, forked branches, export archives, and leaked tokens. Committed secrets are eventually recoverable. - "Default CORS settings are fine for frontend integration." — During development, yes. In production, permissive CORS allows any website to make authenticated requests from a user's browser using their credentials.
- "We will run dependency scans before release." — Quarterly scans or release-gate scans are not security coverage. A vulnerability disclosed on Tuesday does not wait for your release process.
Almost every preventable production issue starts as one of these six assumptions surviving long enough to become infrastructure truth.
The 10-Minute Pre-Production Spring Boot Security Checklist
Print this. Add it to your PR template. Run it before every production deployment. Every item maps to a check above.
- Spring Boot parent version is current (3.3.x+) and explicitly pinned in pom.xml Check #1
- Spring Security version verified — no known HIGH/CRITICAL CVEs via Dependency-Check Check #1
- Actuator exposes only health + info — all other endpoints disabled or ADMIN-only Check #2
- Actuator runs on port 8081 — not the public-facing 8080 Check #2
- SecurityFilterChain ends with anyRequest().authenticated() — not permitAll() Check #3
- Admin and internal routes explicitly protected with role-based rules Check #3
- No secrets, passwords, or API keys in any committed configuration file Check #4
- All secrets sourced from environment variables or a secrets manager Check #4
- Grep audit finds zero + concatenation in query construction code Check #5
- Semgrep java.sql.injection rules passing in CI pipeline Check #5
- Every @RequestBody parameter has @Valid — confirmed with a test case Check #6
- Global @RestControllerAdvice handles MethodArgumentNotValidException cleanly Check #6
- JWT uses RS256 — not HS256 with a shared secret Check #7
- Access token expiry is 15 minutes or less Check #7
- Refresh tokens stored in HttpOnly + Secure cookies — not localStorage Check #7
- CORS configured with explicit origin allowlist — no wildcard Check #8
- Security headers present: HSTS, X-Frame-Options, X-Content-Type-Options, CSP Check #9
- server.error.include-stacktrace=never in production config Check #9
- OWASP Dependency-Check or Snyk passing with no HIGH/CRITICAL findings Check #10
- Docker image scanned with Trivy — no HIGH/CRITICAL CVEs in base image Check #10
- Dockerfile uses non-root USER before ENTRYPOINT Misconfig #5
- H2 console disabled in all non-development profiles Misconfig #1
- Debug logging disabled — no sensitive headers or bodies logged in production Misconfig #4
FAQ — Spring Boot Application Security
No. Spring Boot provides security building blocks — Spring Security, Actuator, Bean Validation — but none are configured for production security by default. Actuator endpoints may be exposed without authentication, the
SecurityFilterChain
can permit unauthenticated access if misconfigured, CORS defaults are permissive in development, and secrets default to plaintext in properties files. Secure configuration requires deliberate setup. Adding
spring-boot-starter-security
is the beginning of the work, not the end.
Yes, when left with default or broad exposure. Endpoints like
/actuator/env
,
/actuator/heapdump
,
/actuator/beans
, and
/actuator/mappings
expose environment variables including secrets, full JVM memory dumps, bean wiring details, and complete route maps. In production, expose only
/actuator/health
and
/actuator/info
. Run Actuator on a separate internal port (8081) and ensure that port is never reachable from outside your VPC or load balancer.
Spring Security is necessary but not sufficient. It handles authentication and route-level authorization — but SQL injection, hardcoded secrets, insecure dependencies, missing input validation, CORS misconfiguration, and container vulnerabilities are all outside Spring Security's scope. A secure Spring Boot application requires correct configuration across all layers: the Security filter chain, application code, infrastructure, and dependency management.
On every build in your CI pipeline — not quarterly. OWASP Dependency-Check, Snyk, or Trivy should run as a pipeline step and fail the build if a HIGH or CRITICAL CVE is detected. New CVEs are disclosed daily. A dependency that was clean on Monday may have a critical disclosure by Friday. Quarterly scans create a false sense of coverage and miss the vulnerabilities disclosed between runs.
Yes, consistently. AI coding assistants generate Spring Boot code from patterns in public training data that includes older insecure configurations. Common outputs include
anyRequest().permitAll()
as the SecurityFilterChain default,
allowedOrigins("*")
for CORS, string-concatenated SQL queries, and hardcoded JWT secrets in properties examples. Treat AI-generated security-related code as untrusted input: review it, lint it with Semgrep, and test it with real attack payloads before merging.
Security Is Not a Spring Dependency — It Is a Deployment Discipline
Most Spring Boot compromises do not begin with advanced attackers discovering obscure framework flaws. They begin with teams shipping ordinary defaults, postponing small reviews, and trusting that "someone else already checked security."
An exposed Actuator endpoint survives because it was convenient during testing.
A hardcoded credential survives because moving it to Vault was scheduled for "later."
A vulnerable dependency survives because no scanner was part of CI.
A raw query survives because it worked and nobody fuzzed it before merge.
None of these decisions feel catastrophic in the sprint where they are made. Combined, they become the exact attack surface production systems carry for months.
That is why this checklist matters. Not because Spring Boot is insecure — but because secure frameworks still ship insecure applications when review discipline is optional.
The ten checks above take under ten minutes. Make them mandatory. — Priya
Run These 3 Browser Checks Before Every Production Deployment
Three minutes of browser testing catches more than most teams expect.
- 🗄️ Review SQL and native queries with the SQL Formatter — concatenation risks become visible when code is structured
- 🛠️ Fire real attack payloads with the REST API Tester — test CORS headers, rate limiting, and injection responses
- 🔍 Decode suspicious encoded requests with the URL Decoder — reveal what attackers are actually sending in your logs