Java security checklist covering OWASP Top 10, input validation, injection prevention, and secure coding. Works with Spring, Quarkus, Jakarta EE, and plain Java. Use when reviewing code security, before releases, or when user asks about vulnerabilities.
npx skills add https://github.com/decebals/claude-code-java --skill security-audit
Security checklist for Java applications based on OWASP Top 10 and secure coding practices.
| # | Risk | Java Mitigation |
|---|------|-----------------|
| A01 | Broken Access Control | Role-based checks, deny by default |
| A02 | Cryptographic Failures | Use strong algorithms, no hardcoded secrets |
| A03 | Injection | Parameterized queries, input validation |
| A04 | Insecure Design | Threat modeling, secure defaults |
| A05 | Security Misconfiguration | Disable debug, secure headers |
| A06 | Vulnerable Components | Dependency scanning, updates |
| A07 | Authentication Failures | Strong passwords, MFA, session management |
| A08 | Data Integrity Failures | Verify signatures, secure deserialization |
| A09 | Logging Failures | Log security events, no sensitive data |
| A10 | SSRF | Validate URLs, allowlist domains |
Works in Spring, Quarkus, Jakarta EE, and standalone.
// ✅ GOOD: Validate at boundary
public class CreateUserRequest {
@NotNull(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be 3-50 characters")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username can only contain letters, numbers, underscore")
private String username;
@NotNull
@Email(message = "Invalid email format")
private String email;
@NotNull
@Size(min = 8, max = 100)
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$",
message = "Password must contain uppercase, lowercase, and number")
private String password;
@Min(value = 0, message = "Age cannot be negative")
@Max(value = 150, message = "Invalid age")
private Integer age;
}
// Controller/Resource - trigger validation
public Response createUser(@Valid CreateUserRequest request) {
// request is already validated
}
// Custom annotation
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SafeHtmlValidator.class)
public @interface SafeHtml {
String message() default "Contains unsafe HTML";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// Validator implementation
public class SafeHtmlValidator implements ConstraintValidator<SafeHtml, String> {
private static final Pattern DANGEROUS_PATTERN = Pattern.compile(
"<script|javascript:|on\\w+\\s*=", Pattern.CASE_INSENSITIVE
);
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) return true;
return !DANGEROUS_PATTERN.matcher(value).find();
}
}
// ❌ BAD: Blocklist (attackers find bypasses)
if (input.contains("<script>")) {
throw new ValidationException("Invalid input");
}
// ✅ GOOD: Allowlist (only permit known-good)
private static final Pattern SAFE_NAME = Pattern.compile("^[a-zA-Z\\s'-]{1,100}$");
if (!SAFE_NAME.matcher(input).matches()) {
throw new ValidationException("Invalid name format");
}
// ✅ GOOD: Parameterized queries
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
// ✅ GOOD: Criteria API
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
query.where(cb.equal(user.get("email"), email)); // Safe
// ✅ GOOD: Named parameters
TypedQuery<User> query = entityManager.createQuery(
"SELECT u FROM User u WHERE u.status = :status", User.class);
query.setParameter("status", status); // Safe
// ❌ BAD: String concatenation
String jpql = "SELECT u FROM User u WHERE u.email = '" + email + "'"; // VULNERABLE!
// ✅ GOOD: Parameterized native query
@Query(value = "SELECT * FROM users WHERE email = ?1", nativeQuery = true)
User findByEmailNative(String email);
// ❌ BAD: Concatenated native query
String sql = "SELECT * FROM users WHERE email = '" + email + "'"; // VULNERABLE!
// ✅ GOOD: PreparedStatement
String sql = "SELECT * FROM users WHERE email = ? AND status = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, email);
stmt.setString(2, status);
ResultSet rs = stmt.executeQuery();
}
// ❌ BAD: Statement with concatenation
String sql = "SELECT * FROM users WHERE email = '" + email + "'"; // VULNERABLE!
Statement stmt = connection.createStatement();
stmt.executeQuery(sql);
// ✅ GOOD: Use templating engine's auto-escaping
// Thymeleaf - auto-escapes by default
<p th:text="${userInput}">...</p> // Safe
// To display HTML (dangerous, use carefully):
<p th:utext="${trustedHtml}">...</p> // Only for trusted content!
// ✅ GOOD: Manual encoding when needed
import org.owasp.encoder.Encode;
String safe = Encode.forHtml(userInput);
String safeJs = Encode.forJavaScript(userInput);
String safeUrl = Encode.forUriComponent(userInput);
Maven dependency for OWASP Encoder:
<dependency>
<groupId>org.owasp.encoder</groupId>
<artifactId>encoder</artifactId>
<version>1.2.3</version>
</dependency>
// Add CSP header to prevent inline scripts
// Spring Boot
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; script-src 'self'; style-src 'self'")
)
);
return http.build();
}
}
// Servlet Filter (works everywhere)
@WebFilter("/*")
public class SecurityHeadersFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Content-Security-Policy", "default-src 'self'");
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-Frame-Options", "DENY");
response.setHeader("X-XSS-Protection", "1; mode=block");
chain.doFilter(req, res);
}
}
// CSRF enabled by default for browser clients
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// For REST APIs with JWT (stateless) - can disable CSRF
.csrf(csrf -> csrf.disable())
// For browser apps with sessions - keep CSRF enabled
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
}
# application.properties
quarkus.http.csrf.enabled=true
quarkus.http.csrf.cookie-name=XSRF-TOKEN
// ✅ GOOD: Use BCrypt or Argon2
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
// BCrypt (widely supported)
PasswordEncoder encoder = new BCryptPasswordEncoder(12); // strength 12
String hash = encoder.encode(rawPassword);
boolean matches = encoder.matches(rawPassword, hash);
// Argon2 (recommended for new projects)
PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String hash = encoder.encode(rawPassword);
// ❌ BAD: MD5, SHA1, SHA256 without salt
String hash = DigestUtils.md5Hex(password); // NEVER for passwords!
// ✅ GOOD: Check authorization at service layer
@Service
public class DocumentService {
public Document getDocument(Long documentId, User currentUser) {
Document doc = documentRepository.findById(documentId)
.orElseThrow(() -> new NotFoundException("Document not found"));
// Authorization check
if (!doc.getOwnerId().equals(currentUser.getId()) &&
!currentUser.hasRole("ADMIN")) {
throw new AccessDeniedException("Not authorized to access this document");
}
return doc;
}
}
// ❌ BAD: Only check at controller level, trust user input
@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
return documentRepository.findById(id).orElseThrow(); // No auth check!
}
@PreAuthorize("hasRole('ADMIN')")
public void adminOnly() { }
@PreAuthorize("hasRole('USER') and #userId == authentication.principal.id")
public void ownDataOnly(Long userId) { }
@PreAuthorize("@authService.canAccess(#documentId, authentication)")
public Document getDocument(Long documentId) { }
// ❌ BAD: Hardcoded secrets
private static final String API_KEY = "sk-1234567890abcdef";
private static final String DB_PASSWORD = "admin123";
// ✅ GOOD: Environment variables
String apiKey = System.getenv("API_KEY");
// ✅ GOOD: External configuration
@Value("${api.key}")
private String apiKey;
// ✅ GOOD: Secrets manager
@Autowired
private SecretsManager secretsManager;
String apiKey = secretsManager.getSecret("api-key");
# ✅ GOOD: Reference environment variables
spring:
datasource:
password: ${DB_PASSWORD}
api:
key: ${API_KEY}
# ❌ BAD: Hardcoded in application.yml
spring:
datasource:
password: admin123 # NEVER!
# Never commit these
.env
*.pem
*.key
*credentials*
*secret*
application-local.yml
// ❌ DANGEROUS: Java ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(untrustedInput);
Object obj = ois.readObject(); // Remote Code Execution risk!
// ✅ GOOD: Use JSON with Jackson
ObjectMapper mapper = new ObjectMapper();
// Disable dangerous features
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
); // Be careful with polymorphic types!
User user = mapper.readValue(json, User.class);
// ✅ Configure Jackson safely
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// Prevent unknown properties exploitation
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Don't allow class type in JSON (prevents gadget attacks)
mapper.deactivateDefaultTyping();
return mapper;
}
}
Maven:
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>9.0.7</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<failBuildOnCVSS>7</failBuildOnCVSS> <!-- Fail on high severity -->
</configuration>
</plugin>
Run:
mvn dependency-check:check
# Report: target/dependency-check-report.html
# Check for updates
mvn versions:display-dependency-updates
# Update to latest
mvn versions:use-latest-releases
| Header | Value | Purpose |
|--------|-------|---------|
| Content-Security-Policy | default-src 'self' | Prevent XSS |
| X-Content-Type-Options | nosniff | Prevent MIME sniffing |
| X-Frame-Options | DENY | Prevent clickjacking |
| Strict-Transport-Security | max-age=31536000 | Force HTTPS |
| X-XSS-Protection | 1; mode=block | Legacy XSS filter |
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.headers(headers -> headers
.contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
.frameOptions(frame -> frame.deny())
.httpStrictTransportSecurity(hsts -> hsts.maxAgeInSeconds(31536000))
.contentTypeOptions(Customizer.withDefaults())
);
return http.build();
}
// ✅ Log security-relevant events
log.info("User login successful", kv("userId", userId), kv("ip", clientIp));
log.warn("Failed login attempt", kv("username", username), kv("ip", clientIp), kv("attempt", attemptCount));
log.warn("Access denied", kv("userId", userId), kv("resource", resourceId), kv("action", action));
log.error("Authentication failure", kv("reason", reason), kv("ip", clientIp));
// ❌ NEVER log sensitive data
log.info("Login: user={}, password={}", username, password); // NEVER!
log.debug("Request body: {}", requestWithCreditCard); // NEVER!
java-code-review - General code reviewmaven-dependency-audit - Dependency vulnerability scanninglogging-patterns - Secure logging practicesExpert in secure backend coding practices specializing in input validation, authentication, and API security. Use PROACTIVELY for backend security implementations or security code reviews.
This skill should be used when the user asks to "perform cloud penetration testing", "assess Azure or AWS or GCP security", "enumerate cloud resources", "exploit cloud misconfigurations", "test O365 security", "extract secrets from cloud environments", or "audit cloud infrastructure". It provides comprehensive techniques for security assessment across major cloud platforms.
You are a dependency security expert specializing in vulnerability scanning, license compliance, and supply chain security. Analyze project dependencies for known vulnerabilities, licensing issues, outdated packages, and provide actionable remediation strategies.
Comprehensive Flow Nexus platform management - authentication, sandboxes, app deployment, payments, and challenges
This skill should be used when the user asks to "escalate privileges on Linux", "find privesc vectors on Linux systems", "exploit sudo misconfigurations", "abuse SUID binaries", "exploit cron jobs for root access", "enumerate Linux systems for privilege escalation", or "gain root access from low-privilege shell". It provides comprehensive techniques for identifying and exploiting privilege escalation paths on Linux systems.
Expert malware analyst specializing in defensive malware research, threat intelligence, and incident response. Masters sandbox analysis, behavioral analysis, and malware family identification. Handles static/dynamic analysis, unpacking, and IOC extraction. Use PROACTIVELY for malware triage, threat hunting, incident response, or security research.
This skill should be used when the user asks to "use Metasploit for penetration testing", "exploit vulnerabilities with msfconsole", "create payloads with msfvenom", "perform post-exploitation", "use auxiliary modules for scanning", or "develop custom exploits". It provides comprehensive guidance for leveraging the Metasploit Framework in security assessments.
Expert in secure mobile coding practices specializing in input validation, WebView security, and mobile-specific security patterns. Use PROACTIVELY for mobile security implementations or mobile security code reviews.
Take decebals/security-audit from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.