C.W.K.
Stream
Lesson 04 of 05 · published

The Rooms You Refuse to Enter

~11 min · secure-field, denylist, refusal, privacy

Level 0Unlit
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"The most important feature is the one where the app looks at what it could do, and declines."

Refusal as a Feature

Firekeeper holds Accessibility. That means it can read the focused element in your password manager. Nothing in the OS stops it. The only thing standing between "can" and "does" is the app choosing not to — and that choice has to be written into the code as a hard refusal, not left as an intention. Secure text fields are the clearest case: if the focused element is a secure field, Firekeeper reads no context, touches no clipboard, and refuses to insert at all.

Two Layers of Refusal

Layer 1 — secure field detected (OS tells you):
    no context read, no clipboard touch, no insertion. Full stop.

Layer 2 — app denylist (you decide):
    password managers, banking apps, anything the user marks private.
    Dictation simply doesn't operate there.

Layer 1 is mechanical: macOS marks secure fields, you honor the mark. Layer 2 is judgment: some apps are sensitive all over, not just in one field, and the user should be able to say "never here." Both are refusals, and both must be enforced at the earliest point — in the target capture step, before any context is read — rather than checked politely just before insertion. A guard at the door beats a guard at the vault.

Why Refuse Instead of Degrade

The tempting middle ground is "in a secure field, still dictate but skip the context reading." Resist it. Partial operation in a sensitive context means the app is still active there, still holding a target, still one bug away from touching what it shouldn't. Full refusal is simpler to reason about, simpler to verify, and simpler to explain to a user: it does not work in password fields, at all, by design. A capability you never exercise in a room can't be misused in that room.

Enforce refusals at the earliest boundary, not the last one. If the rule is 'never read secure fields,' the check belongs in target capture — before context is gathered — not as a condition on the read itself. Refusals checked late leave a window where the sensitive thing has already been touched. Refuse at the door and the dangerous code never runs.
Capability without refusal is just trust you haven't spent yet. Every app with Accessibility could be a keylogger; what distinguishes a tool from spyware is entirely the refusals it writes down and enforces. If your app holds an invasive permission and has no explicit list of things it declines to do with it, the user is trusting your intentions rather than your architecture — and intentions don't survive refactors.

Code

Refuse at capture time, before anything sensitive is touched·swift
func snapshot() -> TargetSnapshot? {
    guard let front = NSWorkspace.shared.frontmostApplication else { return nil }

    // Layer 2 — user/app denylist: we don't operate here at all.
    if denylist.contains(front.bundleIdentifier ?? "") { return nil }

    let element = focusedElement()

    // Layer 1 — secure field: refuse BEFORE reading any context.
    if isSecureTextField(element) {
        return TargetSnapshot(secure: true)   // carries refusal downstream
    }

    // Only now — in a non-secure, non-denylisted target — read context.
    return TargetSnapshot(app: front, element: element,
                          selection: selectedText(element), secure: false)
}
// Insertion, clipboard handling, and context harvest all honor `secure`.

External links

Exercise

For an app you'd build with an invasive permission, write its refusal list: the specific things it will never do despite being able to. Then decide where each refusal is enforced — and check whether any of them are enforced late enough that the sensitive data was already read.
Hint
A good refusal list is specific and checkable ('never reads secure fields', 'never operates in denylisted apps'), not vague ('respects your privacy'). The enforcement point is what makes it real: a refusal checked after the data is in memory has already failed, even if it then throws the data away. Push every refusal to the earliest boundary where the dangerous path can be skipped entirely.

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 inPlease sign in to comment.

No comments yet — be the first.