Package Rules
A package rule targets a piece of software by name in a package catalog (for example a Homebrew cask or an npm package) rather than by a raw hash or Team ID. Workshop resolves the package to its concrete identifiers and materializes ordinary execution rules from them. As new versions are published (or old ones stop matching your filters), Workshop re-resolves the package and adds or removes the materialized rules automatically, so you manage one package rule instead of a growing list of hashes.
One package rule can cover many versions and, depending on the rule type, many binaries per version. That reach is what makes the filters below useful: they let you narrow a broad package rule down to exactly the versions and binaries you want to trust.
Creating a package rule
Open Rules → Package Rules → New Package Rule. A package rule has a few core fields plus the optional filters described later.
Package source and name
Pick the Package Source that hosts the software, then enter the Package Name as it appears in that catalog:
| Source | Name example |
|---|---|
| Homebrew | wget (formula) |
| Homebrew Cask | firefox (cask) |
| NPM | express |
| GitHub | owner/repo |
| Rust (crates.io) | rustls |
| VS Code | publisher.name |
| Terraform Plugin | hashicorp/aws |
| URL | a direct download URL |
| Nix | ripgrep (nixpkgs) |
Workshop looks the package up in the catalog and reports how many execution rules it will create so you can see the reach before saving.
Tags
Package rules respect the same tag scoping as other rules. Leave tags empty to apply the rule everywhere, or add one or more tags to limit it to the hosts that carry them.
Policy
The Policy decides what the materialized execution rules do:
- Allow: permit the package’s binaries to run.
- Allow as Compiler: allow the binaries and treat them as trusted compilers for transitive allowlisting.
- Block: prevent the package’s binaries from running.
- CEL: evaluate a CEL expression at execution time to decide allow or block.
For Block and CEL policies you can set an optional Custom Block Message (shown to the user when execution is blocked, HTML supported) and a Custom URL for a help or appeal link.
Preferred rule type
A package rule materializes into execution rules of the type you pick under Preferred Rule Type:
| Rule type | Materializes to | Per-binary filter |
|---|---|---|
| Binary | one rule per binary | available |
| CDHash | one rule per binary | available |
| Signing ID | one rule per signing identity | not applicable |
| Team ID | one rule per signing identity | not applicable |
| Certificate | one rule per signing identity | not applicable |
Binary and CDHash are the only types that expose per-binary identifiers, so the binary-selection filter is available only for them. The signing-identity types cover every binary signed with that identity, so there is nothing per-binary to filter.
Simple filters
Every package rule can be narrowed with the built-in filters, all optional:
- Min Release Date and Max Release Date: keep only versions released within a date window.
- Version Filter: an RE2 regular expression matched against the version string (for example
^1\.to pin to the 1.x series).
These are evaluated when Workshop resolves the package.
Advanced CEL filters 2026.7
Two optional CEL expressions give you finer control than the simple filters. Open Advanced (CEL Filters) in the package rule dialog to set them.
Both filters:
- Must evaluate to a boolean.
truekeeps the version or binary,falsedrops it. - Are ANDed with the simple filters above and with each other. A version or binary is covered only if every filter that applies to it returns
true. - Are evaluated by Workshop when it materializes the rule, not by Santa at execution time. This is a different, smaller surface than the execution-context CEL described in the CEL Guide: there is no execution context here, so no
target.*signature fields,args,ancestors, or return-value keywords likeALLOWLIST, only the variables listed below and a boolean result. (Thetargetvariable in the version filter below is unrelated to execution CEL’starget: here it is a plain build-target string.)
Version selection
The Version Selection filter runs for each build of each version. A version can ship several builds, one per platform target, and target lets you filter those individually. Use it to soak-test new releases, keep only the newest few versions, or drop builds for platforms you don’t ship.
Variables:
| Variable | Type | Description |
|---|---|---|
version | string | The version string, e.g. 1.25.0 |
released_at | timestamp | Upstream release date of this version |
target | string | Build target, e.g. arm64_tahoe |
latest_released_at | timestamp | Release date of the newest version in the filtered set |
version_rank | int | 1 for the newest version, increasing for older ones |
version_count | int | Number of versions in the filtered set |
version_rank, version_count, and latest_released_at are computed over the versions that already passed the simple filters (date window and version regexp), not the entire upstream catalog. Tightening a simple filter changes all three.
Helper functions (the same relative-time helpers used elsewhere in Workshop CEL):
now() // the current time
today() // the current date at UTC midnight
days(N) // a duration of N days
Use now() - days(30) for relative windows. CEL’s duration() literal does not accept a day suffix (duration("30d") is invalid), so build day-scale durations with days(N).
Examples:
// Soak new releases: only trust versions at least 30 days old.
released_at < now() - days(30)
// Keep only versions released within 60 days of the newest one.
released_at >= latest_released_at - days(60)
// Keep only the five newest versions.
version_rank <= 5
// Drop a build target you don't deploy.
!target.contains("bigsur")
// Only versions released in roughly the last year.
released_at > today() - days(365)
Binary selection
The Binary Selection filter runs once per binary within a matched version. It is available only for the Binary and CDHash rule types, which create one rule per binary. Use it to allow a package’s main executable while excluding the bundled helper binaries it ships.
Variables:
| Variable | Type | Description |
|---|---|---|
path | string | Path of the binary inside the package, e.g. Firefox.app/Contents/MacOS/firefox |
hash | string | SHA-256 hash of the binary |
cdhash | string | Code directory hash of the binary |
Paths are package-relative: they include the bundle root (for example Firefox.app/Contents/MacOS/firefox, or wget/1.25.0/bin/wget for a formula), not a path anchored at Contents/. Match with contains and endsWith rather than startsWith so a rule keeps working regardless of the bundle name:
// Exclude bundled helper binaries (auto-updaters, embedded frameworks, etc.).
!path.contains("/Contents/Frameworks/")
// Allow only the app's main executable.
path.endsWith("/Contents/MacOS/firefox")
Materialized rules are ordinary execution rules and are enforced by Santa like any other. See Rule Packs for the related mechanism that materializes a curated set of rules maintained by North Pole Security.