module NoirAIContext

Overview

NoirAIContext enriches each endpoint with an AIContext — the guards / callees / sinks / validators / signals an LLM (or a human triage pass) needs to reason about the route. The heavy lifting lives in the collaborators required above:

This file keeps only the public module surface: building context for a batch of endpoints, and the --ai-context=… feature filter.

Extended Modules

Defined in:

ai_context/augmentor.cr
ai_context/builder.cr
ai_context/pattern_definition.cr
ai_context/pattern_matcher.cr
ai_context/patterns.cr
ai_context/source_reader.cr

Constant Summary

AUTH_GUARD_PATTERNS = [PatternDefinition.new("auth_guard", "Potential authentication guard — checks who the caller is", 56, source_patterns: [/passport\.authenticate/i, /expressjwt/i, /\bauthenticate\w*\b/i, /\bverifyToken\b/i, /\brequireAuth\b/i, /\blogin_required\b/i, /\bjwt_required\b/i, /\brequire_login\b/i, /\blogged_in_user\b/i, /Depends\s*\(\s*get_current_/i, /before_action\s+:\w*auth/i, /\.Use\s*\(\s*\w*Auth\w*/i])] of PatternDefinition

Authentication guard — "verifies who you are". Login required, JWT verification, session check.

AUTHZ_GUARD_PATTERNS = [PatternDefinition.new("authz_guard", "Potential authorization guard — checks what the authenticated caller may do", 60, source_patterns: [/\bauthorize\w*\b/i, /\bcheckPermission\b/i, /\bhasAuthority\b/i, /\brequiresRole\b/i, /\brole_required\b/i, /\badmin_required\b/i, /\brequire_role\b/i, /\brequire_any_role\b/i, /\brequire_all_roles\b/i, /\buser_has_role\b/i, /@PreAuthorize\b/, /@RolesAllowed\b/, /@Secured\b/, /Security\s*\(/, /\bPundit\b.*\bauthorize\b/i, /\bcan\?\s*\(/, /\b(can|cannot)\s+:[\w_!?]+/, /\bability\.\w+/i])] of PatternDefinition

Authorization guard — "verifies what you can do". Role, permission, ability, policy checks. Distinct from authentication because the common security gap is "logged in but no further check" → horizontal / vertical privilege escalation.

CORS_CREDENTIALS_PATTERN = /\b(?:credentials|allow[_-]?credentials|allowCredentials)\s*[:=]\s*(?:true|['"]true['"])/i
CORS_WILDCARD_PATTERN = /\b(?:origin|origins?)\s*[:=]\s*['"]\*['"]/i

CORS open-with-credentials. Wildcard * origin combined with credentials: true is the textbook CORS misconfiguration — browsers actually block this combination at the spec level for security reasons, so seeing it in code means either the config is broken or the developer is reaching for something the spec forbids. Either way, worth a sharp signal.

The check is order-independent across two regex windows so origin: '*' followed by credentials: true and the reverse both fire.

CSRF_EXEMPT_PATTERNS = [PatternDefinition.new("csrf_exempt", "Explicit CSRF protection bypass — review whether the exemption is justified", 80, source_patterns: [/@csrf_exempt\b/, /\bcsrf_exempt\s*\(/, /protect_from_forgery\s+with:\s*:null_session/, /\.disable\s*\(\s*\.?csrf/i, /csrfProtection:\s*false/i, /@SuppressWarnings\(.*csrf.*\)/i])] of PatternDefinition

Negative protection: explicit CSRF bypass. Emit as a SIGNAL (not a guard) so the LLM knows protection is intentionally disabled here and the endpoint warrants extra scrutiny.

CSRF_GUARD_PATTERNS = [PatternDefinition.new("csrf_guard", "Potential CSRF protection — token check, SameSite cookie policy, or origin check", 70, source_patterns: [/\bprotect_from_forgery\b/, /\bcsrf_token\b/, /\bcsrfProtection\b/, /\bCsrf(Token)?Filter\b/, /\bCSRFGuard\b/, /verify_authenticity_token/, /@csrf_protect\b/, /\bSameSite\b.*\bStrict\b/i])] of PatternDefinition

CSRF protection. Different layer from authn/authz — protects against cross-site request forgery via tokens / SameSite cookies / origin checks. Absent on state-changing endpoints is usually worth a review note.

GUARD_PATTERN_GROUPS = [AUTH_GUARD_PATTERNS, AUTHZ_GUARD_PATTERNS, CSRF_GUARD_PATTERNS, RATE_LIMIT_GUARD_PATTERNS]
JWT_UNSAFE_PATTERNS = [PatternDefinition.new("jwt_unsafe", "JWT verification is disabled or weakened — token contents become attacker-controlled", 82, source_patterns: [/\bjwt\.decode\s*\([^)]*verify\s*=\s*False/i, /\bverify_signature['"]?\s*:\s*False/i, /\bverify_signature\s*=\s*False/i, /algorithms?['"]?\s*[:=]\s*\[?\s*['"]none['"]/i, /algorithm['"]?\s*[:=]\s*['"]none['"]/i, /ignoreExpiration['"]?\s*:\s*true/i, /Algorithm\.none\s*\(/, /\bJWT\.decode\s*\([^)]*verify\s*=>\s*false/i, /\bJWT\.decode\s*\([^)]*verify:\s*false/i])] of PatternDefinition

JWT verification bypass — explicit "trust whatever we received without checking" shapes across pyjwt / jsonwebtoken / java-jwt / ruby-jwt. Each pattern is a single-line dead giveaway, surfaced as a sharp signal alongside csrf_exempt / cors_open. The kind is jwt_unsafe so consumers can sort it next to csrf_exempt.

MOBILE_SINK_KINDS = Set {"webview_load", "intent_redirect"}

Sink kinds that only make sense on a mobile deep-link endpoint — a WebView load or an intent launch fed by inbound deep-link data. They live in the global SINK_PATTERNS so the catalog stays in one place, but the Builder evaluates them only for endpoint.mobile? endpoints: an HTTP route handler never opens a WebView or launches an Android intent, so firing these against every route is conceptually wrong (and a latent FP source as the name/source patterns broaden).

NON_MOBILE_SINK_PATTERNS = SINK_PATTERNS.reject do |pattern| MOBILE_SINK_KINDS.includes?(pattern.kind) end

The non-mobile view of the sink catalog, computed once. Used for HTTP endpoints so the two mobile-only sinks above are never matched against server-side route/callee snippets.

PARAM_PATTERNS = [PatternDefinition.new("credential_input", "Credential-bearing input; review secret handling, logging, and auth bypass paths", 86, name_patterns: [/\b(pass(word)?|token|secret|api[_-]?key|authorization|session|cookie|jwt|bearer)\b/i]), PatternDefinition.new("identifier_input", "Identifier-like input frequently drives authorization and object lookup decisions", 74, name_patterns: [/\bid\b/i, /\buser(_id)?\b/i, /\baccount(_id)?\b/i, /\border(_id)?\b/i, /\bprofile\b/i, /\bdoc(ument)?\b/i, /\bproject(_id)?\b/i]), PatternDefinition.new("redirect_input", "Redirect or callback-like input may affect navigation or outbound requests", 76, name_patterns: [/\b(url|uri|redirect|return|next|continue|dest|destination|callback)\b/i]), PatternDefinition.new("file_input", "File-like input may influence upload, download, or path traversal behavior", 78, name_patterns: [/\b(file|upload|attachment|image|document|filename|filepath|path)\b/i]), PatternDefinition.new("query_builder_input", "Query-builder-like input can influence filtering, sorting, or data access clauses", 70, name_patterns: [/\b(sort|order|filter|where|search|select|field|column)\b/i]), PatternDefinition.new("pii_input", "Personally identifiable information; review storage, logging, retention, and access controls", 78, name_patterns: [/\b(email|e[-_]?mail|phone|mobile|ssn|tax[_-]?id|dob|date[_-]?of[_-]?birth|birthdate|nin|national[_-]?id|passport|driver[_-]?license)\b/i]), PatternDefinition.new("html_content_input", "Rich-content input may flow into HTML output; review escaping or schema validation", 72, name_patterns: [/\b(body|description|html|message|comment|note|memo|markdown|rich[_-]?text)\b/i, /\b(html[_-]?content|content[_-]?html|markdown[_-]?content|content[_-]?markdown|rich[_-]?content|content[_-]?rich)\b/i]), PatternDefinition.new("code_input", "Code/script-like input often flows into eval, template, or interpreter sinks", 80, name_patterns: [/\b(script|formula|expression|command|cmd|template|query[_-]?string)\b/i, /\b(source[_-]?code|script[_-]?code|template[_-]?code|code[_-]?(snippet|block|body|source))\b/i])] of PatternDefinition
RATE_LIMIT_GUARD_PATTERNS = [PatternDefinition.new("rate_limit_guard", "Potential rate-limit / throttling layer", 64, source_patterns: [/\bThrottle\b/, /\bRateLimiter\b/, /@limits?\s*\(/, /@limiter\b/, /\bflask[-_]limiter\b/i, /\bslowapi\b/i, /\bratelimit\b/i, /\.throttle\s*\(/, /Bucket(Token)?\b/])] of PatternDefinition

Rate limiting / throttling. Often the only defence on credential-handling endpoints against credential stuffing / brute-force. Confidence is moderate because not every endpoint needs rate limiting (only credential / lookup-style ones).

SINK_PATTERNS = [PatternDefinition.new("data_store_query", "Potential database or graph/document-store query sink inferred from code or callee name", 76, name_patterns: [/\b(?:mongo|client|neo4jClient)\.query\b/i], source_patterns: [/\b(?:mongo|client|neo4jClient)\.query\s*(?:<|\()/i]), PatternDefinition.new("sql", "Potential SQL/data-store sink inferred from code or callee name", 78, name_patterns: [/raw_?sql/i, /find_?by_?sql/i, /\bquery\b/i, /\bexecute\b/i, /\bselect\b/i], source_patterns: [/find_by_sql/i, /\braw(sql|_sql)\b/i, /\bselect\b.+\bfrom\b/i, /\bexecute(Query|Sql)?\b/i]), PatternDefinition.new("command_exec", "Potential command execution sink inferred from code or callee name", 82, name_patterns: [/\bexec\b/i, /\bsystem\b/i, /\bspawn\b/i, /\bpopen\b/i, /\bshell\b/i], source_patterns: [/\bexec\s*\(/i, /\bsystem\s*\(/i, /\bspawn\s*\(/i, /\bpopen\s*\(/i, /ProcessBuilder/i, /Runtime\.getRuntime/i, /subprocess\./i]), PatternDefinition.new("file_io", "Potential file I/O sink inferred from code or callee name", 68, name_patterns: [/\bfile\b/i, /\bread\b/i, /\bupload\b/i, /\bdownload\b/i], source_patterns: [/\bFile\.(open|read|write)/, /\bread(File|_text)\b/i, /\bwrite(File|_text)\b/i, /\bsend_file\b/i, /\bsendFile\b/i, /\bdownload\b/i, /\bupload\b/i]), PatternDefinition.new("redirect", "Potential redirect sink inferred from code or callee name", 74, name_patterns: [/\bredirect\b/i, /\bredirect_to\b/i], source_patterns: [/\bredirect(_to)?\s*\(/i, /\bres\.redirect\b/i, /Response\.Redirect/i]), PatternDefinition.new("template_render", "Potential template/render sink inferred from code or callee name", 62, name_patterns: [/\brender\b/i, /\btemplate\b/i, /\bhtml\b/i, /\brespond\b/i], source_patterns: [/\brender(_template)?\s*\(/i, /\breturn\s+render\b/i, /\brespond_to\b/i]), PatternDefinition.new("outbound_http", "Potential outbound HTTP/client sink inferred from code or callee name", 64, name_patterns: [/\b(?:reqwest|hyper|hyper_util|ureq|surf|isahc|attohttpc|awc)::[A-Za-z_][A-Za-z0-9_:!]*/i, /\bhttp\b/i, /\bfetch\b/i, /\bclient\b/i, /\baxios\b/i, /\bgetForObject\b/i, /\bexchange\b/i], source_patterns: [/requests\.(get|post|put|delete)/i, /\bfetch\s*\(/i, /\baxios\./i, /\bhttp\.(Get|Post|NewRequest)/, /\bclient\.(get|post|request)/i, /\b\w+\.(getForObject|postForObject|exchange)\s*\(/i, /\b(?:reqwest|ureq|surf|isahc|attohttpc)::(?:get|post|put|delete|patch|request)\s*(?:::<[^>]+>)?\s*\(/i, /\b(?:hyper|hyper_util|awc)::[A-Za-z0-9_:]*Client\b/]), PatternDefinition.new("xss", "Potential XSS sink — unsafe HTML output bypassing the framework's auto-escaping", 75, name_patterns: [/\bhtml_safe\b/i, /\bmark_safe\b/i, /\bdangerouslySetInnerHTML\b/, /\bformat_html\b/i], source_patterns: [/\.innerHTML\s*=/, /\.outerHTML\s*=/, /\bdocument\.write\s*\(/, /\bdangerouslySetInnerHTML\b/, /\{@html\b/, /\bv-html\b/, /\bMarkup\s*\(/, /\bmark_safe\s*\(/, /\|\s*safe\b/, /\.html_safe\b/, /\braw\s*\(/i, /\bbypassSecurityTrust(Html|Script|Style|Url|ResourceUrl)\b/]), PatternDefinition.new("deserialization", "Potential unsafe deserialization — attacker-controlled bytes revive into a live object graph (RCE class)", 86, name_patterns: [/\bpickle\.loads\b/, /\bcPickle\.loads\b/, /\bdill\.loads\b/, /\bjsonpickle\.decode\b/, /\bunserialize\b/, /\bMarshal\.load\b/, /\breadObject\b/], source_patterns: [/\bpickle\.loads\s*\(/, /\bcPickle\.loads\s*\(/, /\bdill\.loads\s*\(/, /\bjsonpickle\.decode\s*\(/, /\byaml\.load\s*\(/, /\bMarshal\.load\s*\(/, /\bunserialize\s*\(/, /\bObjectInputStream\b.*\breadObject\b/m, /\bXMLDecoder\b/, /\bXStream\b.*\bfromXML\b/, /\bBinaryFormatter\b.*\bDeserialize\b/, /\bLosFormatter\b/, /\bnode-serialize\b/]), PatternDefinition.new("template_injection", "Potential server-side template injection (SSTI) — template string itself may be attacker-controlled", 80, name_patterns: [/\brender_template_string\b/, /\bfrom_string\b/, /\bTemplate\.compile\b/], source_patterns: [/\brender_template_string\s*\(/, /\bEnvironment\(\)\.from_string\s*\(/, /\bjinja2\.Template\s*\(/, /\bERB\.new\s*\(/, /\bLiquid::Template\.parse\s*\(/, /\bHandlebars\.compile\s*\(/, /\b_\.template\s*\(/, /\bVelocity\.evaluate\s*\(/]), PatternDefinition.new("code_eval", "Potential in-process code evaluation — attacker-controlled source executed by the runtime (RCE class)", 84, name_patterns: [/\binstance_eval\b/, /\bclass_eval\b/, /\bcreate_function\b/], source_patterns: [/\beval\s*\(/, /\bexec\s*\(/, /\bcompile\s*\([^)]*,\s*['"]exec['"]/, /\bnew\s+Function\s*\(/, /\bFunction\s*\([^)]*\)\s*\(/, /\bsetTimeout\s*\(\s*['"]/, /\bsetInterval\s*\(\s*['"]/, /\binstance_eval\s*\(/, /\bclass_eval\s*\(/, /\bbinding\.eval\s*\(/, /\bcreate_function\s*\(/, /\bassert\s*\(\s*\$/, /\bScriptEngine\b.*\beval\b/]), PatternDefinition.new("mass_assignment", "Potential mass-assignment — request params written into a model without an explicit field allowlist", 60, name_patterns: [/\bupdate_attributes\b/, /\bassign_attributes\b/], source_patterns: [/\bupdate_attributes\s*\(/, /\bassign_attributes\s*\(/, /\.create\s*\(\s*params\b/, /\.create\s*\(\s*req\.body\b/, /\.update\s*\(\s*params\b/, /\.update\s*\(\s*req\.body\b/, /Object\.assign\s*\([^,]+,\s*req\.body\b/, /_\.merge\s*\([^,]+,\s*req\.body\b/]), PatternDefinition.new("crypto_weak", "Potential weak cryptographic primitive in a security-relevant context (MD5/SHA1 for auth, DES, ECB, non-CSPRNG random)", 56, name_patterns: [/\bDigest::MD5\b/, /\bDigest::SHA1\b/, /\bhashlib\.md5\b/, /\bhashlib\.sha1\b/, /\bMessageDigest\.getInstance\b/, /\b(?:Random|random)\.next(Int|Long|Bytes)?\b/], source_patterns: [/\bDigest::(MD5|SHA1)\b/, /\bhashlib\.(md5|sha1)\s*\(/, /\bMessageDigest\.getInstance\s*\(\s*['"](MD5|SHA-?1)['"]/, /\bCipher\.(getInstance|new)\s*\(\s*['"]DES/, /\bAES\/ECB\b/, /\bMode::ECB\b/, /\bRC4\b/, /\bMath\.random\s*\(\s*\)/, /\bRandom\s*\(\s*\)\s*\.\s*next(Int|Long|Bytes)?\s*\(/]), PatternDefinition.new("webview_load", "WebView load of a potentially attacker-controlled URL/HTML — a deep-link XSS/SSRF surface on mobile", 80, name_patterns: [/\.loadUrl\b/i, /\.loadData(WithBaseURL)?\b/i, /\.evaluateJavascript\b/i, /\b(?:wk)?web_?view\w*\??\.load\b/i], source_patterns: [/\.loadUrl\s*\(/i, /\.loadData(WithBaseURL)?\s*\(/i, /\.evaluateJavascript\s*\(/i, /\b(?:wk)?web_?view\w*\??\.load\s*\(/i, /\.load\s*\(\s*URLRequest/]), PatternDefinition.new("intent_redirect", "Intent launched from inbound deep-link data — intent redirection / component-hijack surface", 70, name_patterns: [/\bstartActivity(ForResult)?\b/, /\bsendBroadcast\b/, /\bstartService\b/, /\bbindService\b/], source_patterns: [/\bstartActivity(ForResult)?\s*\(/, /\bsendBroadcast\s*\(/, /\bstartService\s*\(/])] of PatternDefinition
VALIDATOR_PATTERNS = [PatternDefinition.new("validation", "Potential validation step inferred from code or callee name", 64, name_patterns: [/\bvalidate\w*\b/i, /\bvalidator\w*\b/i, /\bverify\w*\b/i, /\bpermit\w*\b/i], source_patterns: [/\bvalidate\w*\s*\(/i, /\bvalidator\b/i, /\bpermit\s*\(/i, /\bverify\w*\s*\(/i]), PatternDefinition.new("expiry_validation", "Expiry/validity-window check inferred from token or session lifetime comparison", 72, name_patterns: [/\b(expiry|expires?|expired|validUntil|notAfter)\w*\.(isBefore|isAfter)\b/i], source_patterns: [/\b(expiry|expires?|expired|validUntil|notAfter)\w*\s*\.\s*(isBefore|isAfter)\s*\(/i]), PatternDefinition.new("uniqueness_validation", "Uniqueness or duplicate-prevention validation inferred from guard helper naming", 70, name_patterns: [/^checkIf\w*(Unique|Exists?|Duplicate)\w*OrThrow$/i, /^ensure\w*(Unique|Exists?|Duplicate)\w*$/i, /^assert\w*(Unique|Exists?|Duplicate)\w*$/i, /\b\w+(?:Repository|Repo|Dao)\.findBy(?!Id\b)(?!Id[A-Z])\w+\b/], source_patterns: [/\bcheckIf\w*(Unique|Exists?|Duplicate)\w*OrThrow\s*\(/i, /\bensure\w*(Unique|Exists?|Duplicate)\w*\s*\(/i, /\bassert\w*(Unique|Exists?|Duplicate)\w*\s*\(/i, /\b\w+(?:Repository|Repo|Dao)\.findBy(?!Id\b)(?!Id[A-Z])\w+\s*\([^\n]*\)[\s\S]{0,480}\b(?:isEmpty|isNotEmpty|isPresent|AlreadyExist|Duplicate|Unique)\b/i]), PatternDefinition.new("existence_validation", "Existence or duplicate-precondition check inferred from repository existence lookup", 70, name_patterns: [/\b\w+Repository\.existsBy\w+\b/, /\b\w+Repo\.existsBy\w+\b/, /\b\w+Dao\.existsBy\w+\b/], source_patterns: [/\b\w+(?:Repository|Repo|Dao)\.existsBy\w+\s*\(/]), PatternDefinition.new("credential_hashing", "Credential hashing step inferred from Spring Security password encoder usage", 74, name_patterns: [/\b(?:passwordEncoder|PasswordEncoder|BCryptPasswordEncoder|Argon2PasswordEncoder|Pbkdf2PasswordEncoder|SCryptPasswordEncoder)\.encode\b/]), PatternDefinition.new("credential_verification", "Credential verification step inferred from Spring Security password encoder usage", 74, name_patterns: [/\b(?:passwordEncoder|PasswordEncoder|BCryptPasswordEncoder|Argon2PasswordEncoder|Pbkdf2PasswordEncoder|SCryptPasswordEncoder)\.matches\b/], source_patterns: [/\b\w*(?:PasswordEncoder|passwordEncoder)\w*\.matches\s*\(/]), PatternDefinition.new("cookie_httponly", "Cookie is configured HttpOnly, reducing client-side script access to session or refresh tokens", 72, source_patterns: [/\b\w+\.isHttpOnly\s*=\s*true\b/, /\b\w+\.setHttpOnly\s*\(\s*true\s*\)/]), PatternDefinition.new("cookie_secure", "Cookie is configured Secure, restricting transport to HTTPS", 72, source_patterns: [/\b\w+\.secure\s*=\s*true\b/, /\b\w+\.setSecure\s*\(\s*true\s*\)/]), PatternDefinition.new("query_parameter_binding", "Database query parameters are bound through the client API instead of string-concatenated into the query", 74, source_patterns: [/\.bind\s*\([^)]*\)\s*\.to\s*\([^)]*\)/, /\.bind\s*\([^)]*\)\s*\.with\s*\{/, /\.bind\s*\(\s*\d+\s*,\s*[^)]*\)/]), PatternDefinition.new("sanitization", "Potential sanitization or escaping step inferred from code or callee name", 68, name_patterns: [/\bsanitize\b/i, /\bescape\b/i, /\bencode\b/i, /\bnormalize\b/i, /\bclean\b/i], source_patterns: [/\bsanitize\w*\s*\(/i, /\bescape\w*\s*\(/i, /\bhtml_escape\b/i, /\bnormalize\w*\s*\(/i, /\bclean\w*\s*\(/i]), PatternDefinition.new("schema_validation", "Schema-based input validation (strong shape guarantee — rejects fields outside the declared schema)", 78, name_patterns: [/\bBaseModel\b/, /\bparse_obj\b/, /\bmodel_validate\b/, /\bz\.object\b/, /\bjoi\.object\b/i, /\bjsonschema\b/i, /\bmarshmallow\b/i], source_patterns: [/\bclass\s+\w+\(\s*BaseModel\s*\)/, /\bpydantic\.BaseModel\b/, /\b\w+:\s*BaseModel\b/, /\.parse_obj\s*\(/, /\.model_validate\s*\(/, /\.parse_raw\s*\(/, /\bz\.(object|string|number|array)\s*\(/, /\bschema\.parse\s*\(/, /\bYup\.(object|string|number)\b/, /\bjoi\.object\s*\(/i, /\bJoi\.validate\s*\(/, /\bSchema\(\)\.load\s*\(/, /\bjsonschema\.validate\s*\(/, /\bvalidate\s*\(\s*\w+,\s*schema\s*\)/i]), PatternDefinition.new("type_coercion", "Type coercion on input — narrower than schema validation but still constrains the value", 50, name_patterns: [/\bparseInt\b/, /\bparseFloat\b/, /\bInteger\b/, /\bFloat\b/, /\btoInt\b/, /\bto_i!\b/], source_patterns: [/\bparseInt\s*\(/, /\bparseFloat\s*\(/, /\bInteger\s*\(/, /\bFloat\s*\(/, /\b\.to_i!\b/, /\b\.to_f!\b/, /\bNumber\s*\(/, /\bint\s*\(\s*request\./, /@(?:PathVariable|DestinationVariable|RequestParam|RequestHeader|CookieValue|Argument)(?:\s*\([^)]*\))?\s+(?:\w+\s*:\s*)?(?:U?Long|U?Int|Short|Byte|Double|Float|BigInteger|BigDecimal|UUID)\??(?=\b|[^A-Za-z0-9_])/, /@(?:PathVariable|DestinationVariable|RequestParam|RequestHeader|CookieValue|Argument)(?:\s*\([^)]*\))?\s+(?:final\s+)?(?:long|int|short|byte|double|float|Long|Integer|Short|Byte|Double|Float|BigInteger|BigDecimal|UUID)\s+\w+\b/]), PatternDefinition.new("allowlist_check", "Allowlist / membership check — value compared against a known-good fixed set", 62, name_patterns: [/\bwhitelist\b/i, /\ballowlist\b/i, /\bpermitted_values\b/i], source_patterns: [/\bwhitelist\b/i, /\ballowlist\b/i, /\ballowed[_-]?values\b/i, /\bpermitted_values\b/i, /\bif\s+\w+\s+in\s+[A-Z_][A-Z0-9_]+\b/, /\b\bALLOWED_[A-Z_]+\.(include|contains)/, /\.include\?\s*\(\s*\w+\s*\)\s*(?:or|\|\|)/, /\bin\s*\[[\w\s,'"]+\]/i])] of PatternDefinition

Instance Method Summary

Instance Method Detail

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

[View source]
def apply_feature_filter(endpoints : Array(Endpoint), features : Set(String)) #

Clears AIContext buckets the user didn't request. Mirrors the plain-text builder's feature filter so JSON/YAML/SARIF/Postman/ OAS — which serialize the struct directly — show the same subset the user asked for via --ai-context=guards,sinks. features follows the canonical bucket names: "guards", "callee", "sources", "sinks", "validators", "signals". An empty set or one containing every name is a no-op.


[View source]
def parse_feature_set(raw : String) : Set(String) #

Parses the comma-separated --ai-context=… value into the set of bucket names that should survive the filter. Empty value or "all" means every bucket (the no-op set).


[View source]