CtrlK
BlogDocsLog inGet started
Tessl Logo

giuseppe-trisciuoglio/developer-kit

Comprehensive developer toolkit providing reusable skills for Java/Spring Boot, TypeScript/NestJS/React/Next.js, Python, PHP, AWS CloudFormation, AI/RAG, DevOps, and more.

82

Quality

82%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Risky

Do not use without reviewing

Validation failed for skills in this tile
One or more skills have errors that need to be fixed before they can move to Implementation and Discovery review.
Overview
Quality
Evals
Security
Files

security-headers.mdplugins/developer-kit-java/skills/spring-boot-rest-api-standards/references/

Security Headers and CORS Configuration

Security Headers Configuration

Basic Security Headers

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .headers(headers -> headers
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives("default-src 'self'; " +
                                     "script-src 'self' 'unsafe-inline'; " +
                                     "style-src 'self' 'unsafe-inline'; " +
                                     "img-src 'self' data:; " +
                                     "font-src 'self';")
                    .reportOnly(false))
                .frameOptions(frame -> frame
                    .sameOrigin()
                    .deny()) // Use sameOrigin() for same-origin iframes, deny() to completely block
                .httpStrictTransportSecurity(hsts -> hsts
                    .maxAgeInSeconds(31536000) // 1 year
                    .includeSubDomains(true)
                    .preload(true))
                .xssProtection(xss -> xss
                    .headerValue(XssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))
                .contentTypeOptions(contentTypeOptions -> contentTypeOptions
                    .and())
            )
            .cors(cors -> cors
                .configurationSource(corsConfigurationSource()))
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/**").authenticated()
                .anyRequest().permitAll()
            );

        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOriginPatterns(List.of("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Requested-With"));
        configuration.setExposedHeaders(Arrays.asList("X-Total-Count", "X-Content-Type-Options"));
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

Enhanced Security Configuration

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class EnhancedSecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/**")
            .headers(headers -> headers
                .contentSecurityPolicy(csp -> csp
                    .policyDirectives("default-src 'self'; " +
                                     "script-src 'self 'unsafe-inline' 'unsafe-eval'; " +
                                     "style-src 'self 'unsafe-inline'; " +
                                     "img-src 'self' data: https:; " +
                                     "font-src 'self'; " +
                                     "connect-src 'self' https:; " +
                                     "frame-src 'none'; " +
                                     "object-src 'none';"))
                .frameOptions(frameOptions -> frameOptions.sameOrigin())
                .httpStrictTransportSecurity(hsts -> hsts
                    .maxAgeInSeconds(31536000)
                    .includeSubDomains(true)
                    .preload(true)
                    .includeSubDomains(true))
                .permissionsPolicy(permissionsPolicy -> permissionsPolicy
                    .add("camera", "()")
                    .add("geolocation", "()")
                    .add("microphone", "()")
                    .add("payment", "()"))
                .referrerPolicy(referrerPolicy -> referrerPolicy.noReferrer())
                .and())
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .csrf(csrf -> csrf
                .ignoringRequestMatchers("/api/auth/**")
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers(HttpMethod.GET, "/api/users/**").hasAnyRole("USER", "ADMIN")
                .requestMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
            );

        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();

        // Allowed origins (consider restricting to specific domains in production)
        configuration.setAllowedOriginPatterns(List.of("https://yourdomain.com", "https://app.yourdomain.com"));

        // Allowed methods
        configuration.setAllowedMethods(Arrays.asList(
            "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"
        ));

        // Allowed headers
        configuration.setAllowedHeaders(Arrays.asList(
            "Authorization",
            "Content-Type",
            "Accept",
            "X-Requested-With",
            "X-Content-Type-Options",
            "X-Total-Count",
            "Cache-Control"
        ));

        // Exposed headers to client
        configuration.setExposedHeaders(Arrays.asList(
            "X-Total-Count",
            "X-Content-Type-Options",
            "Cache-Control"
        ));

        // Allow credentials
        configuration.setAllowCredentials(true);

        // Cache preflight requests for 1 hour
        configuration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", configuration);
        return source;
    }
}

Content Security Policy (CSP)

Basic CSP Configuration

@Configuration
public class ContentSecurityPolicyConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                    .allowedOrigins("https://yourdomain.com")
                    .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                    .allowedHeaders("*")
                    .exposedHeaders("X-Total-Count")
                    .allowCredentials(true)
                    .maxAge(3600);
            }

            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new ContentSecurityPolicyInterceptor());
            }
        };
    }
}

@Component
public class ContentSecurityPolicyInterceptor implements HandlerInterceptor {

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                          Object handler, ModelAndView modelAndView) throws Exception {
        response.setHeader("Content-Security-Policy",
            "default-src 'self'; " +
            "script-src 'self' 'unsafe-inline'; " +
            "style-src 'self' 'unsafe-inline'; " +
            "img-src 'self' data:; " +
            "font-src 'self'; " +
            "connect-src 'self'; " +
            "frame-src 'none'; " +
            "object-src 'none';");

        response.setHeader("X-Content-Type-Options", "nosniff");
        response.setHeader("X-Frame-Options", "DENY");
        response.setHeader("X-XSS-Protection", "1; mode=block");
    }
}

Advanced CSP with Nonce

@Component
@RequiredArgsConstructor
public class SecurityHeadersFilter extends OncePerRequestFilter {

    private final AtomicLong nonceCounter = new AtomicLong(0);

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                 HttpServletResponse response,
                                 FilterChain filterChain) throws ServletException, IOException {

        // Generate nonce for each request
        String nonce = String.valueOf(nonceCounter.incrementAndGet());

        // Set CSP header with nonce for inline scripts
        response.setHeader("Content-Security-Policy",
            "default-src 'self'; " +
            "script-src 'self' 'nonce-" + nonce + "'; " +
            "style-src 'self' 'unsafe-inline'; " +
            "img-src 'self' data:; " +
            "font-src 'self'; " +
            "connect-src 'self'; " +
            "frame-src 'none'; " +
            "object-src 'none';");

        // Add nonce to request attributes for templates
        request.setAttribute("cspNonce", nonce);

        // Set other security headers
        response.setHeader("X-Content-Type-Options", "nosniff");
        response.setHeader("X-Frame-Options", "SAMEORIGIN");
        response.setHeader("Strict-Transport-Security",
            "max-age=31536000; includeSubDomains; preload");
        response.setHeader("X-Permitted-Cross-Domain-Policies", "none");
        response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");

        filterChain.doFilter(request, response);
    }
}

CORS Configuration

Method-level CORS

@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "https://yourdomain.com", methods = {RequestMethod.GET, RequestMethod.POST})
public class UserController {

    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        // CORS allowed for GET requests
        return ResponseEntity.ok(userService.findAll());
    }

    @PostMapping
    @CrossOrigin(origins = "https://app.yourdomain.com")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        // CORS allowed with different origin for POST requests
        return ResponseEntity.status(HttpStatus.CREATED).body(userService.create(user));
    }
}

Dynamic CORS Configuration

@Configuration
public class DynamicCorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        // Development configuration
        CorsConfiguration devConfig = new CorsConfiguration();
        devConfig.setAllowedOriginPatterns(List.of("*"));
        devConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        devConfig.setAllowedHeaders(Arrays.asList("*"));
        devConfig.setAllowCredentials(true);
        source.registerCorsConfiguration("/api/**", devConfig);

        // Production configuration - restrict to specific domains
        CorsConfiguration prodConfig = new CorsConfiguration();
        prodConfig.setAllowedOriginPatterns(List.of(
            "https://yourdomain.com",
            "https://app.yourdomain.com",
            "https://api.yourdomain.com"
        ));
        prodConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
        prodConfig.setAllowedHeaders(Arrays.asList(
            "Authorization",
            "Content-Type",
            "Accept",
            "X-Requested-With"
        ));
        prodConfig.setExposedHeaders(Arrays.asList("X-Total-Count"));
        prodConfig.setAllowCredentials(true);
        source.registerCorsConfiguration("/api/**", prodConfig);

        return source;
    }
}

Security Headers Best Practices

Essential Headers for Production

  1. Content-Security-Policy: Mitigates XSS attacks
  2. X-Content-Type-Options: Prevents MIME type sniffing
  3. X-Frame-Options: Prevents clickjacking
  4. Strict-Transport-Security: Enforces HTTPS
  5. X-XSS-Protection: Legacy browser XSS protection
  6. Referrer-Policy: Controls referrer information

CSP Examples by Application Type

Blog/Content Site

response.setHeader("Content-Security-Policy",
    "default-src 'self'; " +
    "script-src 'self' https://cdn.jsdelivr.net; " +
    "style-src 'self' 'unsafe-inline'; " +
    "img-src 'self' https: data:; " +
    "font-src 'self'; " +
    "connect-src 'self'; " +
    "frame-src https://www.youtube.com; " +
    "media-src https://www.youtube.com;");

Single Page Application (SPA)

response.setHeader("Content-Security-Policy",
    "default-src 'self'; " +
    "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
    "style-src 'self' 'unsafe-inline'; " +
    "img-src 'self' data:; " +
    "font-src 'self'; " +
    "connect-src 'self' wss:; " +
    "frame-src 'none'; " +
    "object-src 'none';");

API Only

response.setHeader("Content-Security-Policy",
    "default-src 'self'; " +
    "connect-src 'self'; " +
    "frame-src 'none'; " +
    "object-src 'none';");

Security Header Testing

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
class SecurityHeadersTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void securityHeaders_shouldBeSet() throws Exception {
        mockMvc.perform(get("/api/users"))
            .andExpect(header().string("Content-Security-Policy", notNullValue()))
            .andExpect(header().string("X-Content-Type-Options", "nosniff"))
            .andExpect(header().string("X-Frame-Options", notNullValue()))
            .andExpect(header().string("Strict-Transport-Security", notNullValue()));
    }
}

Rate Limiting

Basic Rate Limiting

@Component
public class RateLimitingFilter extends OncePerRequestFilter {

    private final ConcurrentHashMap<String, RateLimit> rateLimits = new ConcurrentHashMap<>();
    private static final long REQUEST_LIMIT = 100;
    private static final long TIME_WINDOW = 60_000; // 1 minute

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                 HttpServletResponse response,
                                 FilterChain filterChain) throws ServletException, IOException {

        String clientIp = request.getRemoteAddr();
        String path = request.getRequestURI();
        String key = clientIp + ":" + path;

        RateLimit rateLimit = rateLimits.computeIfAbsent(key, k -> new RateLimit());

        synchronized (rateLimit) {
            if (System.currentTimeMillis() - rateLimit.resetTime > TIME_WINDOW) {
                rateLimit.count = 0;
                rateLimit.resetTime = System.currentTimeMillis();
            }

            if (rateLimit.count >= REQUEST_LIMIT) {
                response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
                response.getWriter().write("Rate limit exceeded");
                return;
            }

            rateLimit.count++;
        }

        filterChain.doFilter(request, response);
    }

    private static class RateLimit {
        long count = 0;
        long resetTime = System.currentTimeMillis();
    }
}

Token-based Authentication Headers

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                 HttpServletResponse response,
                                 FilterChain filterChain) throws ServletException, IOException {

        try {
            String jwt = getJwtFromRequest(request);

            if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
                UsernamePasswordAuthenticationToken authentication =
                    jwtTokenProvider.getAuthentication(jwt);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }

            filterChain.doFilter(request, response);
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
            response.sendError(HttpStatus.UNAUTHORIZED.value(), "Unauthorized");
        }
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

WebSocket Security

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
               .setAllowedOriginPatterns("https://yourdomain.com")
               .withSockJS();
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new ChannelInterceptor() {
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    // Validate token and authenticate
                    String token = accessor.getFirstNativeHeader("Authorization");
                    if (!isValidToken(token)) {
                        throw new UnauthorizedWebSocketException("Invalid token");
                    }
                }

                return message;
            }
        });
    }
}

plugins

developer-kit-java

skills

README.md

tile.json