Skip to content

Matchers and Patterns

Define which applications or files your policy targets by combining matcher types with clear, maintainable patterns. Matchers evaluate data discovered about an application — its shortcut or bundle, the resolved executable, and its signature — not arbitrary files on disk.

What You'll Learn

  • Choose the right matcher for the job (shortcut, path, arguments, bundle, or high-assurance identity)
  • Use pattern types (exact, regex, glob, semver) appropriately
  • Author path rules with simple, readable globs
  • Use matchAll (AND), matchAny (OR) and matchNone (exclusions) to build precise rules

Matcher Types

The matcher vocabulary is one list on every platform. A policy authored for Windows decodes on macOS and vice versa; a matcher type a platform cannot evaluate simply never matches there (the rule fails closed) and never refuses the document.

TypeDescriptionEvaluated onExample
shortcutNameDisplay name of the shortcut (Windows) or application (macOS)Windows, macOS"Google Chrome"
targetPathExecutable path; on macOS also the .app bundle pathWindows, macOS"C:\Program Files\Google\Chrome\Application\chrome.exe", "**/Visual Studio Code.app"
argumentsShortcut command-line argumentsWindows only"--incognito"
publisherCertificateLeaf code-signing certificate of the resolved executable: thumbprint, Subject CN (publisher name), Issuer CN, or full DNWindows, macOS"F0E1D2C3B4A5968778695A4B3C2D1E0FABCDEF12" or "Google LLC"
fileHashSHA-256 of the main executable's bytesWindows, macOS"9F2A2F2B…"
versionExecutable version (Windows: ProductVersion, then FileVersion; macOS: CFBundleShortVersionString)Windows, macOS"123.0.6422.141", ">=123.0"
bundleIdmacOS bundle identifiermacOS only"com.microsoft.VSCode"
teamIdApple Developer Team identifier of the signermacOS only"UBF8T346G9"
codeDirectoryHashThe signed bundle's code directory hash (cdhash); requires "schemaVersion": 2macOS only"a1b2c3…"

Notes

  • On Windows the Launcher enumerates shortcut files (.lnk) from Start Menu and Desktop; on macOS it enumerates application bundles (LaunchServices, Homebrew) and command-line tools named by targetPath matchers. Executables must be in discovery scope to be matched and launched.
  • teamId and publisherCertificate are the durable pins across updates; fileHash and codeDirectoryHash pin one build and change on every re-sign (the macOS launcher re-signs the apps it stages).
  • An unknown type string — a typo, or a type introduced by a newer schema — also decodes as a matcher that never matches. The document is still applied; only that rule is affected.

Security Rule — Path vs. Identity

Path-based matching (targetPath, glob) validates location, not integrity. It is vulnerable to "Rename Attacks" where a user with write access to the target folder renames a malicious binary to match your allowed pattern.

Requirement: For high-assurance environments (Zero Trust, CMMC), you must combine path matchers with publisherCertificate or fileHash.

Pattern Types

TypeDescription
exactExact text match (case-insensitive)
regexRegular expression, unanchored and case-insensitive
globShell-style wildcard matching for file paths (supports *, ?, and **). Easier to read and avoids regex escaping for typical path rules. See Glob patterns.
semverVersion-range comparison for version matchers (>=, <=, ==, >, <). See version.

Default when omitted

  • The default depends on the document's schemaVersion, on every reader: regex when the document declares version 1 (or nothing), glob when it declares version 2 or higher. Regex is unanchored, so the version-1 default matches anything containing the pattern — the over-authorizing reading. Always state patternType.
  • An unknown patternType string makes the matcher never match; the document is still applied.

Recommendations

  • Always specify patternType explicitly to avoid ambiguity.
  • Thumbprints, fileHash and codeDirectoryHash values are always compared exactly and case-insensitively; patternType is ignored for them. Write exact for clarity.

File Type Extensions

  • For fileTypes.match and handler-level match filters, author extensions using an extensions array and avoid regex alternation for extensions. Matching is case-insensitive and patternType does not apply to the extensions form.
json
{ "match": { "extensions": ["doc", "docx"] } }

Single extension:

json
{ "match": { "extensions": ["pdf"] } }

Prefer glob for Windows paths

Regex patterns in JSON require double escaping (for example: ".*\\myapp\.exe$"), which is cumbersome and error-prone. Use glob patterns instead (for example: "C:\\Program Files\\**\\myapp.exe") for simpler, more readable rules. Remember to escape backslashes in JSON strings.

Case Sensitivity

  • exact
    • Exact text match is case-insensitive across all matcher types. This includes publisher certificate thumbprints and SHA-256 hashes (hex is matched case-insensitively).
  • regex
    • Case-insensitive by default. This applies to Windows-targeted fields such as shortcutName and targetPath so that "chrome.exe" and "Chrome.exe" both match.
    • For non-path fields (for example, arguments, version, Subject/Issuer names), matching is also case-insensitive by default for consistency. If you need case-sensitive distinctions, author explicit character classes in your regex.
  • glob
    • Intended for file paths (for example, targetPath) and evaluated case-insensitively on both platforms, consistent with typical Windows path behavior.

Notes

  • File system paths are typically case-insensitive on Windows and on default macOS volumes; the readers align with this behavior for path-oriented fields regardless of pattern type.

Boolean Logic Semantics

Application policies support flexible logic using matchAll (AND) and matchAny (OR).

  • Logical AND (matchAll): All matchers in the array must evaluate to true.
  • Logical OR (matchAny): At least one matcher in the array must evaluate to true.
  • Flat lists: a matcher list contains matchers only. Nested groups are not supported on any reader — a group object placed inside matchAll or matchAny decodes as a matcher of unknown type and never matches. Express (A OR B) AND C as two rules, or with regex alternation inside one field (below).
  • Rule: a rule carries either matchAll or matchAny, not both. The JSON Schema rejects the pair; the Windows validator warns at schemaVersion 1 and refuses at 2; the macOS daemon refuses.
  • targetPath shorthand: a rule may instead name one executable directly with a rule-level targetPath string. It is evaluated only when neither matchAll nor matchAny is populated, as a full-path, case-insensitive comparison after $VAR / ${VAR} / %VAR% expansion, on every reader.
  • A rule with none of them matches nothing. There is no implied wildcard. Write a catch-all explicitly: "matchAny": [{ "type": "targetPath", "pattern": "**", "patternType": "glob" }].

Exclusions (matchNone) A rule can carry a third matcher list, matchNone, holding exclusions. The rule applies when its positive side matches and no entry in matchNone matches. Any single entry matching vetoes the rule.

This is what expresses "authorize this broad set except these" — a catch-all over C:\Program Files\** that carves out three applications:

json
{
  "matchAll": [
    { "type": "targetPath", "pattern": "C:\\\\Program Files\\\\**", "patternType": "glob" }
  ],
  "matchNone": [
    { "type": "targetPath", "pattern": "**\\\\EXCEL.EXE", "patternType": "glob" },
    { "type": "targetPath", "pattern": "**\\\\POWERPNT.EXE", "patternType": "glob" },
    { "type": "shortcutName", "pattern": "Access", "patternType": "exact" }
  ]
}

Requires "schemaVersion": 2. A document that uses matchNone without declaring it is refused, not accepted-and-ignored — see Version gate below.

Points worth knowing:

  • It can only narrow. A rule is never matched by its positive side, excluded by matchNone, and applied anyway.
  • It scopes one rule — it does not block an app. An excluded application is still authorized by any other rule that matches it. To say "this app is not permitted here", use an explicit deny; to say "this rule isn't about those", use matchNone. See Authorization & Visibility.
  • It works on any positive form. matchNone composes with matchAll, with matchAny, and with the targetPath shorthand.
  • It never supplies the positive side. A rule with no matchAll, matchAny, or targetPath matches nothing, with or without matchNone — exclusions subtract from an empty set. Under "schemaVersion": 2 such a rule is refused as an authoring error naming the rule; under version 1 it stays valid (a deployed document is not refused for a shape it already carried) and is reported as a dead rule. To say "everything except these", write the wildcard explicitly: "matchAny": [{ "type": "targetPath", "pattern": "**", "patternType": "glob" }] plus matchNone.
  • An empty list changes nothing. "matchNone": [] and null behave exactly like an absent field and do not trip the version gate.
  • It is a rule-level list, not a per-matcher flag — deliberately. A negated matcher is only meaningful under AND; inside matchAny it could only widen the rule (NOT com.example.app is true of nearly every application), so it could not express an exclusion at all.
  • Every reader honours it: the Windows client, the macOS daemon and the macOS launcher all evaluate matchNone first.

Version gatematchNone is the one field whose silent loss inverts a policy rather than degrading it: a reader that does not understand it reads "allow everything except X" as "allow everything, including X" — it authorizes precisely what you excluded. So the contract is strict in both directions:

  • A policy using matchNone must declare "schemaVersion": 2.
  • A document declaring version 1 (or declaring nothing) that carries matchNone is refused, naming the offending rule.
  • A document declaring a version newer than a reader supports is refused rather than partially applied. The reader keeps its current policy.

Practically: publish matchNone only once every client in the estate understands it. Until then a v2 document is refused outright by older clients — which is the safe failure, but it is a refusal.

Other checks that tighten at version 2 Declaring "schemaVersion": 2 also turns two long-standing warnings into refusals, so a v2 document is held to the stricter reading on every platform:

  • A rule carrying both matchAll and matchAny is refused, naming the rule. (At version 1 it is a warning on Windows; the macOS daemon has always refused it.)
  • A rule with no matchAll, matchAny, or targetPath is refused, naming the rule (see It never supplies the positive side above).
  • An omitted patternType defaults to glob at version 2 and to unanchored regex at version 1, on every reader. Always state patternType; the version-1 reader warns when it is missing.

Where matchNone does not fit — an exclusion about part of a single field's value rather than about which apps a rule covers — regex negative lookahead remains available, with the anchoring caveat noted in Limitations & Scope — Exclusions and negation. Prefer matchNone, and prefer positive, readable matches over both.

Other ways to express OR and (A OR B) AND C

  • Author multiple allow policies when alternatives span different fields. Any matching allow policy authorizes. If more than one allow policy matches, merges resolve by priority then id. See: Merging & Precedence
  • Use regex alternation within a single field when appropriate.
  • For (A OR B) AND C, write one rule per alternative, each carrying C in its matchAll — nested groups are not available.

Examples

AND (path + publisher) using matchAll:

json
{
  "matchAll": [
    { "type": "targetPath", "pattern": "**\\\\myapp.exe", "patternType": "glob" },
    { "type": "publisherCertificate", "pattern": "5E0B1B36A6F3D1C2A6C9E4B1A46C9D18B1E3F2A469C7BA0E8F1A2B3C4D5E6F70", "patternType": "exact" }
  ]
}

OR (simple list) using matchAny:

json
{
  "matchAny": [
    { "type": "targetPath", "pattern": "**\\\\putty.exe", "patternType": "glob" },
    { "type": "targetPath", "pattern": "**\\\\psftp.exe", "patternType": "glob" },
    { "type": "targetPath", "pattern": "**\\\\pageant.exe", "patternType": "glob" },
    { "type": "targetPath", "pattern": "**\\\\puttygen.exe", "patternType": "glob" }
  ]
}

(A OR B) AND C, as two rules:

json
[
  {
    "id": "allow-chrome-signed",
    "matchAll": [
      { "type": "shortcutName", "pattern": "Google Chrome", "patternType": "exact" },
      { "type": "publisherCertificate", "pattern": "Google LLC", "patternType": "exact" }
    ]
  },
  {
    "id": "allow-edge-signed",
    "matchAll": [
      { "type": "shortcutName", "pattern": "Microsoft Edge", "patternType": "exact" },
      { "type": "publisherCertificate", "pattern": "Microsoft Corporation", "patternType": "exact" }
    ]
  }
]

Direct path via the targetPath shorthand (no matcher list):

json
{
  "id": "system-tar",
  "targetPath": "%SYSTEMROOT%\\System32\\cmd.exe",
  "visibility": "hidden",
  "action": "allow"
}

OR (single field via regex alternation):

json
{
  "matchAll": [
    { "type": "targetPath", "pattern": ".*\\\\(chrome|msedge)\\.exe$", "patternType": "regex" }
  ]
}

NOT (exclude a specific argument via regex):

json
{
  "matchAll": [
    { "type": "arguments", "pattern": "^(?!.*--private).*$", "patternType": "regex" }
  ]
}

Glob patterns

One glob reading on every platform:

  • * matches any sequence within a single path segment (it does not cross /)
  • ? matches a single character within a single path segment
  • ** matches across path separators (recursive); **/ may also match zero segments, so **/chrome.exe matches chrome.exe at any depth including the root
  • Matching is case-insensitive
  • \ is normalised to / before matching, so a Windows pattern and a Windows path compare segment by segment whichever separator each used

Examples

  • C:\\\\Program Files\\\\*\\\\myapp.exe — exactly one folder between Program Files and myapp.exe
  • C:\\\\**\\\\Acrobat.exe
  • **\\\\chrome.exe
  • **/Visual Studio Code.app (macOS bundle path)

JSON escaping note

  • When writing patterns inside JSON strings, Windows backslashes must be escaped as \\.
    • Regex example: ".*\\\\myapp\\\.exe$"
    • Glob example: "C:\\\\**\\\\myapp.exe"

Windows paths in JSON require double backslashes

When writing Windows paths inside JSON strings, every \ in the actual path must be written as \\ in JSON.

Examples

  • Correct glob: "C:\\\\Program Files\\\\**\\\\myapp.exe"
  • Correct regex: ".*\\\\myapp\\\.exe$"
  • Incorrect: "C:\\Program Files\MyApp\myapp.exe" (mixed or unescaped backslashes) — this either fails to parse or matches the wrong path.

Because JSON and regex escaping both apply to Windows paths, complex regex patterns quickly become hard to read and easy to get wrong. For path-based matchAll rules (for example, targetPath), prefer:

  • patternType: "glob" for most Windows path rules, and
  • simple, stable patterns such as "C:\\\\Program Files\\\\**\\\\myapp.exe".

High-assurance matchers

Use these to bind identity and integrity, especially for sensitive applications.

publisherCertificate

Accepted fields

  • Thumbprint (SHA-256 64-hex or SHA-1 40-hex; paste without spaces; case-insensitive)
  • Subject CN (publisher display name, for example "Microsoft Corporation")
  • Issuer CN (for example "Microsoft Windows Production PCA 2011")

Pattern guidance

  • Use patternType: "exact" for thumbprints and CNs (exact is case-insensitive).
  • Use patternType: "regex" (or glob) against the full Subject or Issuer distinguished name to anchor on CN= specifically (for example: (^|,)\\s*CN=Microsoft Corporation(,|$)).
  • Without field, the pattern is tried against the thumbprint, the Subject CN and the Issuer CN; a match on any of them matches. State field when you mean one of them.

Trust behavior

  • Unsigned executables do not match.
  • For dual-signed binaries, matching is applied against the leaf signer used by platform trust (Windows Authenticode; macOS code signature).

Best practices

  • Prefer Subject CN for maintainability; thumbprints rotate on renewals.
  • Combine with targetPath and/or version for higher assurance; add fileHash for immutability.

Field selection (explicit "field" property)

  • You can specify which certificate field to match with the optional field property on the publisherCertificate matcher:
    • thumbprint — Leaf signer certificate thumbprint; 64-hex (SHA-256) or 40-hex (SHA-1). Always compared exactly; patternType is ignored.
    • subjectCN — Leaf signer Subject Common Name (publisher display name); case-insensitive; prefer for maintainability.
    • issuerCN — Issuer Common Name of the leaf signer’s issuing CA; use only when intentionally pinning to an issuer.
    • subjectDN — Full Subject distinguished name (RFC 4514). Any pattern type; anchor explicitly on CN= to avoid ambiguity.
    • issuerDN — Full Issuer distinguished name (RFC 4514). Any pattern type; anchor explicitly on CN= to avoid ambiguity.

Validation rules

  • field: "thumbprint" → pattern should match ^(?:[A-Fa-f0-9]{64}|[A-Fa-f0-9]{40})$ (a non-hex thumbprint never matches).
  • All certificate comparisons are case-insensitive.

Examples

  • Thumbprint pin: { "type": "publisherCertificate", "field": "thumbprint", "pattern": "5E0B1B36A6F3D1C2A6C9E4B1A46C9D18B1E3F2A469C7BA0E8F1A2B3C4D5E6F70", "patternType": "exact" }

  • Subject CN: { "type": "publisherCertificate", "field": "subjectCN", "pattern": "Microsoft Corporation", "patternType": "exact" }

  • Issuer CN: { "type": "publisherCertificate", "field": "issuerCN", "pattern": "Microsoft Windows Production PCA 2011", "patternType": "exact" }

  • Subject DN (regex anchored on CN): { "type": "publisherCertificate", "field": "subjectDN", "pattern": "(^|,)\s*CN=Microsoft Corporation(,|$)", "patternType": "regex" }

fileHash

  • Matches the SHA-256 hash of the resolved executable (on macOS, the main Mach-O only — frameworks and resources are outside it, and the digest changes on every re-sign).
  • Strongest integrity guarantee; any update requires a policy update.
  • Always compared exactly and case-insensitively; patternType is ignored.
  • Hashing is performed only when a fileHash matcher is present or when cache is invalidated.

codeDirectoryHash (macOS)

  • Matches the code directory hash (cdhash) of the signed bundle — what codesign -dvvv prints as CDHash. It seals every resource in the bundle, so it pins one exact build. Requires "schemaVersion": 2; never matches on Windows.
  • Always compared exactly; patternType is ignored.

version

  • Matches ProductVersion (fallback to FileVersion) from PE metadata on Windows, and CFBundleShortVersionString on macOS.
  • patternType decides the comparison, on every reader:
    • semver — a range compare with one operator: >=1.2, <2.0, ==1.2.3, >1.0, <=1.9. Numeric parts compare numerically with missing parts read as zero, so >=1.2 accepts 1.2.0 and 1.10. When patternType is omitted and the pattern starts with an operator, it is read as semver.
    • exact — pins one version string (case-insensitive).
    • regex / glob — a string match on the version text (for example ^(1\\.(2|3)\\.[0-9]+)$ for 1.2.x–1.3.x).
  • There is no 1.x wildcard form; an x in a pattern is a literal x. Use semver bounds or a regex.

Enterprise recommendations

  • Bind identity to location: targetPath + publisherCertificate (or teamId on macOS).
  • Constrain updates: add a version matcher (semver bounds, or a regex for a range).
  • Require immutability: publisherCertificate + fileHash (operationally heavy).

Obtaining values

PowerShell snippets to extract values from an executable:

  • Publisher certificate thumbprint: (Get-AuthenticodeSignature 'C:\Path\To\app.exe').SignerCertificate.Thumbprint

  • SHA-256 file hash: (Get-FileHash -Algorithm SHA256 'C:\Path\To\app.exe').Hash

  • Version (ProductVersion): (Get-Item 'C:\Path\To\app.exe').VersionInfo.ProductVersion

Examples

Pin by publisher and version (glob path):

json
{
  "matchAll": [
    { "type": "targetPath", "pattern": "**\\\\myapp.exe", "patternType": "glob" },
    { "type": "publisherCertificate", "pattern": "A1B2C3D4E5F60718273645546352413F0E1D2C3B", "patternType": "exact" },
    { "type": "version", "pattern": "^(1\\.(2|3)\\.[0-9]+)$", "patternType": "regex" }
  ]
}

Allow all Program Files executables (glob path):

json
{
  "matchAll": [
    { "type": "targetPath", "pattern": "C:\\\\Program Files\\\\**\\\\*.exe", "patternType": "glob" }
  ]
}

Network destination matching

Use these patterns to match the destination of a network connection for proxy routing or egress policies. Host matching is case-insensitive; prefer glob for readability.

Supported match fields

  • hosts: Array of HostPatternSpec objects (pattern + patternType). Case-insensitive. Prefer glob (e.g., "*.corp.local").
  • cidrs: Array of IPv4/IPv6 CIDR strings (e.g., "10.0.0.0/8", "2001:db8::/32").
  • ipRanges: Array of { from, to } with IPv4/IPv6 addresses.
  • ports: Array of PortSpec strings (single "443" or range "8080-8090").
  • protocol: "tcp" | "udp" | "icmp".

HostPatternSpec

json
{ "pattern": "*.corp.local", "patternType": "glob" }

CIDR examples

  • "10.0.0.0/8"
  • "172.16.0.0/12"
  • "192.168.0.0/16"
  • "2001:db8::/32"

IP range example

json
{ "from": "1.1.1.1", "to": "1.1.1.255" }

PortSpec

  • Single: "443"
  • Range: "10000-10100"
  • Valid ports are 0–65535; ranges must be start ≤ end.

Examples

Hosts via glob (proxy internet, deny otherwise)

json
{
  "match": { "hosts": [ { "pattern": "*", "patternType": "glob" } ], "protocol": "tcp" }
}

CIDR-based direct for RFC1918

json
{
  "match": { "cidrs": ["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"] }
}

IP range + port

json
{
  "match": { "ipRanges": [ { "from": "203.0.113.0", "to": "203.0.113.255" } ], "ports": ["443"], "protocol": "tcp" }
}

Guidance

  • Prefer CIDR for known networks and host globs for domains; use regex only for complex host rules.
  • Hosts are matched case-insensitively. CIDR/range matching applies equally to IPv4 and IPv6.
  • Combine filters (e.g., host + port + protocol) to narrow matches.

Next steps