Thursday, July 30, 2026

fapolicyd 2.0: Writing fapolicyd Rules with Globs and Paths with Spaces

Fapolicyd rules normally explain what to do with each file type. They follow a pattern of "if this file type is trusted, allow it. If not deny it". But sometimes you have to carve out certain areas for specific uses. In those situations, fapolicyd policy normally uses exact values. An exact executable or object path is easy to review and does not match anything else by accident.

fapolicyd 2.0 adds two related rule-writing tools:

glob:     opt in to fnmatch-style matching for exe and path
"..."     keep one complete rule value together when it contains spaces
The important point is that quoting and globbing are separate. Quotes preserve spaces in a rule value. The glob: prefix requests pattern matching. A value can use neither, either one, or both.

Three ways to express a path

Use an exact path or exe value when the name is known:
allow perm=execute all : path=/usr/bin/id trust=1
Use dir when the policy applies to a complete subtree rooted at a fixed directory:
allow perm=open all : dir=/opt/vendor/ trust=1
Use glob: only when one or more path components must vary:
allow perm=execute all : path=glob:/opt/vendor/release-*/bin/tool trust=1

The glob: prefix is accepted only for subject exe and object path. A glob pattern must be an absolute path. dir deliberately remains a literal directory-prefix match, even if its text contains *?, or [.


Exact values, directory prefixes, and explicit globs express different policy shapes.

What a glob matches

Patterns use fnmatch(3) syntax and match the entire concrete path.
*        zero or more characters within one path component
?        exactly one character within one path component
[0-9]    one character from a bracket expression
Very important - wildcards do not cross /. For example:
glob:/home/*/bin/tool

matches:       /home/alice/bin/tool
does not match: /home/alice/project/bin/tool
A wildcard also does not match a leading period unless the pattern names the period explicitly. glob:/home/*/bin/tool does not match /home/.admin/bin/tool, while glob:/home/.*/bin/tool does.

Matching is case-sensitive. Recursive ** globstar matching is not supported. Bracket ranges and named character classes use the daemon's active locale, so use exact alternatives or explicit characters when the same policy must behave identically under every locale.

An unprefixed value remains exact, even if it contains metacharacters:
path=/tmp/name*
matches a literal file named /tmp/name*. To match names such as /tmp/name1, use path=glob:/tmp/name* instead.

fapolicyd obtains object paths from the event file descriptor and subject executable paths from /proc/<pid>/exe. Symbolic links have therefore already been followed. Rules normally match the target path, not the link name used by the application. Hard links and mount aliases can expose different concrete paths for one inode, so policy that must cover those aliases needs a value or
pattern for each reported path.

Paths and directories with spaces

Rule fields are separated by spaces. Enclose a complete string value in double quotes when that value contains a space. The quotes are syntax; they are not part of the path used for matching.
allow perm=execute all : path="/opt/Vendor Tools/bin/tool" trust=1
allow perm=open all : dir="/srv/Team Files/" trust=1
allow perm=open exe="/opt/Vendor Tools/bin/runner" : all
This applies to subject exe, subject and object dir, and object path. Double quotes also work in named string-set definitions and in each comma-separated string value.

To combine the features, quote the complete glob value, including the prefix:
allow perm=execute all : path="glob:/opt/Vendor Tools/release-*/bin/tool" trust=1
allow perm=open exe="glob:/opt/Vendor Tools/release-*/bin/runner" : all

The quotes are removed before glob validation and matching. A quoted dir="glob:/srv/Team Files/" is still an error: dir does not accept globs.


Quote the entire glob value when a pattern contains spaces.

Only double quotes are rule-file quoting. Shell-style single quotes and a backslash before a space are not rule-file quoting forms. %20 is also literal in a rule value; it does not become a space. Inside a quoted value, \" represents a double quote and \\ represents one backslash. Other backslashes are preserved so a glob can still quote its own metacharacters.

Named sets can mix exact values and globs

Named string sets are useful when several rules share a path list. Each pattern in a set or comma-separated value needs its own glob: prefix:

%vendor_tools="/opt/Vendor Tools/bin/tool","glob:/opt/Vendor Tools/release-*/bin/tool"

allow perm=execute all : path=%vendor_tools trust=1
The first set member is exact. The second is a pattern. The rule engine checks the exact values first, then evaluates the marked patterns when needed.

This does not change rule ordering. fapolicyd evaluates rules from top to bottom, and the first complete rule match decides the access. A broad glob before a narrower exact allow or deny can therefore determine the result first.

Keep trust separate from path selection

A glob determines whether a path fits a rule. It does not add the path to the trust database or prove who can write it.

For an allow rule covering a user-writable location, normally pair an object path glob with trust=1 or an explicit FILE_HASH constraint. A subject exe glob in such a location should normally include subject trust=1. The same guidance applies to exact paths when the location is writable by an untrusted principal.

Prefer exact paths, exact named sets, or dir when they express the policy. They are simpler to review and avoid pattern evaluation. Use a glob for the specific variable component that policy needs to express.

Safe syntax demos

The following validation demo creates a temporary policy file only. It does not load the rules into a running daemon. Run the following command:

tmp_rules="$(mktemp)" && \
printf '%s\n' 'allow perm=execute all : path="/tmp/first last/tool" trust=1' 'allow perm=execute all : path="glob:/tmp/first last/release-*/bin/tool" trust=1' > "$tmp_rules" && \
fapolicyd-cli --check-rules "$tmp_rules"; rc=$?; \
rm -f "$tmp_rules"; exit "$rc"


--check-rules validates quotes, the glob: prefix, the allowed attributes, and the absolute-path requirement. It does not expand a pattern or simulate which concrete paths it matches.

Use a deliberate invalid rule to see why a fixed subtree belongs in dir and a variable path belongs in path. Run the following command:

tmp_rules="$(mktemp)" && \
printf '%s\n' 'allow perm=execute all : dir="glob:/tmp/first last/" trust=1' > "$tmp_rules" && \
fapolicyd-cli --check-rules "$tmp_rules"; rc=$?; \
rm -f "$tmp_rules"; echo "exit status: $rc"


The command should report that glob: is valid only with exe and path.

For an installed rules.d policy, assemble and validate the candidate without replacing compiled.rules:

sudo fagenrules --check
If you are testing a source checkout, the focused parser and matching regressions are also available through:

make -C src/tests check TESTS=rules_test


The practical rule

Use quotes to keep a path with spaces together. Use glob: to make a path component variable. Use both by quoting the complete glob: value.

Keep dir for fixed subtrees, keep trust constraints separate from path selection, validate proposed rules before installation, and preserve rule order as part of the policy's meaning.

The complete reference is fapolicyd.rules(5). For release context, see the fapolicyd 2.0 overview article examining decision workers.

No comments: