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

Read-Only, Loopback-Only

~11 min · read-only, loopback, least-privilege, security

Level 0Open Gate
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Give the brain a window into the vault, not a key. It can look at everything and change nothing."

Two locks on the same door

Pippa needs to see the portfolio to answer questions about it, but it must never touch it. Keep enforces that with two independent locks. First, the host adapter is read-only: it exposes exactly three capabilities — status, read, and search — and no write path at all. Pippa cannot mutate holdings, cash, seeds, watchlists, or simulation runs, because there is literally no adapter method that would let it. Second, the internal Sidekick API is loopback-only: /api/internal/sidekick/* answers only to requests originating from 127.0.0.1, so nothing off-machine can reach it even if it somehow found the URL.

Why 'no write method exists' beats 'writes are checked'

There are two ways to prevent the brain from writing: check permissions on every write, or simply don't build a write path. Keep chose the second, and it's meaningfully stronger. A permission check can be misconfigured, bypassed by a new code path, or forgotten on the next endpoint someone adds. But a capability that doesn't exist can't be misconfigured — there's no method to call, no flag to get wrong. The read-only adapter isn't "writes are denied"; it's "writes are unrepresentable." Least privilege at its cleanest is not a guard on a dangerous power, but the absence of the power entirely.

The safest permission is a capability that doesn't exist. A checked-but-present dangerous operation is one misconfiguration away from firing. An operation you never built cannot be enabled by accident. When a component must never do something, prefer removing the ability over guarding it — you can't misconfigure a method that isn't there.

Why loopback-only is the second, independent lock

Read-only handles "what can Pippa do to the data." Loopback-only handles "who can even reach this door." The internal Sidekick endpoints trust a scheme that only makes sense on the local machine, and the server is configured so that trust is only granted for genuinely-local requests (forwarded_allow_ips=127.0.0.1 makes the proxy header trustworthy). Even a perfectly read-only API shouldn't be reachable from arbitrary network locations, and even a loopback-only API shouldn't be able to write. The two locks defend different threats, so you keep both — belt and suspenders, on purpose.

Defense in depth means the locks are independent. Read-only and loopback-only don't overlap: one bounds capability, the other bounds reachability. If you could only have one you'd be exposed on the other axis. Keep them both precisely because a failure in one — a hypothetical bug that reached the endpoint, or a hypothetical write path — is still contained by the other. Independent locks compound; redundant ones don't.

Code

Two independent locks: capability and reachability·python
# Lock 1 — the host adapter has NO write methods. Not 'writes denied';
# writes are unrepresentable. There is simply no method to call.
class SidekickHost:
    def status(self): ...      # read
    def read(self, scope): ...  # read
    def search(self, q): ...    # read
    # (no set_*, no update_*, no delete_* — none exist)

# Lock 2 — internal endpoints answer only to loopback.
@app.get("/api/internal/sidekick/context/portfolio")
def sidekick_context(request):
    if client_ip(request) != "127.0.0.1":   # off-machine cannot reach it
        raise HTTPException(403)
    return host.read(current_scope())
# forwarded_allow_ips=127.0.0.1 makes the proxy header trustworthy here.

External links

Exercise

Take a component in your system that should only READ some data. Check how 'read-only' is enforced: is it a permission check on write operations that exist, or is the write capability simply absent from that component's interface? Argue why 'the method doesn't exist' is stronger than 'the method is guarded.' Then add a second, independent lock (network reachability) and explain what threat each lock handles that the other doesn't.
Hint
A guarded write is one forgotten check away from an accidental write; an absent write can't be un-absented by a config mistake. And capability limits (what it can do) are orthogonal to reachability limits (who can invoke it) — you want both, because each contains a failure the other can't.

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.