class

NoirAIContext::Builder

Inherits Reference < Object

Builds an AIContext for each endpoint by running every populate step (route / technology / method / param / tag / callee / source scan / guard-absence / combination signals) over the endpoint and its source. Pattern detection is delegated to PatternMatcher and source/snippet reading to a per-run SourceReader (file cache).

Constants

ACCOUNT_NAMESPACE_SEGMENT_PATTERN = /(?:^|\/)(user|users|account|accounts|auth)(?:\/|$)/i
ACCOUNT_REGISTER_SEGMENT_PATTERN = /(?:^|\/)(register|sign[_-]?up|signup)(?:\/|$)/i
ANNOTATION_SEGMENT = /\A\d+: (?:@|#\[|\[)/
AUTH_LIFECYCLE_SEGMENT_PATTERN = /(?:^|\/)(login|log[_-]?in|logout|log[_-]?out|authenticate|refresh[_-]?token|verify|verification|reset[_-]?password|forgot[_-]?password|password[_-]?reset)(?:\/|$)/i
AUTH_NAMESPACE_PATTERN = /(?:^|\/)auth(?:\/|$)/i
BODY_JUDGED_SINK_KINDS = Set {"template_render", "deserialization"}

Sink kinds whose callee names are words a project reuses for its own helpers (render, unserialize). The other kinds keep the name match: their catalogs miss common body forms (Python open(, Django HttpResponseRedirect), so judging a helper's body would drop real ones.

BODY_LIKE_PARAM_TYPES = Set {"json", "form"}
CALLEE_SNIPPET_RADIUS = 1
CLASS_HEADER = /\A\s*(?:(?:public|private|protected|internal|static|final|abstract|sealed|open|data|partial|export|default|inner)\s+)*(?:class|object|module|struct|interface|trait|impl|enum|record|defmodule)\b/
COMPANION_HEADER = /\A\s*(?:(?:private|internal|public)\s+)?companion\s+object\b/

A Kotlin companion object belongs to its class: a bare call reaches it.

CREDENTIAL_INPUT_NAME_PATTERNS = PARAM_PATTERNS.find! do |pattern| pattern.kind == "credential_input" end.name_patterns
CREDENTIAL_KEY_IN_RESPONSE = Regex.new("(?:[^\"'\\w](#{CREDENTIAL_NOUNS_IN_RESPONSE})|([\"'])(#{CREDENTIAL_NOUNS_IN_RESPONSE})\\2)\\s*:", Regex::Options::IGNORE_CASE)

Capture 1 is the bare-key noun, capture 3 the quoted-key noun; exactly one of them is set per match.

CREDENTIAL_NOUNS_IN_RESPONSE = "password|passwd|token|secret|api[_-]?key|session_id|access_token|refresh_token|private_key"

Sensitive-response detection runs as a two-step check on the route-scope snippet:

  1. RESPONSE_EMITTER_PATTERN — the snippet serializes a response, either through a helper (res.json, jsonify, render json:, to_json, …) or by returning a literal (return {...}).
  2. CREDENTIAL_KEY_IN_RESPONSE — the snippet also has a credential noun as a key, in either of the two spellings a response literal uses: bare — { token: … } (JS/TS shorthand, Ruby, YAML) quoted — { "token": … } (JSON, Python dicts, Go maps, PHP arrays, any serialized body)

Both have to match in the same scope. The earlier single-regex version was too loose and caught english prose mentioning credentials in response strings, so the fix required the noun not be preceded by a quote. That over-corrected: it also excluded every quoted key, which is the dominant shape in most languages, and the signal quietly stopped firing on them.

The two branches below keep the original guard for bare keys while admitting quoted ones, and stay narrow because the quoted branch requires the string to close immediately after the noun. { message: "Set X-API-KEY header" } still doesn't fire — the noun sits mid-string, so no closing quote follows it.

CREDENTIAL_SOURCE_PATTERNS = [/(?:const|let|var)\s*\{[^}]*\b(password|passwd|token|secret|api[_-]?key|jwt|bearer)\b[^}]*\}\s*=\s*req\.body/i, /\breq\.body\.(password|passwd|token|secret|api[_-]?key|jwt|bearer)\b/i, /\brequest\.(form|json|data)\[\s*['"](password|passwd|token|secret|api[_-]?key|jwt|bearer)['"]/i, /\b(payload|credentials|creds|input|body)\.(password|passwd|token|secret|api[_-]?key|jwt|bearer)\b/i, /\b(?:r|req|request)\.(?:FormValue|PostFormValue)\s*\(\s*['"](password|passwd|token|secret|api[_-]?key|jwt|bearer)['"]/i, /\b(?:c|ctx|context)\.(?:PostForm|DefaultPostForm|FormValue|QueryParam)\s*\(\s*['"](password|passwd|token|secret|api[_-]?key|jwt|bearer)['"]/i, /\b(?:context\.Request|HttpContext\.Request|Request)\.(?:Form|Query|Headers|Cookies)\s*\[\s*['"](password|passwd|token|secret|api[_-]?key|authorization|jwt|bearer)['"]/i, /\b(password|passwd|token|secret|api[_-]?key|jwt|bearer)\s*=\s*req\./i]

Some analyzers (express's loose destructuring, Java route extractors that don't follow @RequestBody Credentials c.password, …) miss credential-bearing parameters at extract time. When that happens the param-based credential_input signal never fires and downstream heuristics like rate_limit_absence silently skip the route.

As a backstop, scan the route-scope snippet for the canonical request-body destructuring shapes (req.body.password, request.form['password'], { password } = req.body, …) and emit a credential_input signal with a slightly lower confidence than the param-based one. The kind is the same so downstream rate_limit_absence / guard_absence logic catches it transparently.

CRYPTO_FACTORY_CALLEE = /\A(?:create(?:Hash|Hmac|Cipheriv|Decipheriv|Sign|Verify)|md5|sha\d*)\z/
DATA_FRAME_RECEIVER = /(?:\A|\.)(?:\w*_?df|\w*[fF]rame|pl|pd)\.select\z/i
FOREIGN_IDENTIFIER_ASSIGNMENT_PATTERN = /\b([A-Za-z_][A-Za-z0-9_]*Id)\s*=\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*Id)\b/
GRAPHQL_OPERATION_ROOTS = ["Query", "Mutation", "Subscription"]
GRAPHQL_RESOLVER_ANNOTATION_PATTERN = /@(QueryMapping|MutationMapping|SchemaMapping|SubscriptionMapping|BatchMapping|DgsQuery|DgsMutation|DgsData|DgsSubscription)\b/
HASH_COMMENT_EXTENSIONS = {".rb", ".rake", ".ru", ".cr", ".ex", ".exs", ".py", ".pl", ".pm"}
HELPER_BODY_CALL = /(?:(?<recv>\$?\w+)?(?<op>\.|->|::)\s*)?(?<![\w$\\])(?<name>\w+)\s*\(/

Every call in a helper body: an optional receiver (torch, $smarty, none after )), the operator, and the called name.

HELPER_BODY_KEYWORDS = Set {"if", "elif", "for", "while", "switch", "catch", "return", "def", "fun", "function", "func", "fn", "sub", "new", "not", "and", "or", "in", "print", "super", "lambda", "with", "match", "when", "unless", "until"}
HELPER_BODY_SINK_CALLS = {"template_render" => /\brender_to_string\b|\bExecute(?:Template)?\s*\(|\b\w*(?:engine|template|tpl|tmpl|view)\w*\.(?:process|render|execute|evaluate|merge|fill)\w*\s*\(|\bget_template\b|\bTemplateResponse\b|\bModelAndView\b|\b(?:include|require)(?:_once)?\b[^;|]*\$|(?<![\w.>])view\s*\(|\b(?:ERB|Erubi|Liquid|Handlebars|Mustache|Jinja2?|Velocity|Twig)\b/i, "deserialization" => /\b\w*pickle\.\w*load|\b(?:dill|shelve|marshal|jsonpickle)\.\w*load|\bUnpickler\b|(?<![\w.])loads?\s*\(|\b(?:yaml|YAML|Psych)\.(?:unsafe_|full_)?load\b|\bMarshal\.(?:load|restore)\b|\bObjectInputStream\b|\breadObject\b|\b(?:BinaryFormatter|SoapFormatter|LosFormatter|NetDataContractSerializer)\b|\bunserialize\s*\(/}

What a helper's body does when it renders or revives, beyond the kind's own catalog: Django render_to_string, Go tpls.ExecuteTemplate(w, ...), Thymeleaf engine.process(view, c), a PHP include of a variable view path; from pickle import loads + bare loads(d), yaml.unsafe_load, cloudpickle.loads, pickle.Unpickler(...).load(), Ruby Marshal.restore. Body-only: the callee-name catalog stays as narrow as it was.

IDENTIFIER_INPUT_NAME_PATTERNS = PARAM_PATTERNS.find! do |pattern| pattern.kind == "identifier_input" end.name_patterns

Resolved once from the catalog instead of re-scanning PARAM_PATTERNS on every call. identifier_like? in particular runs several times per parameter per endpoint, and the answer never changes.

IMPORT_LINE = /\A\s*(?:from\s+\S+\s+import|import|use)\b|\brequire\s*\(/

Lines that bring names in from elsewhere: Python from x import a as render, import / use statements, require(...).

KOTLIN_COLLECTION_ID_LOOKUP_PATTERN = /\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*(firstOrNull|find|singleOrNull)\s*\{[^}]*\b(?:it|[A-Za-z_][A-Za-z0-9_]*)\.(?:id|[A-Za-z_][A-Za-z0-9_]*Id)\s*==/i
KOTLIN_COLLECTION_ID_LOOKUP_THROWS_PATTERN = /\b(?:firstOrNull|find|singleOrNull)\s*\{[^}]*\b(?:id|[A-Za-z_][A-Za-z0-9_]*Id)\b[^}]*\}[\s\S]{0,180}(?:\?:\s*throw|\borElseThrow\b|\bthrow\s+\w*(?:NotFound|NotExist|Missing)\b)/i
KOTLIN_CREDENTIAL_RETURN_PATTERN = /\bfun\s+\w+[^{|;]*=\s*(?:this\.)?(password|passwd|token|secret|api[_-]?key|session_?id|access_?token|refresh_?token|private_?key)\b/i
KOTLIN_WRITE_IN_SNIPPET_PATTERN = /\b(?:save|insert|persist|update|create|add|addAll)\s*\(|\.\s*(?:save|insert|persist|update|add|addAll)\s*\(/i
LOG_EMITTER_PATTERN = /\b(?:logger\.(?:info|warn|warning|error|debug|critical|fatal)|log\.(?:info|warn|warning|error|debug)|console\.(?:log|info|warn|error|debug)|print|puts|printf|System\.out\.println|Log\.[dwiev])\b/i

Log injection / sensitive data in logs. Catches the canonical "log user input or credential field directly" shape. False positives are bounded because both the log call AND a user- input reference (or credential noun) need to be on the same line / snippet window.

LOG_INPUT_OR_CRED_PATTERN = /\b(?:req\.body|req\.query|req\.params|request\.form|request\.json|request\.args|params\[|password|passwd|token|secret|api[_-]?key|jwt|session_id|access_token|refresh_token)\b/i
METHOD_DISPATCH_PATTERN = /(?:request\.method\s*==|req\.method\s*==|r\.Method\s*==|request\.getMethod\(\)\s*\.equals|\.match\(\s*['"](?:GET|POST|PUT|PATCH|DELETE|QUERY)['"])/i

Suppression for unsafe_method: route handlers that branch on request.method (Flask / Django) or req.method (Express, Node, Go's r.Method, Java's request.getMethod()) typically register a single route under multiple methods. Analyzers split that into one endpoint per method but share the callees list. The mutating callee in the GET endpoint is often the POST branch's call, not actually reachable via GET.

MOBILE_SOURCE_EXTS = Set {".swift", ".m", ".mm", ".kt", ".java"}
MUTATING_CALLEE_PATTERN = /\b(create|destroy|delete|update|save|insert|remove|drop|truncate|persist|flush|commit|rollback|set_)\w*/i

Each verb may be followed by additional word chars (destroy_all, createMany, deleteOne, updateUser), so we don't anchor with a trailing \b — that would miss those suffixed forms because _ and word continuation count as the same word in regex.

MUTATING_POST_CALLEE_PATTERN = /(?:\A|[.:_])(?:create|save|update|delete|insert|remove|destroy|modify|persist|revoke|store)|(?:Create|Save|Update|Delete|Insert|Remove|Destroy|Modify|Persist|Revoke|Store)/

A mutating verb anywhere in the callee name (start, after a separator, or as a CamelCase segment) disqualifies the endpoint from "read-only POST". Without this, a callee like getOrCreate/findAndDelete matches the read-only pattern via its leading read verb, silently suppressing the state-change review signal on an endpoint that does mutate.

MUTATING_VERB_SEGMENT = /\A(?:create|destroy|delete|update|save|insert|remove|drop|truncate|persist|flush|commit|rollback|set_)/i
OBJECT_LOOKUP_FALLBACK_CALLEE_PATTERN = /(?:^|\.)(?:deleteById|removeById|get\w*ById|retrieve\w*)\b|\b(?:session|db|repo|repository)\.get\b/i
OBJECT_LOOKUP_PRIMARY_CALLEE_PATTERN = /(?:^|\.)(?:findById|findOne|getOne|existsById|find\w*ById|find\w*By\w*Id|(?:find|count|exists)By\w*Id)\b/i
OUTBOUND_ANY_URI_PATTERN = /\.\s*(?:uri|getForObject|postForObject|exchange)\s*\(\s*["']([^"']+)["']/
OUTBOUND_TEMPLATE_URI_PATTERN = /\.\s*(?:getForObject|postForObject|exchange)\s*\(\s*["']([^"']+)["']/
OUTBOUND_URI_PATTERN = /\.\s*uri\s*\(\s*["']([^"']+)["']/
OWN_CLASS_CALL_EXTS = Set {".java", ".kt", ".cs", ".scala", ".groovy", ".swift", ".dart", ".rb", ".cr"}

Extensions where a bare call reaches a method of the caller's own class (Kotlin render(id) inside the controller, a Ruby private method). Elsewhere a bare name is a module-level function.

PERL_HASH_DELIMITED_OP = /(?<![\w$@%])(?:s|m|y|tr|q|qq|qw|qr)\z/
POST_AUTHORIZE_ANNOTATION = /@PostAuthorize\b[^|]*/

@PostAuthorize(...) up to the scope's next line: Spring evaluates it after the handler returns, so on a write it guards nothing.

PRIORITY_SCORING_SINK_BLACKLIST = Set {"sql", "data_store_query", "command_exec", "code_eval", "deserialization", "template_injection", "xss", "mass_assignment", "crypto_weak", "webview_load", "intent_redirect"}

Categories whose mere presence is a security-review signal — used alongside concrete signals to compute the overall priority bucket.

PRIVILEGED_REALTIME_TAGS = Set {"admin"}
PURE_CALLEE_RECEIVER = /\A(?:crypto|hashlib|hmac|hash|hasher|digest|\w*SearchParams|searchParams|params|urlParams|queryParams|headers|url|map|set)\z/

Receivers whose mutating-named methods touch only a local value: a URL or search-param bag (params.delete, redirSearchParams.delete), headers, a bare Map/Set, a hash module, and a hash/HMAC built by a call (crypto.createHash().update(), hashlib.sha256().update). Case-sensitive: Url.create and Digest.create are model classes.

PURE_TEXT_HELPER_CALLEE = /\Aremove_?\w*?(?:links|html|markup|whitespace|spaces|newlines|accents|diacritics|emojis?|punctuation|chars|characters|ansi|prefix|suffix|quotes|markdown|formatting|trailing_?slash(?:es)?|slashes|extension|indent(?:ation)?)\z/i

ponytail: name-only guess for receiverless text helpers (remove_links, remove_playground_links); resolve the callee's definition if more of these turn up.

QUOTED_LITERAL = /(["'])[^"'\n]*\1/
READ_ONLY_POST_CALLEE_PATTERN = /(?:^|[.:])(?:find|findAll|findOne|findBy\w+|get|list|listAll|count|search|query|retrieve)\w*\b/i
REPOSITORY_SOURCE_EXTS = Set {".java", ".kt"}
REQUEST_COOKIE_READ_PATTERN = /\b[A-Za-z_][A-Za-z0-9_]*\s*\.\s*(?:cookies\b|getCookies\s*\()/i
RESPONSE_EMITTER_PATTERN = /\b(jsonify|res\.json|res\.send|json_response|JsonResponse|JSONResponse|make_response|render\s+json:|to_json|respond_with)\b|\breturn\s*\{/i

The emitter list was built from helper calls (jsonify, res.json, …) and so missed the frameworks that serialize a returned literal with no helper at all: FastAPI and modern Flask handlers just return {...}, which is the single most common response shape in the Python corpus. Without it the credential-key check could not run on those handlers no matter what the payload held.

REVIEW_PRIORITY_SIGNAL_KINDS = Set {"guard_absence", "authz_absence", "rate_limit_absence", "idor_review", "csrf_exempt", "jwt_unsafe", "cors_open", "open_redirect", "ssrf", "path_traversal", "sensitive_response", "server_secret_source", "unsafe_method", "log_injection", "deep_link_input"}

Concrete review-worthy signal kinds. These are the "this is actually scary" categories — not the structural ones (route_definition, technology, path_param) that every endpoint surfaces.

ROCKET_PARAM_BINDING = /<(\w+)(?:\.\.)?>/
ROUTE_ARG = /(?:"[^"\n]*"|'[^'\n]*'|`[^`\n]*`|:?[A-Za-z_][\w$]*(?:(?:\.|::)[A-Za-z_][\w$]*)*)/

A route path written as a constant, a qualified name, a literal or a symbol; a list of them (@GetMapping({A, B})); and a handler string after it (Rails get 'authorize' => 'oauth#authorize', Laravel Route::get('authorize', 'OAuthController@authorize')).

ROUTE_ARG_LIST = /#{ROUTE_ARG.source}(?:\s*,\s*#{ROUTE_ARG.source})*/
ROUTE_DECL_ARG_TAIL = /(?=\s*(?:[,)\]}+]|=>|do\b|\z))/
ROUTE_DECL_FIRST_ARG = /\A(\d+: #{ROUTE_DECL_HEAD.source}(?:\s*\(\s*|\s+)(?:(?:value|path)\s*=\s*)?)(?:\[\s*#{ROUTE_ARG_LIST.source}\s*\]|\{\s*#{ROUTE_ARG_LIST.source}\s*\}|arrayOf\s*\(\s*#{ROUTE_ARG_LIST.source}\s*\)|#{ROUTE_ARG.source}(?:\s*(?:=>|,)\s*#{ROUTE_HANDLER_STR.source})?)#{ROUTE_DECL_ARG_TAIL.source}/i

The first argument of the route's own verb or mapping call: its path, written as a constant (@GetMapping(AUTHORIZE_PATH)) or as a segment without a leading / (Grape post 'authorize' do). Only the first argument: a middleware passed after it (router.get('/x', authenticate, h)) is a guard. ponytail: first positional (or value =/path =) argument only; @RequestMapping(method = GET, value = X) is not masked.

ROUTE_DECL_HEAD = /(?:[@#]\[?|\[)?(?:[\w$]+(?:\.|::))*(?:get|post|put|patch|delete|path|\w*mapping|http(?:get|post|put|patch|delete)|route(?:\s*\(?\s*:\w+\s*,)?)/
ROUTE_HANDLER_STR = /(?:"[\w\\:\/]+[@#]\w+"|'[\w\\:\/]+[@#]\w+')/
ROUTE_PATH_LITERAL = /(["'`])\/[^"'`\n]*\1/
ROUTE_SNIPPET_RADIUS = 2
SAFE_DECODE_RECEIVER = /\A(?:json|simplejson|ujson|orjson|JSON|base64|binascii|codecs)\z/

Receivers whose loads / decode revive no objects: the JSON modules, byte codecs, and a name bound in the body to itsdangerous' signed-JSON serializers (CTFd: s = URLSafeTimedSerializer(secret) then s.loads(...)). Ruby's JSON.load / JSON.restore are the exception: they honour create_additions and revive objects.

SAFE_METHODS = SAFE_HTTP_METHODS

HTTP method intent vs implementation mismatch. A GET/HEAD endpoint that mutates server state through a callee (User.create, record.destroy, db.delete, …) is a textbook CSRF / side-effect-on-read bug — the verb says "safe / idempotent" but the code says otherwise. QUERY makes the same promise (RFC 10008 defines it safe + idempotent), so a mutating QUERY handler earns the same signal. The shared set keeps this file, the taggers, and the probe path-filler agreeing on what a read is.

SAFE_SERIALIZER_CLASS = /\b(?:URLSafe)?(?:Timed)?(?:JSONWebSignature)?Serializer\b|\b(?:Timestamp)?Signer\b|\bJSONDecoder\b/
SCOPE_SEGMENT_LINE = /\A(\d+): /
SELECT_BUILT_ARGUMENT = /#\{|\$\{|\bf["']|["']\s*(?:\+|%|\.\s*\$)|\+\s*["']|\btext\s*\(|\bArel\.sql\b|\bSprintf\b|\.format\s*\(/

User.select("#{params[:col]}"), SQLAlchemy select(text(request.args["c"])): a select whose argument is built at run time is SQL whatever the receiver. A fixed column list (User.select(*attrs)) stays out, and so do a lambda or block argument (LINQ _items.Select(x => x.Name + ""), Ruby select(&:active?), JS .select((x) => "#" + x)) and a data frame's column pick (polars df.select(f"col_{i}")).

SELECT_LAMBDA_ARGUMENT = /\A\s*(?:\(?[\w\s,]*\)?\s*=>|&:|\{\s*\||lambda\b|->|fn\b|function\b|\|)/
SEND_IN_MEMORY_BUFFER = /\bsend_?file\s*\(\s*#{IN_MEMORY_BUFFER}/i
SENSITIVE_RESPONSE_SCOPE_CHARS = 1200
SENSITIVE_RESPONSE_SCOPE_LINES = 40

This check needs the response emitter and the credential key in the same window, and in real handlers they sit far apart — the body assembles a dict/struct first and serializes it at the end. Under the default 12-line / 240-char route scope the two could not co-occur, so the signal never fired on the shape it exists to catch. Widen the window for this check only; the scope walk still stops at the end of the handler, so it cannot pair one route's emitter with another route's payload.

SINKISH_CALL_NAME = {"template_render" => /render|template|tpl|tmpl|view|display|fetch|result|process|merge|evaluate|compile/i, "deserialization" => /load|restore|deserial|unserial|unmarshal|unpickle|decode|revive|thaw/i}

A call in a helper's body the judge cannot follow but that may well be the render / revive, by the call's last segment, qualified or not (torch.load(b), Oj.load, $smarty->display($tpl), ERB.new(src).result(binding), cfg.getTemplate(n).process(m, out)): such a helper keeps its sink.

SOAP_READ_OPERATION_PATTERN = /\A(?:[Gg][Ee][Tt]|[Ll][Ii][Ss][Tt]|[Ff][Ii][Nn][Dd])(?:[A-Z]|[_-]|\z)/

SOAP transports every operation over POST. Treat read-style operation names as reads so HTTP method heuristics do not label them as mutations.

SOURCE_SCAN_RADIUS = 6
SPRING_CONFIG_KEY_PATTERN = /\$\{([^}:]+)/
SPRING_MVC_MAPPING_ANNOTATION = /@(GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping|RequestMapping)\b/
SPRING_MVC_VIEW_EXPR = /\bfun\b[^{\n]*:\s*String\b[^{\n]*=\s*["']([^"']+)["']/
SPRING_MVC_VIEW_RETURN = /\breturn\s+["']([^"']+)["']/
SPRING_SECRET_NAME_PATTERN = /\b(pass(word)?|secret|token|api[_.-]?key|credential|jwt|private[_.-]?key)\b/i
SPRING_VALUE_ANNOTATION_PATTERN = /@Value\s*\(([^)]*)\)/
STATE_CHANGING_METHODS = Set {"POST", "PUT", "PATCH", "DELETE"}
TYPE_NAME = /\b[A-Z]\w*\b/
TYPED_PARAM = /\b([A-Za-z_]\w*)\s*:\s*((?:&(?:'\w+\s+)?)?(?:\w+::)*\w+(?:<[^()|]*?>)?)/

Constructors

Class methods

mutating_callee?(name : String) : Bool

A callee changes state when one of its chain segments is a mutating verb whose immediate receiver is not a known pure value. Judging the segment with its receiver keeps crypto.createHash().update().digest and params.delete('debug') out while User.destroy, db.session.commit and doc.set().save stay in.

Source

Instance methods

apply(endpoints : Array(Endpoint)) : Array(Endpoint)
Source