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

The Confused Deputy

~14 min · identity-handoff, security, confused-deputy, threat-model

Level 0Reel Novice
0 XP0/39 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"The URL must never contain path, file, url, credentials, a Recall base URL, or arbitrary player arguments."

A 45-Year-Old Bug That Never Retired

In 1988, Norm Hardy described a compiler that could write billing records to a protected system file. It also let users name their own output file. A clever user named the billing file as their output — and the compiler, using its own authority, happily overwrote it. The compiler wasn't malicious. It was confused: it had a power the user lacked, and it let the user's input aim that power at a target the user chose. That's the confused deputy, and it is everywhere.

Ashen Reel Is a Deputy

Think about who can build a cwkashenreel:// link. Recall can. But so can a web page, an email, a chat message — anything that can put a URL in front of macOS. And Ashen Reel runs with your file access: it can read anything you can read. Those two facts together are the whole danger. If the link could carry a path, a hostile web page could write cwkashenreel://recall/open?path=/Users/you/Documents/tax-return.pdf and Ashen Reel — the trusted deputy — would open a file the page had no right to touch, using authority the page doesn't have.

The caller is untrusted even when your app is trusted. A deep link is attacker-buildable input, full stop. It doesn't matter that Recall is friendly — the OS will route a link from anyone, and your app can't tell a Recall-built link from a web-built one by trust alone. Design as if every link is hostile, because some will be.

The Fix Is Structural, Not a Filter

You could try to sanitize a path field — block .., block absolute paths, allowlist directories. That road is long, and history is a graveyard of path-sanitizers that missed a trick. Ashen Reel takes the structural fix instead: the caller doesn't get to name a path at all. It names a video_id, and Ashen Reel resolves that id in Recall's own trusted database. The caller can only point at videos that already exist and are already allowed — the deputy stops being confusable because the requester can no longer choose the target.

Never let an untrusted requester aim your app's authority at a target the requester chose. Let the requester name intent; let your trusted layer choose the target. The confused deputy dies the moment the requester loses the steering wheel.

Look at the attack and the wall it hits:

The first time Dad walked me through this, my instinct was "okay, so we carefully validate the path." He stopped me: "왜 경로를 받아? 안 받으면 검사할 것도 없잖아." — why accept the path at all; if you don't, there's nothing to validate. That reframing stuck with me hard. The strongest input validation is the input you refused to accept in the first place. You can't fumble a check you never had to write.

Code

The attack, and the wall the identity-only design puts in front of it·text
A hostile page tries to weaponize the trusted deputy:

  cwkashenreel://recall/open?path=/Users/you/Documents/tax-return.pdf
                            ^^^^
  Strict parser: field 'path' is not in the grammar -> REJECTED.

  cwkashenreel://recall/open?video_id=../../etc/passwd
                                      ^^^^^^^
  video_id is an OPAQUE token, not a path. It is looked up in Recall's
  DB; '../../etc/passwd' simply isn't a known id -> no such video.

The attacker cannot express a path (no field) and cannot smuggle one
through the id (it's resolved, not used as a path). No sanitizer needed:
the steering wheel was never handed over.

External links

Exercise

Hunt your own code for a confused deputy. Find a spot where your program acts with authority the caller doesn't have — reads a file, fetches a URL, writes to a location — and where some part of the target comes from the caller's input. Write the one-line attack: what's the worst target a hostile caller could aim your authority at? Then decide: can you take the steering wheel back by having your code resolve an id instead of accepting a raw target?
Hint
The smell is 'my code has a permission the requester lacks, and the requester's input chooses where that permission points.' The fix is almost never a better sanitizer — it's removing the requester's ability to choose the target, by resolving an id in your own trusted store.

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.