Skip to main content
ssh, sftp, scp and rsync are not four ways to do the same thing. Each takes a different route through an SSH connection and therefore produces different statements — which is what decides whether a rule can see it.

Fencing a path

A rule never sees the client. It sees a statement, and tests its pattern_regex against that statement’s text. So the only question that matters is what string each route puts into that text. The same file, fetched five ways: The path survives into the text whichever route the client took — as the entire text on an sftp_* operation, as a substring of a command line on exec_line. So one regex catches all five, provided operations names both families:
Leave one family out and it becomes the bypass: Compare rows 3 and 4 of the first table: the same scp command, one flag apart, lands in a different family. Naming only one of them is a hole, not a style choice.
The two families are not the same kind of match.On sftp_* the statement text is the path, so the match is exact and the client cannot restate it.On exec_line the text is an arbitrary command line and the regex is a substring search over it. It catches the path wherever it appears — but the shell expands before anything runs, so X=secrets; cat data/$X.env produces an exec_line carrying no secrets.env at all. A pattern cannot see through expansion. The analyzer, reading the command, can.So a path rule fences every client. It does not fence every spelling.
Scoping earns its keep in the other direction too: unscoped, this regex would also be tested against every env_set variable name and every other statement the lane produces.

sftp

File transfer is the one subsystem admitted by name, because its wire format frames one request at a time — open a path, read or write through a handle, remove, rename, create or list a directory, read attributes. That framing is what turns the subsystem was used into this path was read.
Downloads are masked, exactly as terminal output is — one rule set covers every content-bearing stream the lane produces. A download is read whole before it is rewritten, so unlike terminal output it carries no read boundary a value can be cut on; see Known Limitations.
Paths are resolved from /, not from the home directory. Use absolute paths.
Each operation is its own statement, so the trail reads sftp_read /home/devuser/data/customers.csv, not “a file was transferred”. The ten operations and what each matches against are in Configuration.
sftp requires the Sidecar process to be the account. File transfer is served inside the process — there is no child to hand a credential to — so a root listener cannot admit it. See Root vs non-root.

Uploads

A file the connection reads back is safe to rewrite in place. A file it is writing is not: rewriting those bytes would silently corrupt what the user believes they uploaded. So an upload a masking rule would touch is refused, and nothing is written:
The refusal lands when the file is closed, not when it is opened, because whether the content matches is only decidable once the bytes exist. The client learns late; the file never lands. Silently rewriting a file someone believes they uploaded is worse than refusing it — they would go on believing the original arrived.

scp

OpenSSH 9 changed scp’s default transport to the SFTP subsystem. -O selects the legacy one. Same file, same user, same result — two completely different policy surfaces:
The -O run produces a single exec_line reading scp -f /home/devuser/data/customers.csv. Both are masked. And a rule scoped across both operation families catches both:
This is exactly why the operations list on a path rule must name exec_line and the sftp_* operations. A rule that names only one of them is bypassed by a flag.

rsync

rsync -e ssh runs a remote command, so it lands as one exec_line:
The -e flag string encodes rsync’s own protocol negotiation and varies by version. What matters is that the path is in it, and so is the verb — so a guardrail fences rsync the same way it fences anything else:

Masking conflict

This is not a bug and it cannot be fixed at this layer. rsync checksums the file at both ends. The Sidecar rewrote the bytes in between, so the checksums disagree and rsync correctly discards what it received.Any transfer protocol that verifies its own content integrity will reject masked data. Plan for it: either exclude the paths rsync moves from your mask rules, or accept that rsync and masking do not coexist on the same lane.
Note the direction of the failure: rsync discards the transfer, so nothing lands in a corrupted state. The cost is a transfer that does not complete, not a file that is silently wrong.

Audit trail

There is no setting that adds file content, on purpose: a setting that records nothing is the failure this design refuses everywhere else.

Known issues

Both are defects with fixes pending, not design. Knowing them stops you reading a failure as your own mistake.

Next

Configuration

The ten sftp_* operations, and why there is no sftp_open.

Guardrail Rules

Every rule type, and the full operation vocabulary across protocols.