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

No Raw Command Surface

~14 min · firelink, command-injection, typed-capability, security

Level 0Cold Ash
0 XP0/32 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"You don't harden the injection surface. You make sure it was never built."

The Admin Panel That Shells Out

Picture the most natural way to build 'restart a service' into a web hub: take the service name from the UI, drop it into a command string, and run it. It works on the first try, and it's a breach waiting to happen. The moment a name can carry a semicolon, the button that restarts a service can also delete a home directory. This is command injection, and it's not a rare exotic bug — it's the default outcome of letting a user-provided string become part of an executed command.

Remove the Surface, Don't Guard It

The usual response is to sanitize: escape the string, validate it, allowlist characters. Firelink takes the stronger path — it removes the surface entirely. User-provided strings never become executable flags, labels, paths, or shell fragments. Every operation is a typed capability resolved through a registry and a fixed adapter. To restart a service you don't pass a name; you resolve a typed restart-service capability whose service id is a registered value, and the adapter runs a fixed command with no user string anywhere in it. There is nowhere for an injection to live, because nothing the user typed reaches the execution path.

Injection-Proof by Construction

This is the difference between defense and design. Sanitizing is defense: you're guarding a dangerous surface and hoping you caught every escape. Typed capabilities are design: the dangerous surface was never built. A registered id either resolves to a known adapter or it doesn't — there is no clever string that turns 'restart' into 'delete,' because the operation is chosen from a closed, typed set, not assembled from text. The security property isn't a filter you have to keep perfect; it's a shape that has no hole to begin with.

Don't sanitize a raw command surface — refuse to have one. When operations are chosen from a closed, typed set resolved by policy, user input can select an authorized action but can never construct a new one. Injection isn't blocked; it's structurally impossible, which is the only kind of impossible you can trust.

Code

A raw surface (breach) vs a typed capability (no surface)·python
# ANTI-PATTERN: a raw command surface. The classic breach.
def restart(service_name):                 # service_name comes from the UI
    subprocess.run(f"launchctl kickstart -k {service_name}", shell=True)
    #   service_name = 'foo; rm -rf ~'  ->  game over.
    #   Sanitizing this is a game you have to win every single time.

# FIRELINK: no raw command surface. Resolve a typed capability instead.
def restart(cap: Capability):              # a typed value, never a string
    assert cap.kind == "restart-service"
    adapter = registry.restart_adapter(cap.service_id)  # a REGISTERED id
    if adapter is None:
        raise Unauthorized(cap.service_id)  # unknown id -> nothing happens
    adapter.execute()                       # fixed argv; no user string in it
    #   There is no slot for '; rm -rf ~' to go. The surface does not exist.

External links

Exercise

Find a place in a system where user input eventually reaches a command, a query, a path, or a shell (a search box that becomes a query, an admin action that names a resource). Ask whether it's defended by sanitizing or by design. If it's sanitizing, sketch the typed-selection version: a closed set of authorized operations chosen by id, where the user's text selects but never constructs. Notice how the injection question simply stops being askable.
Hint
The test: can the user's input ever change WHICH operation runs or add an argument to it, versus only SELECTING from a fixed set the server defined? If input can construct, you're sanitizing. If input can only select, the surface is gone.

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.