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:
PatternDefinition/Patterns— the declarative detection catalogs (sinks, validators, guards, parameter classes).SourceReader— cached source reads + snippet extraction.PatternMatcher— the stateless name/snippet detection engine.Builder— orchestrates every populate step per endpoint.
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.crai_context/builder.cr
ai_context/features.cr
ai_context/pattern_definition.cr
ai_context/pattern_matcher.cr
ai_context/patterns.cr
ai_context/source_reader.cr
Constant Summary
-
ACCEPTED_FEATURES =
FEATURES + [FEATURE_ALL] -
Every name
--ai-context=accepts, including theallalias. -
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 withcredentials: trueis 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 bycredentials: trueand 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.
-
FEATURE_ALL =
"all" -
Accepted from the user as "every bucket". Not a bucket itself, so it is kept out of
FEATURESand added back only where user input is validated. -
FEATURES =
["guards", "callee", "sources", "sinks", "validators", "signals"] of ::String -
The
--ai-contextbucket vocabulary, in emission order.There used to be four copies of this list — two in
options.cr(the CLI help text and the validator), one inNoirAIContext.parse_feature_set, one inOutputBuilderCommon#ai_context_feature_filter— and they disagreed.sourceswas in the two that decide what gets emitted and missing from the two that decide what the CLI accepts, so--ai-context=sourceswas rejected for a bucket the augmentor and every output builder fully implement. The only way to see sources was to ask for all of them, and--ai-context=guards,sourceswas unreachable.Anything that needs the vocabulary reads it here.
-
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_unsafeso 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
Class Method Summary
- .all_features : Set(String)
-
.parse_feature_set(raw : String) : Set(String)
Parses a
--ai-context=…value into the set of buckets that survive the filter. -
.unknown_features(raw : String) : Array(String)
Names in
rawthat are outside the accepted vocabulary, in the user's original spelling so an error message can echo the typo as written.
Instance Method Summary
- #apply(endpoints : Array(Endpoint)) : Array(Endpoint)
-
#apply_feature_filter(endpoints : Array(Endpoint), features : Set(String))
Clears AIContext buckets the user didn't request.
Class Method Detail
Parses a --ai-context=… value into the set of buckets that survive the
filter. An empty value, or one naming all, means every bucket.
Names are case-folded to match the CLI, which lowercases before storing:
a config-file ai_context_features: "Guards" reaches here unmodified and
would otherwise match no bucket at all.
Names in raw that are outside the accepted vocabulary, in the user's
original spelling so an error message can echo the typo as written.
--ai-context= validates through this; so does the effective option
value after config + CLI are merged. Without the second check a typo in
a config file's ai_context_features reached .parse_feature_set,
matched no bucket, and silently emptied every endpoint's AI context —
the same value on the command line was a hard error.
Instance Method Detail
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 holds bucket names from NoirAIContext::FEATURES. An empty
set, or one naming every bucket, is a no-op.