Phase 02 · Filesystem containment

Filesystem containment

Deny-by-default filesystem authority built on resolved objects rather than path strings, because prefix matching on a requested path is how sandboxes leak.

Permanent ruleBitShout does not trust filenames, directory names, paths alone, process names, or PIDs alone.

Filesystem containment starts only after Phase 1 has answered who is asking. Every decision here consumes the containment-session resolver and keeps three facts separate, in the schema and in the interface:

  • observation — BitShout saw, or was asked to evaluate, a filesystem operation
  • policy — BitShout deterministically decided allow, deny, or not governed
  • enforcement — macOS actually allowed or denied it through a BitShout-controlled authorization path

The word blocked is reserved for the third one. A policy-only denial is reported as “Observed — Policy Would Deny”, which is less satisfying to read and considerably more honest.

The operation model

Filesystem operations normalise to a fixed set: read, write, create, open, delete, rename, execute, metadataRead, metadataWrite, directoryEnumerate, link, and unknown.

unknown is denied for contained processes, and it cannot be added to a rule. An operation BitShout does not recognise never becomes authority by being unusual.

Resource identity

This is where most of the work is. Before policy sees a target, BitShout builds a FilesystemResourceIdentity that records, separately:

  • the original requested path
  • the normalised path
  • the canonical path after symlink resolution
  • device, inode, size and modification time where available
  • parent canonical path and parent device/inode, for targets that do not exist yet
  • volume identifier and mount metadata
  • file type, symlink resolution status, existence state
  • volume case-sensitivity where available
  • the Unicode-normalised canonical path

Each field carries an evidence label — authoritative, supporting or advisory — and the labels are not decorative. Caller-supplied path strings are advisory and kept for audit only. Device and inode identity is supporting evidence. Volume metadata is advisory, because it comes from URL resource values rather than from a kernel authorization event, and pretending otherwise would overstate what BitShout knows.

Why resolution beats comparison

Consider a grant for ~/Projects/app, and a symlink at ~/Projects/app/keys pointing at ~/.ssh.

requested          ~/Projects/app/keys/id_ed25519
expanded           /Users/you/Projects/app/keys/id_ed25519
symlinks resolved  /Users/you/.ssh/id_ed25519
device / inode     dev(1,17) : 8814402
verdict            outside grant → deny

A prefix comparison on the requested string allows this, because the string does start with the granted path. BitShout compares the resolved object, so the link leads nowhere. The same reasoning covers hard links where it is technically possible to detect them, rename races, and .. traversal.

What is denied by default

Everything. For a contained subject there is no implicit allow set, so the interesting list is not what is denied but what is representatively denied — the resources a grant should never quietly cover:

  • ~/.ssh, ~/.aws and other credential stores
  • browser profile data
  • unrelated Documents and unrelated source repositories
  • sensitive configuration files
  • mounted network volumes and removable storage

These are denied because nothing granted them, not because their names are on a list. A list of dangerous names is a blocklist, and blocklists are the wrong shape for this problem.

Enforcement boundary

Policy decisions here are designed to be consumed by an Endpoint Security authorization path. The adapter is built, the policy path is proven, and the suite passes on real hardware in our lab. Production runtime denial additionally needs the restricted Endpoint Security entitlement, which is submitted and in review with Apple. Until it is granted, BitShout labels these decisions as policy evaluations rather than operating-system denials. See Trust for the full position.