class RustSecurityTagger

Overview

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:

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.

Defined in:

tagger/framework_taggers/rust/rust_security.cr

Constant Summary

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

Class Method Summary

Instance Method Summary

Instance methods inherited from class FrameworkTagger

collect_files_by_extension(extension : String) : Array(String) collect_files_by_extension, read_file(path : String) : String | Nil read_file, read_source_context(endpoint : Endpoint) : Array(SourceContext) read_source_context, static_asset_route?(url : String) : Bool static_asset_route?

Constructor methods inherited from class FrameworkTagger

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

Class methods inherited from class FrameworkTagger

target_techs : Array(String) target_techs

Instance methods inherited from module FileHelper

all_files : Array(String) all_files, get_files_by_extension(extension : String) : Array(String) get_files_by_extension, get_files_by_prefix(prefix : String) : Array(String) get_files_by_prefix, get_files_by_prefix_and_extension(prefix : String, extension : String) : Array(String) get_files_by_prefix_and_extension, get_public_dir_files(base_path : String, folder : String) : Array(String) get_public_dir_files, get_public_files(base_path : String, anchors : Array(String) = ["shard.yml", "Gemfile"]) : Array(String) get_public_files

Instance methods inherited from class Tagger

name : String name, perform(endpoints : Array(Endpoint)) : Array(Endpoint) perform

Constructor methods inherited from class Tagger

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

Constructor Detail

def self.new(options : Hash(String, YAML::Any)) #

[View source]

Class Method Detail

def self.target_techs : Array(String) #

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).


[View source]

Instance Method Detail

def perform(endpoints : Array(Endpoint)) : Array(Endpoint) #

[View source]