patterns

Pattern matching with glob patterns and regular expressions for authorization policies.

Pattern Matching in Authorization Policies

This library provides two complementary approaches for pattern matching in access control decisions.

Glob Patterns

Glob patterns provide hierarchical wildcard matching that respects segment boundaries. Use them when:

  • Matching file paths, domain names, or other hierarchical identifiers
  • Segment boundaries matter for your matching logic
  • Non-technical users need to write or understand patterns

Glob patterns support:

  • * - Matches zero or more characters within a segment
  • ** - Matches across segment boundaries
  • ? - Matches exactly one character
  • [abc] or [a-z] - Character sets and ranges
  • [!abc] - Negated character sets
  • {cat,dog,bird} - Alternatives
  • \ - Escape character for literal matching

Delimiters define segment boundaries. When not specified, . is used by default.

Regular Expressions

Regular expressions provide full pattern matching power using standard regex syntax. Construction rules follow the Java Pattern class specification. Use them when:

  • Glob patterns cannot express the required matching logic
  • Advanced features like lookahead, backreferences, or precise quantifiers are needed
  • Complex validation rules require regex capabilities

When to Use Simple String Operations

For literal prefix, suffix, or substring checks, use the string library instead. Functions like string.startsWith or string.contains are faster and clearer for these common cases.

Security

All regex functions include protection against Regular Expression Denial of Service attacks. Patterns containing dangerous constructs like nested quantifiers (a+)+, excessive alternations, or nested wildcards are rejected before evaluation.


replaceAll

replaceAll(TEXT value, TEXT pattern, TEXT replacement): Replaces all regex matches with replacement text.

Replacement can include backreferences like $1 to refer to captured groups from parentheses in the pattern.

Examples:

policy "redact_emails"
permit
  var redacted = patterns.replaceAll(resource.text, "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}", "[REDACTED]");
  resource.publicText == redacted;
policy "normalize_paths"
permit
  var normalized = patterns.replaceAll(resource.path, "/+", "/");
  string.startsWith(normalized, "/api");
policy "swap_format"
permit
  var swapped = patterns.replaceAll(resource.date, "(\\d{2})/(\\d{2})/(\\d{4})", "$3-$1-$2");
  swapped == resource.expectedFormat;

split

split(TEXT pattern, TEXT value): Splits text by a regex pattern into array segments.

Each match of the pattern becomes a boundary where the string is divided.

Examples:

policy "parse_csv"
permit
  var fields = patterns.split(",", resource.csvLine);
  array.size(fields) == 5;
policy "split_whitespace"
permit
  var tokens = patterns.split("\\s+", resource.input);
  array.containsAll(tokens, subject.requiredTokens);
policy "split_delimiters"
permit
  var parts = patterns.split("[;,|]", resource.delimitedData);
  array.size(parts) > 0;

matchGlob

matchGlob(TEXT pattern, TEXT value, ARRAY delimiters): Matches text against a glob pattern.

Performs hierarchical wildcard matching where wildcards respect segment boundaries defined by delimiters. Use this for file paths, domain names, or structured identifiers where hierarchical structure matters.

When to Use:

  • File paths: /api/*/documents matches /api/v1/documents but not /api/v1/admin/documents
  • Domain names: *.example.com matches api.example.com but not foo.api.example.com
  • Hierarchical permissions: user:*:read matches user:profile:read within segments

Default Delimiter: When delimiters array is empty or not provided, . is used as the delimiter.

Examples:

policy "api_paths"
permit
  patterns.matchGlob("/api/*/users/*", resource.path, ["/"]);
policy "domain_matching"
permit
  patterns.matchGlob("*.company.com", request.host, ["."]);
policy "permission_hierarchy"
permit
  patterns.matchGlob("document:*:read", subject.permission, [":"]);
policy "file_extensions"
permit
  patterns.matchGlob("report.{pdf,docx,xlsx}", resource.filename, ["."]);
policy "cross_segment"
permit
  patterns.matchGlob("/api/**/admin", resource.path, ["/"]);

matchGlobWithoutDelimiters

matchGlobWithoutDelimiters(TEXT pattern, TEXT value): Matches text against a glob pattern without segment boundaries.

Performs flat wildcard matching where all wildcards match any characters without restriction. Both * and ** behave identically. Use this for simple filename matching or flat strings without hierarchical structure.

Examples:

policy "filename_only"
permit
  patterns.matchGlobWithoutDelimiters("report_*.pdf", resource.filename);
policy "simple_wildcard"
permit
  patterns.matchGlobWithoutDelimiters("user_*_token", subject.sessionToken);
policy "alternatives"
permit
  patterns.matchGlobWithoutDelimiters("status_{active,pending}", resource.status);

escapeGlob

escapeGlob(TEXT text): Escapes all glob metacharacters to treat input as literal text.

Prepends backslash to glob special characters. Essential for safely incorporating untrusted input into glob patterns to prevent pattern injection where malicious input could match unintended values.

Examples:

policy "safe_user_input"
permit
  var safeUsername = patterns.escapeGlob(request.username);
  var pattern = string.concat("/users/", safeUsername, "/*");
  patterns.matchGlob(pattern, resource.path, ["/"]);
policy "literal_match"
permit
  var escaped = patterns.escapeGlob(resource.tag);
  escaped == "literal*text";

isValidRegex

isValidRegex(TEXT pattern): Checks if text is a valid regular expression.

Returns true if the pattern is syntactically valid and within length limits, false otherwise.

Examples:

policy "validate_pattern"
permit
  patterns.isValidRegex(resource.customPattern);
policy "check_before_use"
permit
  var pattern = request.filterPattern;
  patterns.isValidRegex(pattern) && patterns.findMatches(pattern, resource.text) != [];

findMatches

findMatches(TEXT pattern, TEXT value): Finds all matches of a regex pattern.

Returns all non-overlapping matches. Maximum 10,000 matches returned. Patterns with dangerous constructs are rejected.

Examples:

policy "extract_emails"
permit
  var emails = patterns.findMatches("[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}", resource.text);
  array.size(emails) > 0;
policy "find_tags"
permit
  var tags = patterns.findMatches("#[a-zA-Z0-9]+", resource.content);
  array.containsAll(tags, subject.allowedTags);
policy "extract_numbers"
permit
  var numbers = patterns.findMatches("\\d+", resource.input);
  array.size(numbers) <= 10;

findMatchesLimited

findMatchesLimited(TEXT pattern, TEXT value, INT limit): Finds up to limit matches of a regex pattern.

Stops searching after finding the specified number of matches. Limit is capped at 10,000.

Examples:

policy "first_ten_matches"
permit
  var matches = patterns.findMatchesLimited("\\b\\w+\\b", resource.text, 10);
  array.size(matches) == 10;
policy "limited_extraction"
permit
  var urls = patterns.findMatchesLimited("https://[^\\s]+", resource.document, 5);
  array.size(urls) <= 5;

findAllSubmatch

findAllSubmatch(TEXT pattern, TEXT value): Finds all regex matches with their capturing groups.

Each match returns an array where index 0 is the full match and subsequent indices are captured groups from parentheses in the pattern.

Examples:

policy "parse_permissions"
permit
  var matches = patterns.findAllSubmatch("(\\w+):(\\w+):(\\w+)", subject.permissions);
  array.size(matches) > 0;
policy "extract_structured"
permit
  var data = patterns.findAllSubmatch("user=([^,]+),role=([^,]+)", resource.metadata);
  var firstMatch = data[0];
  firstMatch[2] == "admin";

findAllSubmatchLimited

findAllSubmatchLimited(TEXT pattern, TEXT value, INT limit): Finds up to limit matches with capturing groups.

Each match array contains the full match at index 0 and captured groups at subsequent indices.

Examples:

policy "limited_parsing"
permit
  var matches = patterns.findAllSubmatchLimited("(\\d{4})-(\\d{2})-(\\d{2})", resource.dates, 3);
  array.size(matches) <= 3;

matchTemplate

matchTemplate(TEXT template, TEXT value, TEXT delimiterStart, TEXT delimiterEnd): Matches text against a template with embedded regex patterns.

Combines literal text matching with embedded regular expressions. Text outside delimiters is matched literally with backslash escapes. Text inside delimiters is treated as regex patterns.

When to Use:

  • Mix literal text with dynamic patterns
  • Build patterns from configuration without escaping entire strings
  • Separate static structure from variable matching logic

Template Syntax:

  • Literal portions: Text outside delimiters, use \ to escape special characters
  • Pattern portions: Text between delimiterStart and delimiterEnd
  • Escape sequences: \* becomes literal *, \\ becomes literal \

Examples:

policy "api_version"
permit
  patterns.matchTemplate("/api//users", resource.path, ", ");
policy "structured_id"
permit
  patterns.matchTemplate("tenant::resource", resource.id, ", ");
policy "mixed_format"
permit
  var template = "user--}";
  patterns.matchTemplate(template, resource.userId, ", ");
policy "escaped_literals"
permit
  patterns.matchTemplate("file\\*\\.txt", resource.filename, ", ");