class

RustSecurityTagger

Inherits FrameworkTagger < FileHelper < Tagger < Reference < Object

Rust-specific security tagger.

rust_auth already classifies authentication (request guards, extractors, auth middleware), so this tagger covers the other framework-level security protections that a reviewer wants to know are (or are not) in front of an endpoint. Each protection maps onto a tag whose description says whether the configuration is hardened or a risk:

  • cors — CORS middleware. Permissive configs (any origin, wildcard) are flagged as a risk; restricted allow-lists are recorded as informational.
  • rate-limit — request throttling (actix-governor / tower_governor / actix-limitation / tower limit).
  • security-headers — hardening response headers (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, …).
  • body-limit — request body size cap (DoS mitigation). A disabled limit is flagged as a risk.

Detection is two-pronged:

  1. Source middleware — every .rs file is pre-scanned for the builder calls above. The enclosing Actix web::scope("/x") (if any) gives the URL prefix the protection applies to; app-wide middleware (App::new().wrap(..), Router::new().layer(..)) maps to / so it tags every endpoint. Test modules (#[cfg(test)]) and tests//benches//examples/ files are skipped so test-only middleware can't taint real endpoints.

  2. Loco config — Loco wires its middleware in config/*.yaml (middlewares: { cors:, limit_payload:, secure_headers: }) rather than in code, so those files are parsed too and applied app-wide.

Scope mapping errs toward false negatives (a too-narrow prefix tags fewer endpoints) rather than false positives, in keeping with the rest of Noir's tagging.

Constants

BODY_LIMIT_DISABLED_PATTERNS = [{/DefaultBodyLimit::disable\s*\(/, "DefaultBodyLimit::disable()"}]

--- Request body size limit ----------------------------------------

BODY_LIMIT_PATTERNS = [{/DefaultBodyLimit::max\s*\(/, "axum DefaultBodyLimit"}, {/\bDefaultBodyLimit\b/, "axum DefaultBodyLimit"}, {/\bRequestBodyLimitLayer\b/, "tower-http RequestBodyLimitLayer"}, {/\bPayloadConfig\b/, "actix PayloadConfig"}, {/\b(?:Json|Form|Payload)Config\b[^=]*\.limit\s*\(/, "actix config limit"}]
CORS_PERMISSIVE_PATTERNS = [{/Cors::permissive\s*\(/, "Cors::permissive()"}, {/CorsLayer::permissive\s*\(/, "CorsLayer::permissive()"}, {/CorsLayer::very_permissive\s*\(/, "CorsLayer::very_permissive()"}, {/\.allow_any_origin\s*\(/, ".allow_any_origin()"}, {/\.send_wildcard\s*\(/, ".send_wildcard()"}, {/\.allow_origin\s*\(\s*Any\b/, ".allow_origin(Any)"}, {/\.allowed_origin\s*\(\s*"\*"/, "allowed_origin(\"*\")"}]

--- CORS ----------------------------------------------------------- Permissive: any origin is accepted. The classic finding.

CORS_PRESENT_PATTERNS = [{/Cors::default\s*\(/, "actix-cors"}, {/CorsLayer::new\s*\(/, "tower-http CorsLayer"}, {/\.allowed_origin\s*\(/, "actix-cors allow-list"}, {/\.allow_origin\s*\(/, "tower-http allow-list"}, {/\bCorsOptions\b/, "rocket-cors"}]

Configured (restricted) CORS — present but not wide open.

RATE_LIMIT_PATTERNS = [{/\.(?:wrap|layer|route_layer|attach)\s*\(\s*&?\s*[\w:]*Governor\b/, "governor"}, {/\bGovernorLayer\b/, "tower_governor"}, {/\bRateLimitLayer\b/, "tower RateLimitLayer"}, {/\.(?:wrap|layer)\s*\(\s*&?\s*[\w:]*RateLimiter\b/, "rate limiter"}, {/\bactix_limitation\b/, "actix-limitation"}]

--- Rate limiting -------------------------------------------------- Match the application of a limiter (.wrap/.layer) or a Layer type that is only ever applied — never a bare GovernorConfigBuilder value, which is config and would mis-map an app-wide tag onto every endpoint even when the limiter is wrapped onto one scope.

SECURITY_HEADER_PATTERNS = [{/"[Ss]trict-[Tt]ransport-[Ss]ecurity"|\bSTRICT_TRANSPORT_SECURITY\b/, "Strict-Transport-Security (HSTS)"}, {/"[Cc]ontent-[Ss]ecurity-[Pp]olicy"|\bCONTENT_SECURITY_POLICY\b/, "Content-Security-Policy"}, {/"[Xx]-[Ff]rame-[Oo]ptions"|\bX_FRAME_OPTIONS\b/, "X-Frame-Options"}, {/"[Xx]-[Cc]ontent-[Tt]ype-[Oo]ptions"|\bX_CONTENT_TYPE_OPTIONS\b/, "X-Content-Type-Options"}, {/"[Rr]eferrer-[Pp]olicy"|\bREFERRER_POLICY\b/, "Referrer-Policy"}, {/"[Pp]ermissions-[Pp]olicy"|\bPERMISSIONS_POLICY\b/, "Permissions-Policy"}]

--- Security response headers -------------------------------------- The header name — as a quoted literal ("X-Frame-Options") or the http crate's HeaderName constant (X_FRAME_OPTIONS) — is a strong signal the app sets it (request-side reads of these are rare). Framework-agnostic: works for Actix DefaultHeaders, tower-http SetResponseHeaderLayer, and helmet-style crates alike.

Constructors

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

Class methods

target_techs

Kept in lock-step with rust_auth (the guard-support invariant in techs_spec requires every framework-tagger target to be a guard-supported tech, which currently excludes Salvo/Poem).

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

Nested types