Skip to main content

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, Workshop re-resolves the package and adds rules for them. When a version or binary no longer passes your CEL filters, Workshop removes its rules. You manage one package rule instead of a growing list of hashes. See How rules stay in sync for what each sync adds, updates, and removes.

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.

info

Package rules are a licensed feature. If the package rule form isn't available, talk with us to get access.

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:

SourceName example
Homebrewwget (formula)
Homebrew Caskfirefox (cask)
NPMexpress
GitHubowner/repo
Rust (crates.io)rustls
VS Codepublisher.name
Terraform Pluginhashicorp/aws
URLa direct download URL
Nixripgrep (nixpkgs)

You can paste several names at once, such as the output of brew list. Each name becomes its own package rule, with the same source, policy, tags, and filters.

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. When you add more than one name, that estimate is skipped.

Catalogs often keep old names as aliases: pkg-config and pkgconf are the same Homebrew formula. Workshop keeps the name you entered and shows the name it resolves to next to it, on both the package rules list and the rule's detail page.

Tags​

Package rules respect the same tag scoping as other rules. Select at least one tag. Pick the global tag to apply the rule everywhere, or pick one or more other 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 typeMaterializes toPer-binary filter
Binaryone rule per binaryavailable
CDHashone rule per binaryavailable
Signing IDone rule per signing identitynot applicable
Team IDone rule per signing identitynot applicable
Certificateone rule per signing identitynot 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 filters decide which versions Workshop asks the catalog about, so they control what gets added. If you narrow a simple filter later, rules that were already materialized stay in place. To make narrowing remove existing rules, use the version selection CEL filter instead. For example, version.startsWith("1.") as a version selection filter removes rules for versions outside the 1.x series, while ^1\. as a Version Filter only stops adding them.

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. true keeps the version or binary, false drops it.
  • Remove rules as well as add them. If a version or binary that was covered stops passing a filter, Workshop removes its rules on the next sync. A rolling window such as version_rank <= 5 retires old versions as new ones ship.
  • 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 like ALLOWLIST, only the variables listed below and a boolean result. (The target variable in the version filter below is unrelated to execution CEL's target: 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:

VariableTypeDescription
versionstringThe version string, e.g. 1.25.0
released_attimestampUpstream release date of this version
targetstringBuild target, e.g. arm64_tahoe
latest_released_attimestampRelease date of the newest version in the filtered set
version_rankint1 for the newest version, increasing for older ones
version_countintNumber 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.

Some versions have no known release date. If the result of your filter depends on released_at or latest_released_at for such a version, Workshop leaves that version undecided: it adds no rules for the version and removes none it already has. For example, both released_at < now() - days(30) and released_at > now() - days(365) leave it undecided. If any version in the filtered set has no release date, latest_released_at is unknown for every version, because the undated version could be the newest. Once the release date is known, the next sync applies your filter as usual. A filter that does not depend on the date, such as version_rank <= 5, still applies to the version.

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:

VariableTypeDescription
pathstringPath of the binary inside the package, e.g. Firefox.app/Contents/MacOS/firefox
hashstringSHA-256 hash of the binary
cdhashstringCode 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")

How rules stay in sync​

Workshop re-resolves a package rule when you create or edit it, when the catalog reports a change to the package, and on a timer every 25 to 35 minutes. Use the Sync Execution Rules action on a package rule to run a sync now.

Each sync runs three stages:

  1. Remove. Workshop checks the rules the package rule already manages against the preferred rule type and the CEL filters. Rules that no longer pass are removed.
  2. Filter. The catalog's current list of versions goes through the same filters.
  3. Add and update. Rules for anything in that list that isn't materialized yet are added. Rules whose policy no longer matches the package rule are updated. This stage never removes a rule.

In practice:

  • A version the catalog no longer reports keeps its rules. Catalogs re-index packages and purge old history, so a missing version is not a reason to remove rules. To remove every rule a package rule created, delete the package rule and check Also delete all execution rules associated with this package.
  • A policy change applies to every rule the package rule manages, including rules for versions the catalog no longer reports.
  • Narrowing a simple filter stops adding rules but does not remove existing ones. Narrowing a CEL filter does both.
  • If you change the preferred rule type, rules for the versions the catalog currently reports are replaced with the new type. Rules for versions it no longer reports keep the old type.

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.