Skip to content

The rules file format

Every field of rules.json and settings.json: the envelope, the rule fields, the matchers and the settings keys, as Outbound writes and reads them.

Outbound 1.2.0 · Updated

Outbound keeps its rules in rules.json and its settings in settings.json, both in the app group container (or wherever OUTBOUND_STORE_PATH points). Both files are versioned JSON, written by the app and the background agent; outboundctl reads and writes the same formats. You rarely need to edit them by hand: the Rules pane and outboundctl rules are the supported ways to change rules, and backup is the supported way to move a configuration. But every field is documented here, because the same formats are what a managed rule feed delivers and what outboundctl rules import and rules export read and write.

Outbound Rules: rules grouped by app and written as sentences, with blocklist, profile and organization tags, lifetimes, and a checkbox to turn each rule on or off
Example data.
~/Library/Group Containers/5GS535L4GD.com.outboundfirewall.outbound/
├── rules.json
├── settings.json
└── review.json

Full details, including the .bak and .lock sidecar files, are in Where Outbound keeps its data.

{
"schemaVersion": 2,
"ruleSet": {
"rules": [ ],
"blocklists": [ ]
}
}
  • The envelope carries schemaVersion (2 for files this version writes) and ruleSet.
  • rules holds every rule that is not compacted into blocklists.
  • blocklists holds compacted entries from blocklist subscriptions: rules that name no process, go only outgoing, have any port and protocol, live forever, are enabled, are not priority, belong to no profile and have no note. Entries are grouped by subscription, action and creation date, with the hosts and domains as separate arrays. This is a size optimization; expanding the groups on load reproduces the same rules, so the two shapes are equivalent. Rules that do not fit the pattern (including addresses and networks from a list) stay in rules as ordinary rules.
  • configurationDegraded and configurationDegradedReason are optional diagnostic fields; older decoders ignore them.

A file with "schemaVersion": 1 (an older build) is migrated automatically on load. A schema above 2 is never overwritten and is reported with “The rule store uses schema version N, which this version of Outbound does not understand.”

Dates (createdAt, modifiedAt and the date inside an until lifetime) are written as numbers counting seconds since the 2001-01-01 reference date, so a round trip is lossless. When reading, ISO-8601 strings (with or without fractional seconds) are also accepted. modifiedAt is omitted when it equals createdAt.

Field Type Meaning
id UUID Identifies the rule. Unique in the store: a save with a repeated id is refused.
action String allow, deny or ask.
direction String outgoing, incoming or any.
process Matcher Which program the rule is for. See below.
via Matcher, optional An intermediate program (a browser, a helper). Absent or {"any":{}} means no via condition.
remote Matcher Which destination. See below.
ports Matcher {"any":{}} or {"ports":{"_0":[[80,80],[8000,8100]]}} with ranges of port numbers 0 to 65535.
proto String tcp, udp, icmp or any.
lifetime Matcher How long the rule lives. See below.
priority Boolean Priority rules are compared before everything else.
enabled Boolean A disabled rule is kept but never matches.
profileID UUID, optional The profile the rule belongs to; nil applies in every profile.
source Matcher Who owns the rule: user, managed, ruleGroup or blocklist. See below.
createdAt Date When the rule was created.
modifiedAt Date When the rule’s meaning last changed; omitted when it equals createdAt.
note String The rule’s note; usually empty.
processCDHash String, optional Code hash the rule was approved for, narrowing a path rule.
processPinnedPath String, optional The original approved path.

The same fields are required or optional in a managed rule feed; a feed rule’s source is replaced with managed on install whatever it says.

Matchers are encoded the way Swift encodes them: one key for the case, holding an object. An unlabelled value sits under _0 (and _1, and so on, for several); a labelled value uses its own name; a nil labelled value drops out of the object.

Process matchers (which program):

JSON Meaning
{"any":{}} Every program.
{"codeIdentity":{"teamID":"ABCDE12345","signingID":"com.example.app"}} A signing identifier, optionally pinned to a team. teamID may be omitted.
{"path":{"_0":"/usr/bin/curl"}} The path of an executable.
{"appleSigned":{"signingIDPrefix":"com.example.prefix"}} Code signed by Apple, verified by the filter, whose signing identifier starts with the prefix; a nil prefix drops the signingIDPrefix key (just {"appleSigned":{}}) and means any Apple-signed code.
{"appleSignedCode":{"_0":"system"}} Apple-signed code outside any .app bundle (daemons, system services). "app" means code inside an .app bundle. Used by the “Trusted by default” groups.

Remote matchers (which destination):

JSON Meaning
{"any":{}} Every destination.
{"domain":{"_0":"example.com"}} The domain and all its subdomains.
{"host":{"_0":"a.example.com"}} Exactly that host.
{"address":{"_0":"203.0.113.7"}} One IPv4 or IPv6 address, normalized.
{"network":{"_0":"10.0.0.0/8"}} A CIDR network, IPv4 or IPv6.
{"localNetwork":{}} The local network ranges (10/8, 172.16/12, 192.168/16, 169.254/16, 127/8, ::1, fe80::/10, fc00::/7) plus your own extra ranges.

Lifetimes:

JSON Meaning
{"forever":{}} Never expires.
{"untilQuit":{"pid":1234,"processStart":<date>}} Until the process quits (pid and start time).
{"untilLogout":{"sessionID":"<sessionID>"}} Until this login session ends.
{"untilRestart":{"bootSessionUUID":"<bootUUID>"}} Until this Mac restarts.
{"until":{"_0":"2027-01-01T00:00:00Z"}} Until a date.
{"schedule":{"_0":{"weekdays":[1,2,3],"start":540,"end":1020}}} A weekly window in local time; weekdays are 1 to 7, start and end are minutes from midnight, and an overnight window belongs to the day it starts.

Source:

JSON Meaning
{"user":{}} Your own rule.
{"managed":{}} From the organization’s feed.
{"ruleGroup":{"_0":"<uuid>"}} A rule group: trusted-by-default groups, accepted allow-list entries.
{"blocklist":{"_0":"<uuid>"}} A rule from a blocklist subscription.

Which rule wins when several match is decided by precedence, not by file order: see Rules.

  • Saves are atomic and durable, and the previous file is copied to rules.json.bak first, but only when that file is itself valid, so a corrupt main file never replaces a good backup.
  • Writes are serialized by an advisory lock on rules.json.lock, so the app, the background agent and outboundctl never lose each other’s changes.
  • If both the main file and the backup are unreadable, the store reports an error rather than starting empty. outboundctl rules import is the supported way to replace an unreadable store: it starts from the imported file.
{
"schemaVersion": 1,
"settings": { }
}
  • The envelope is {"schemaVersion":1,"settings":{…}}, written pretty-printed with sorted keys, and serialized by the same kind of .lock file.
  • A missing file means first launch and gets the defaults (Learn mode). A file that exists but cannot be read or decoded is an error: the app never treats that as “defaults” and overwrites it. A schema above 1 is never overwritten, with “settings.json uses schema N, which this version does not understand”.
  • Managed preferences are never written here. They are read from the managed-preference domain and laid over the policy at run time. See Managed preferences.
Key Type Meaning
policy Object The policy pushed to the filter: mode, timeouts, pausing. See below.
blocklists Array Subscriptions: id, name, url, action (deny for a blocklist, allow for an allow list), pinnedSHA256 (optional), refreshInterval (seconds; default 86400), lastFetched, etag, peakEntries.
profiles Object profiles (each with id, name, and an optional mode that comes on with the profile) and activeID (nil = No Profile).
trustedGroups Object Which “Trusted by default” groups are on (enabled), the apps the “apps you already have” group was built from (installedAppsSnapshot), and the VPN apps the VPN group was last built from (vpnApps).
knownNetworks Object Networks this Mac has joined (networks, each with id, fingerprint, name, profileID, lastSeen, neverAsk), plus askOnNewNetwork (the master switch for automatic switching) and defaultAction (ask, keepCurrent, useProfile or noProfile).
dnsEncryption Object Choices only, never a system DNS configuration: provider (quad9, cloudflare, google, nextDNS with a profile id, or custom), transport (https or tls), exceptions.
pauseNetwork String, optional The network fingerprint a pause-until-network-change is tied to.
onboarding Object Setup choices: passedIntroduction, completed, the trust groups offered during setup (macOSServices, iCloudServices, appleApps, existingApps, localNetwork, existingAppNames).
downloadedAllowGroups Array Allow-list subscriptions staged for review: each group has id, name and entries (each with id, remote, state of pending or accepted). Pending entries are not rules.
notifications Object One switch per notification kind, all boolean: approvalNeeded, operationalProblems, securityEvents, ruleActivity, profileChanged, blocklistUpdateSucceeded, blocklistUpdateFailed, reviewWaiting, deniedDuringOSUpdate, heavyTelemetry.
assistantKeepsHistory Boolean Whether Assistant transcripts stay on this Mac. Turning it off preserves existing conversations.
menuBar Object warnWhenFilterOff (default true), flashOnBlock (default false), countLocalTraffic (default false).
monitor Object historyCapacity (default 5,000, range 1,000 to 50,000) and autoUpdateGeoDatabase (default true).
telemetryBlock Object The “Block known telemetry” settings: enabled (default off), categories, apps (signing ids of apps with telemetry turned off), mutedApps, appIdentities.
telemetryDatabaseVersion String The tracker database version the telemetry rules were last built from; empty until the first reconcile.
isLocked Boolean Whether the settings lock is on. Default false.
lockRuleEditing Boolean Default true. See Locking settings.
lockProfileSwitching Boolean Default true.
lockSettingsEditing Boolean Default true.
allowOutboundctlChanges Boolean Default true.
alertDetailLevel String compact or full. Default compact.
confirmWithReturnEscape Boolean Whether Return and Escape answer an alert. Default true.
alertsTakeKeyboardFocus Boolean Off by default: a new alert becomes the key window once its answers arm.
denyWarningSuppressions Array of strings Deny warnings you chose not to show again.
markNewRulesUnapproved Boolean Default false. See Review.
approveRulesOnSelect Boolean Default true.
dataRateUnit String bytes or bits. Default bytes.
extraLocalRanges Array of strings Extra CIDR ranges counted as local network, on top of the standard private ranges.
Key Type Meaning
mode String alert, silentAllow or silentDeny.
managedRulesOverride Boolean Managed rules beat everything except priority rules.
usersMayEditRules Boolean Whether the user may edit rules.
disableAlerts Boolean ask becomes deny in silentDeny mode, allow otherwise, without an alert.
approvedBlocklists Array Subscriptions accepted with the enforced configuration (id, url, action, pinnedSHA256).
extraLocalRanges Array of strings Mirrored here so the filter sees them.
ruleFeedURL URL, optional The feed address. Nothing in the app sets it in the stored policy, so it stays absent here: the feed address comes from the managed preference and is laid over the policy at run time.
ruleFeedPinnedSHA256 String, optional Likewise for the feed pin.
tcpAlertTimeout Number Seconds a TCP connection waits for an answer before the timeout action applies: 15 to 120, default 60.
activeProfileID UUID, optional The active profile; nil = no profile.
timeoutAction String What an unanswered flow gets: allow or deny (ask reads as allow). Default allow.
requireApprovalToUnblock Boolean Default true. Changes that unblock something need approval with Touch ID or the login password.
pausedUntil Date, optional A timed pause deadline.
pauseBootSessionUUID String, optional Ties a pause to the current boot, so the next restart ends it.
appAbsentAction String, optional What a connection gets when the app that would answer is absent. nil = same as the timeout action.