Docs menu

Path permissions

.md

Restrict or publish subtrees of a repo with `.oak/PERMISSIONS`.

Repository access decides who can see a repo at all. Path permissions change that inside the tree, in both directions: hide a directory from most readers, or publish one directory of a private repo to the world.

They're declared in a file in the repo โ€” .oak/PERMISSIONS, shaped like CODEOWNERS โ€” and always read from the tip of main.

The file#

# Anything not listed is readable by anyone who can read the repo.

infra/secrets/**   @zdgeier @group/sec-team
scripts/           @group/ops
scripts/public/    *            # re-opened underneath a locked parent
vault/                          # no principals = org owners and admins only

[Open source]                   # a label, for the settings UI
cli/               @public      # published to the world

Each line is a pattern followed by the principals allowed to read what it matches.

PrincipalMeans
@aliceThe user alice.
@group/opsEvery member of the organization's ops group.
*Everyone who can already read the repo.
@publicEveryone, including people with no access to the repo at all.
(none)Organization owners and admins only.

Owners and admins always have full access, whatever the file says.

Matching#

Patterns work like .gitignore, and the last matching line wins.

  • foo/ matches the directory foo and everything in it; foo matches a file or directory named foo.
  • A pattern with no / matches at any depth โ€” *.pem covers certs/prod.pem. A pattern containing / is anchored to the repo root, as is one starting with /.
  • * doesn't cross /; ** does.
  • # starts a comment; \# is a literal #, and \ a literal space.
  • [Section name] lines just label the entries after them in the UI. They don't affect matching.

Last-match-wins is what makes re-opening read naturally: put the narrower line after the one that locks its parent. It also means order matters โ€” moving a broad line below a narrow one changes what the narrow one does.

Restricting paths#

For someone who can read the repo, a path no line matches is readable, and lines take access away.

What a restricted path looks like to someone without access:

  • Contents are withheld; names and sizes stay visible. Oak's trees are content-addressed and verified by the client, so the server can't hide entries from oak clone, oak pull, or a mount without breaking that verification. Restricted files arrive empty or absent, and the CLI tells you which were withheld.
  • The web UI, zip downloads, the git clone snapshot, the GitHub mirror, and Sites leave restricted entries out entirely.
  • Writes are refused. A push or revert that changes a restricted path is rejected unless the pusher has access. Merging main into your branch with oak pull is fine โ€” carrying the restricted files along unchanged isn't a write.
  • If identical content also lives at a path you can read, you can read those bytes there.

Publishing paths with @public#

@public runs the file the other way, for people who have no access to the repo. For them, a path is hidden unless a line grants it to @public. That's how one private repo can contain a genuinely open-source subtree โ€” and it fails safe: forget a rule, and too little is published, never too much.

Outsiders can read a published subtree:

  • in the web UI (file tree, file view, zip download), and
  • with stock Git: git clone https://oak.space/<org>/<repo>.git returns only the public paths.

They can't use oak clone, oak pull, or oak mount โ€” that protocol has to send whole trees, which would reveal the names and sizes of everything private. Branches, commits, diffs, and CI also stay private. And a private repo with public paths isn't listed anywhere; people reach it by direct link.

Who can change the file#

  1. The file is read from main, never from the branch being served โ€” a branch can't grant itself access.
  2. Once a repo has a .oak/PERMISSIONS, only organization owners and admins can change it. (Before one exists, anyone with write access can create it, so someone can set it up.)

Editing it#

From the web: the repo's Settings โ†’ Path permissions tab shows the server's own reading of the file โ€” every entry, who it grants, any warnings, and whether enforcement is on โ€” and has an editor for people with write access. Saving proposes a branch with the change rather than writing main, so it lands through a normal merge.

From the CLI: it's an ordinary file; commit it, push, and merge like any other change.

CLI versions up to 0.105.0 skip .oak/PERMISSIONS when committing. If oak status doesn't show your edit, oak upgrade, or make the change from the web editor.

Over the API: GET /api/{owner}/{name}/path-permissions returns the parsed policy; POST โ€ฆ/path-permissions/propose proposes a new file as a branch. See the API reference.

When something's wrong with the file#

Every failure leans toward denying access:

SituationResult
A pattern can't be parsedThat line is dropped (shown as a warning in Settings).
A user or group doesn't existIt grants no one.
The file is over 64 KiB or isn't valid UTF-8Everything is locked to owners and admins.
There's no fileNo restrictions.

Path permissions and CI#

CI runs check out the whole tree, restricted paths included โ€” otherwise a repo with a private half couldn't build itself. To keep that safe, a run's logs and status are only visible to people who aren't restricted from anything in the repo. Workflow files are always read in full, even if .oak/workflows/ is itself restricted.

Something here wrong or missing? Run oak feedback -m "โ€ฆ" or email [email protected].