class

SpringSecurityTagger

Inherits FrameworkTagger < FileHelper < Tagger < Reference < Object

Spring-specific security tagger.

spring_auth already classifies authentication/authorization (@PreAuthorize/@Secured/@RolesAllowed annotations and HttpSecurity URL rules). This tagger covers the other Spring security signals a reviewer cares about — the protections Spring ships and the deviations from its secure defaults — that map cleanly onto an endpoint:

  • csrf-protection — Spring Security CSRF-protects every state-changing request by default. We flag the state-changing endpoints (POST/PUT/PATCH/DELETE) where that is turned off, either wholesale for a filter chain (csrf().disable(), csrf(AbstractHttpConfigurer::disable), Kotlin csrf { disable() }) or selectively for specific paths (csrf(c -> c.ignoringRequestMatchers("/api/**"))). Common and often intentional for token/stateless APIs, but always worth surfacing.
  • cors — a @CrossOrigin annotation on the handler/controller, or a global WebMvcConfigurer CORS mapping (addMapping(...).allowedOrigins("*")), opts the endpoint out of the browser same-origin default. Wildcard origins (*), especially combined with credentials, are permissive.
  • security-headers — Spring Security adds a sensible default header set (X-Frame-Options DENY, X-Content-Type-Options, Cache-Control, …). We flag the endpoints where those are weakened: clickjacking protection off (frameOptions().disable()) or the whole header writer disabled (headers().disable() / headers(HeadersConfigurer::disable)).
  • input-validation — @Valid / @Validated on the handler applies Bean Validation to the request payload, the primary Spring input-validation control. Surfacing where it IS applied also makes the gaps — handlers taking a body without it — visible by their absence.

CSRF / security-headers / config CORS are detected from the security, MVC, and WebSocket config (pre-scanned once, like spring_auth's URL rules). The @CrossOrigin and input-validation signals are per-endpoint, line-based walks of the handler the endpoint maps to. Cross-file concerns (a custom Filter bean, a bespoke CorsConfigurationSource) are out of scope.

Constants

CSRF_DISABLE = /csrf\s*(?:\(\s*\)\s*\.\s*disable\b|\([^)]*\bdisable|\{[^}]*\bdisable)/

csrf().disable() (fluent), csrf(csrf -> csrf.disable()) / csrf(AbstractHttpConfigurer::disable) (lambda/method-ref), and Kotlin csrf { disable() }. Whole-chain disable.

FRAME_OPTIONS_DISABLE = /frameOptions\s*(?:\(\s*\)\s*\.\s*disable\b|\([^)]*\bdisable|\{[^}]*\bdisable)/

Clickjacking protection off: frameOptions().disable(), frameOptions(f -> f.disable()), Kotlin frameOptions { disable() }.

HEADERS_FULLY_DISABLED = /headers\s*(?:\(\s*\)\s*\.\s*disable\b|\([^)]*::\s*disable)/

Whole default header writer off. Restricted to the unambiguous forms — empty-paren fluent headers().disable() and the method-ref headers(HeadersConfigurer::disable) — so a nested per-header disable such as headers(h -> h.frameOptions(f -> f.disable())) is NOT mistaken for an all-headers-off (that one is caught by FRAME_OPTIONS_DISABLE).

IGNORING_ARGS = /(?:ignoringRequestMatchers|ignoringAntMatchers)\s*\(([^;]*?)\)/

ignoringRequestMatchers(...) / ignoringAntMatchers(...) — CSRF kept on for the chain but skipped for these (absolute) path patterns. Captured across line breaks: the arg group stops at the statement's ;, never crossing into the next statement. The inner quote scan pulls the path literal even when wrapped (e.g. new AntPathRequestMatcher("/api")).

MATCHER_CALL = /\b(?:securityMatcher|antMatcher)\s*\(/

Chain-level request matchers that scope a SecurityFilterChain to a URL subset. requestMatchers(...) is deliberately excluded: inside authorizeHttpRequests {…} it scopes an authorization rule, not the chain, and treating those as CSRF scopes would mis-attribute the rule.

SCOPE_BOUNDARY = /SecurityFilterChain\b|configure\s*\(\s*(?:final\s+)?HttpSecurity/

A SecurityFilterChain bean / WebSecurityConfigurerAdapter.configure body delimits one HttpSecurity scope. A CSRF-disable and any chain-level securityMatcher are associated within the same scope/block.

STATE_CHANGING_METHODS = Set {"POST", "PUT", "PATCH", "DELETE"}

Constructors

new(options : Hash(String, YAML::Any))
Source

Class methods

target_techs
Source

Instance methods

perform(endpoints : Array(Endpoint)) : Array(Endpoint)

The per-endpoint shape: look at each endpoint, tag in place, hand the array back. Fifteen framework taggers carried a byte-identical copy of this; they now declare only check_endpoint.

Not every framework tagger fits it — eleven still override perform because they need a pre-scan over the project (config files, middleware registration) before the per-endpoint pass, or they group endpoints first. Those keep their own.

Source