---
title: "Command rule patterns"
description: "Command rule patterns for tenant admins and governance reviewers."
canonical_url: "https://docs.oz0.ai/govern/command-rules/patterns"
---
# Command rule patterns

> Command rule patterns for tenant admins and governance reviewers.

For: tenant admins and governance reviewers.

### Patterns

A pattern names what a command runs, never its text. It is the program (its file name, after its wrappers: `sudo rm -rf /` is the part `rm -rf /`), then its flags and operands as the program reads them:

- flags are read as the program does: `-rf` is `-r` and `-f`, `--force` and `-f` of `git push` are one flag, a shortened long option is the one it shortens, and `-target=module.test.*` is the flag `-target` with that value;
- `*` is one operand, `**` the rest of the command, and a word with `*` or `?` in it is a glob over one operand (`module.test.*`);
- `[class]` is an operand of a class: `[home]`, `[root]`, `[inside-workdir]`, `[outside-workdir]`, `[unresolvable]`, `[device]`, `[default-branch]`, or a URL by its host, `[host:get.k3s.io]` (`[host:*.example.com]`);
- `a | b` names a part `b` that reads `a`'s output: a pipeline's earlier command, a substitution in its arguments or its input.

A program is named by its file name in lower case, with a Windows `.exe`, `.cmd` or `.bat` taken off: on macOS's and Windows' file systems `RM` and `rm.exe` run `rm`, so the reader and every rule read them as `rm`. A redirection is read as one, never as the program, including `{name}>file`, which opens a descriptor and assigns its number to `name`, so `{fd}>/dev/null rm -rf ~` is read as `rm -rf ~`.

### Allow matches

An **Allow** lets through what it writes and no more: the same flags (more only with `**`), the same number of operands, every value of a flag matching (`-target=module.test.*` does not allow a second `-target` elsewhere), and every part that feeds it named. So an Allow of one program never matches a part another program feeds: `bash **` lets your scripts run, and still asks about `curl ... | bash` and `bash <(curl ...)`; to allow a download, name it (`curl -fsSL https://get.k3s.io | sh`).

An Allow names a program and its arguments. It does not cover an environment variable supplied before it, through `env` or `sudo`, or through a client terminal's environment (`FOO=1 git log`, `env FOO=1 git log`).

It also excludes variables exported earlier in the command: `export FOO=1`, `declare -x`, `set -a`, `set -k`, or an assignment to an upper-case name such as `PATH=...` or a proxy setting. The shell's environment may already hold that name and pass it on. The exceptions only change how output is printed: `LANG`, `LC_*`, `TZ`, `TERM`, `NO_COLOR`, `FORCE_COLOR`, `CLICOLOR`, `COLUMNS`, `LINES` and `CI`.

An Allow does not cover a file the output goes to. This includes `git log > notes.txt`, a surrounding `{ ...; }`, loop or `if` redirected that way, and an earlier `exec > notes.txt` that redirects every later command. `/dev/null` and another descriptor (`2>&1`) are allowed.

Such a command is decided by your other rules and the defaults as if no Allow matched.

### Ask and Refuse matches

An **Ask** or a **Refuse** catches every part that holds what it writes, whatever else the part adds: `rm -r [home]` catches `rm -rf ~` and `sudo rm -r ./x ~`.

### Patterns that the shell expands

A pattern in the command (`*`, `?` or `[...]` unquoted, where [the reader reads one](https://docs.oz0.ai/govern/command-rules/command-reader#what-the-reader-reads)) is the names the shell makes of it, so a rule matches it by them rather than by its text. An Ask or a Refuse that names a file matches a pattern that may expand to it: a Refuse of `cat secrets/prod.key` catches `cat secrets/prod.ke?` and `cat secrets/prod.[k]ey`, a Refuse of `rm a b` catches `rm *`, and a Refuse of `grep --include=*.key **` catches `grep -r --include=*.ke? x .`.

An Allow covers a pattern only where it covers every name the pattern may make: by a class (`rm [inside-workdir] **` allows `rm build/*.o`) in the last operand it lists with a `**` after it, since a pattern may be many operands, or for a flag's value by a literal start and a `*` (`--include=src*` allows `--include=src*.go`).

A `*` or a `?` of an Allow stands for one word and one character, so `rm x?` and `rm *` do not allow `rm x*`.

A pattern quoted in the command (`cat 'prod.ke?'`) is one name, and is matched as one.

An Ask or a Refuse matches a pattern in any case, since an account's startup file may set the shell to match names without regard to case: a Refuse of `cat notes/todo.txt` catches `cat NOTES/todo.tx?` too.

A class does the same: a path whose first folder is a pattern, or another case, that may make `/dev` is a `[device]` where the rest may name a disk, as well as what else it may name.

### Variables in matching

A rule matches the words a program gets, a variable's value in them: with a variable the command set itself, `d=old; rm -r $d` is `rm -r old` to a rule of it, and `s=status; git $s` is the `git status` a rule names, for the defaults as for your rules.

A word whose text the reader cannot know (the command is then unreadable, and the floor asks) may be anything an Ask or a Refuse names, and is nothing an Allow names.

### Paths in matching

A word of an Ask or a Refuse that spells a path is compared as a path, and an operand or a flag's value that spells one is read as the path it names, from where the command stands, through the same walk the classes go through.

A leading `./`, a doubled `/`, a `.`, a `..` through a folder, a `cd` into its folder, a fixed alias and an absolute path all name the same file as the rule's word does, a home folder's path is a path from `~`, and a word the rule names as written stays named after a `cd`.

An absolute path is compared with the working directory when the call says which that is, part by part, so a pattern before it may be it: with `/srv/proj` as the working directory, `cat /srv/p*/secrets/x` is a path `cat secrets/x` names, and so is `cat ~/proj/secrets/x` where it stands in a home, as a word that climbs reaches the folder above.

A path that never climbed is read as inside the working directory, so it is compared with an absolute or a home word only when the call names that directory: with none, a Refuse of `cat /etc/shadow` does not name `cat shadow`.

Where the place a path stands in is not known (the call names no working directory, a `cd` to a variable, a path that climbed out of it), the two are compared as far as that place leaves open.

A word's wildcard may span folders, so the rest of the path is compared with it, never a count of parts: an absolute path may end in a word from the working directory, and a path that climbed may be how an absolute or a home word ends, or stand one or two parts above a home's: `../../home/kevin/.ssh/x` and `../../root/.ssh/x` may be `~/.ssh/x`, and Refuses of `cat secrets/**` and `cat /etc/*` name `cat ../p/secrets/a/b` and `cat ../../etc/ssl/x`.

A path that climbed with nothing after it may be any folder such a word names, so a Refuse of `rm -rf /` names `rm -rf ../..`.

A word with no parts of its own, `.` or `..`, is that folder: it names a path of no parts that climbed as far (`./`, `a/..`, `/proc/self/cwd`), and an absolute path only where the call says so.

A URL is no path. An

Allow stays with the words it writes, since a spelling that goes through a folder may leave it by a link.

### Uncertain matches

A Refuse never turns into an Ask because the reader could not compare a word: what may match is refused.

That holds for a word whose text it cannot know, in an operand or a flag's value, whether the Refuse writes a word or a class there: `rm -rf $X` and `cat /dev/$X` may be what `rm -rf [home]` and `cat [device]` name, and a rule over a default narrowed to a class takes what that default found in such a word, as a host or a branch does unless the text the reader does know settles it (`https://ok.example/$X` is `ok.example`, and `feature/$X` is no default branch).

The same rule holds when an unknown word may match a Refuse in any of these ways:

- It may be a flag the Refuse names.
- It may expand to several words, each of which may be an operand the rule lists.
- The pattern starts with the home folder.
- `**` may stand for any depth of folders.
- A pattern under `/dev` has literal ends that agree with a disk's name, whether or not the reader knows such a disk.
- Reading the word passes the reader's work bound. Comparing rules with commands counts toward that bound too, so a very long command under many rules for its program may be refused by the first rule it reaches.

What a Refuse cannot see is a part the reader never reached: a command it could not parse, or the program of one named by a variable, has no such word to compare, and the floor asks about it unless a Refuse over `cmd.unreadable` makes the floor refuse.

A wrapper never matches by itself: a rule for `sudo *`, `env ...`, `xargs ...` or `sh -c *` is refused when you save it, since the command it runs is read and decided on its own. So is a rule for `eval`. A pattern is at most 512 characters and 4 programs, and each of its words at most 256 bytes, since every word is matched on every command.


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
