Skip to content
C.W.K.
Lesson 03 of 06 · published

Refusals Are About Places

~14 min · security, credentials, macos-tcc, privacy, launchd

Level 0Loose File
0 XP0/43 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Two Kinds of Place Delta Never Serves

Serving a whole home folder to the other family Macs is generous, and it would be reckless without two exceptions. Delta's architecture states them as invariant 4: credential stores and TCC-guarded folders are never served or written, and a guarded folder is refused before it is touched. Each exception exists for a different reason, and the way each one is checked follows from its reason.

Credential Stores: Lexical, Case-Insensitive, at Any Depth

The first list is the obvious one: the places on a Mac where keys and tokens live. HomeShare is built with three tuples from shares.py:

  • CREDENTIAL_DIRS: .ssh, .gnupg, .aws, .azure, .kube, .docker, .config/gh, .config/gcloud, .config/cwk, Library/Keychains, Library/Cookies, and cwk-dbs/delta-db, Delta's own state, where its tickets, its TLS key and its local token live. These are anchored at the share's root: .ssh means the home's own ~/.ssh and everything below it.
  • CREDENTIAL_NAMES: .env, .env.*, .netrc, .npmrc, .pypirc, .git-credentials, .claude.json, .credentials.json, and the four private-key names id_rsa, id_ed25519, id_ecdsa, id_dsa. These match a single path component anywhere in the tree, so a project's .env.local three folders down is refused like one at the top.
  • CREDENTIAL_PATHS: exact paths, today one, .codex/auth.json.

All three are compared in lower case, because APFS compares names case-insensitively by default: .SSH/config is the same folder as .ssh/config, and a guard that disagreed with the filesystem about that would be a guard with a hole. A name pattern's * and ? never cross a slash, so .env.* matches .env.production but not a folder path that merely starts with .env. In a listing, these entries are simply absent. On a read, the refusal names its reason: …: refused, .env.local is a credential file.

Guarded Folders: Refused Before They Are Touched

The second list exists because of how macOS privacy (TCC, Transparency, Consent and Control) works. Folders such as Desktop, Documents, Downloads, Pictures, Music, Movies, Library/Containers, Library/Group Containers, Library/Mail, Library/Messages and a dozen more are protected: when a process without a grant opens, lists or even stats something inside one, macOS attributes the access to that process's binary and raises a consent prompt.

For a person at a terminal, that prompt is a click. For a launchd daemon it is a hang. The prompt appears on the Mac's screen in the daemon's name, nobody is sitting there to answer it, and the read blocks inside opendir until somebody does. The family learned this from Watchfire's records before Delta existed: a memory engine had registered another app's capture folder under Library/Group Containers as a source, and every observer tick, once a minute, asked macOS for other apps' data on a screen nobody was watching. The rule that came out of it lives in one shared kit file, kit_guarded_paths: a family engine never enumerates a guarded folder, and it asks the kit before it touches a configured path.

So Delta's guard is not a check on the result of a read. It runs before the read, decided from the path alone. The kit's check is lexical first; when a prefix of the path is a symbolic link, it reads the link itself with readlink (which touches only the link's parent, already judged clean) and judges where it leads. With walk=True, meaning the caller wants to recurse, it also refuses any folder that holds a guarded one: the disk root, /Users, the home itself, ~/Library, ~/.local.

The Order Matters

For one read of one file, a DirectoryShare asks in this order:

  1. clean_rel: no NUL, no ...
  2. The credential lists, on the path as asked.
  3. The privacy guard, on the path as asked, before any system call touches it. The house sign for Delta says it plainly: a stat inside a guarded folder raises the prompt too.
  4. One lstat.
  5. The path with every link followed: it must stay inside the share, and it faces the credential lists and the guard a second time. A link from ~/projects/inbox into ~/Downloads is refused exactly as ~/Downloads is.

That is what the phrase refusals are rules about places means. The rule is not "never accept the string Downloads"; it is "never reach the place where downloads live", however the request is spelled.

What It Looks Like From the Other Mac

A plain listing of the home leaves the guarded folders out. A recursive listing of the whole home is refused, because a walk from there would enter them. And a copy from a guarded folder on a Mac that kept the guard is refused by name, with the way out in the message: grant Full Disk Access to Delta's python3 on that Mac and set tcc_guard to false in its host config, ~/.config/cwk/delta.json. That is a decision a person makes once, on purpose, at the Mac in question. Delta never makes it for them, and never makes it by accident.

Code

What the home share refuses, and the guard's message (trimmed excerpt)·python
# delta/shares.py — the credential lists and the privacy guard (trimmed excerpt)

# Credential stores and Delta's own state: never served or written.
# Lexical and case-insensitive (APFS default).
CREDENTIAL_DIRS = (".ssh", ".gnupg", ".aws", ".azure", ".kube", ".docker", ".config/gh",
                   ".config/gcloud", ".config/cwk", "Library/Keychains", "Library/Cookies",
                   "cwk-dbs/delta-db")
CREDENTIAL_NAMES = (".env", ".env.*", ".netrc", ".npmrc", ".pypirc", ".git-credentials",
                    ".claude.json", ".credentials.json", "id_rsa", "id_ed25519", "id_ecdsa", "id_dsa")
CREDENTIAL_PATHS = (".codex/auth.json",)


class DirectoryShare(DataShare):
    def _guard(self, path, rel, *, walk=False):
        why = guarded.guarded_reason(path, walk=walk)
        if why:
            raise Forbidden(f"{self.id}/{rel}: refused, it reaches {why}; macOS would raise a "
                            "privacy prompt in Delta's name (grant Full Disk Access to Delta's "
                            "python3 and set tcc_guard false in ~/.config/cwk/delta.json to allow)")


class HomeShare(DirectoryShare):
    """The account's home, the way scp sees it, minus credentials and guarded folders."""

    kind = "home"

    def __init__(self, **kw):
        kw.setdefault("readers", {"fleet": True})
        kw.setdefault("writers", {"fleet": True})
        super().__init__("home", Path.home(), writable=kw.pop("writable", True),
                         deny_dirs=CREDENTIAL_DIRS, deny_names=CREDENTIAL_NAMES,
                         deny_paths=CREDENTIAL_PATHS, **kw)
Runnable: the same refusals as rules over a path, decided before anything is touched·python
# Refusals as rules about places: credential stores, guarded folders, and the order they are asked in.
import re

CREDENTIAL_DIRS = (".ssh", ".gnupg", ".aws", ".config/gh", "Library/Keychains", "cwk-dbs/delta-db")
CREDENTIAL_NAMES = (".env", ".env.*", ".netrc", "id_rsa", "id_ed25519")
GUARDED = ("Desktop", "Documents", "Downloads", "Pictures", "Movies", "Library/Containers")


def name_pattern(pattern):
    """One deny name as a regex over a single path component: * and ? never cross a slash."""
    return "".join("[^/]*" if c == "*" else "[^/]" if c == "?" else re.escape(c) for c in pattern)


DENY_NAME = re.compile("(?:^|/)(?:" + "|".join(name_pattern(n) for n in CREDENTIAL_NAMES) + ")(?:/|$)")


def credential_reason(rel):
    low = rel.lower()  # APFS compares names case-insensitively by default
    for d in CREDENTIAL_DIRS:
        if low == d.lower() or low.startswith(d.lower() + "/"):
            return f"{d} holds credentials"
    hit = DENY_NAME.search(low)
    return f"{hit.group(0).strip('/')} is a credential file" if hit else None


def guard_reason(rel, walk=False):
    low = rel.lower()
    for g in GUARDED:
        if low == g.lower() or low.startswith(g.lower() + "/"):
            return f"the guarded folder ~/{g}"
    if walk and (low == "" or low == "library"):
        return "a folder that holds a guarded folder"
    return None


def judge(rel, walk=False):
    """Lexical refusals first, then the privacy guard, and only then would a stat run."""
    why = credential_reason(rel) or guard_reason(rel, walk)
    return f"refused: {why}" if why else "may be read (next: lstat, then the same checks on its realpath)"


if __name__ == "__main__":
    for rel, walk in [("projects/app/.env.local", False), (".SSH/config", False), ("projects/.ssh/config", False),
                      ("Movies/trip.mov", False), ("projects/notes.md", False),
                      ("", True), ("", False), ("work/keys/ID_ED25519", False)]:
        print(f"{rel or '~'!r:28} walk={walk!s:5} -> {judge(rel, walk)}")

External links

Exercise

Run the runnable block and read each verdict. Then extend judge with the step it leaves out: given a real temporary folder, resolve the path with os.path.realpath and run both reason functions again on the resolved, share-relative path. Create a symbolic link inbox pointing at a folder you named Downloads inside your temporary root and show that inbox/file.txt is refused only after the second pass.
Hint
Build the tree with tempfile.TemporaryDirectory(), make Downloads/file.txt, then os.symlink("Downloads", os.path.join(root, "inbox")). The lexical pass sees inbox/file.txt and allows it; os.path.relpath(os.path.realpath(...), os.path.realpath(root)) gives Downloads/file.txt, which the guard refuses.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.