🔄 Topic

Closing a known, accepted gap: my local agent daemon had zero authentication. Anything on the network that could reach it could control it. I built a real pairing-token layer for the daemon and its iOS companion app.


🎯 Goal

Turn “documented as an open gap” into “enforced on every request,” without breaking the dozen scripts and one iOS app that already talk to the daemon.


🛠 What I Did

I added a shared pairing-token layer enforced at every dispatch point in the daemon, then propagated the token through every client that talks to it.

Main areas covered:

  • generating a secrets.token_urlsafe(32) token on first run, persisted with chmod 600
  • checking Authorization: Bearer <token> for API/script clients and ?token= for browser navigation, using hmac.compare_digest for a timing-safe check
  • enforcing the check at the only three real dispatch points (do_HEAD, do_GET, do_POST) instead of scattering checks across every handler
  • updating five local shell/Python scripts and a webhook to send the bearer token
  • storing the token in iOS Keychain instead of UserDefaults, since this token is meaningfully more sensitive than the app’s other local preferences — it grants remote control of the laptop
  • giving the iOS network layer one shared URLSession with the header baked in, instead of touching 28 separate call sites

🔗 Key Cybersecurity Connections

The most interesting decision wasn’t adding the token, it was where I chose not to reuse existing code. The daemon’s shared link() helper embeds the real token into every internal href so browser navigation carries it forward automatically. That’s convenient — and exactly why the unauthorized-request 401 page can’t use it. Reusing link() on a page served to someone with no valid token would print the real token onto a page they can already see, defeating the whole point. The 401 page is a deliberately separate, minimal template that never touches the shared helper.

I also chose Keychain over the codebase’s existing UserDefaults convention for this one value specifically, because not everything a device stores deserves the same protection — a remote-control credential and a UI preference are not the same risk class, even in a small app.


🔍 Investigation Questions

  • Does every page or endpoint that should require auth actually go through the same check, or did one route slip through?
  • Could any code path leak the valid token to an unauthenticated caller?
  • Is the comparison timing-safe, or does a naive == leak timing information about how many characters matched?
  • Where is the token stored on each client, and does that storage match how sensitive the token actually is?
  • What’s the blast radius if this one shared secret leaks — and is there any way to revoke access for a single device without rotating it for everyone?

🚨 Detection Opportunities

Potential monitoring ideas:

  • repeated 401 responses from a single source (token brute-force or a misconfigured client)
  • successful auth from an IP/device that’s never authenticated before
  • the token file’s permissions changing from 600
  • code changes that call the shared “authenticated” link helper from an unauthenticated context

Example:

project=local-agent-daemon
change_type=pairing_token_rollout
risk_area=shared_secret_authentication
triage=confirm_no_leak_path_before_and_after_each_route_change

🧭 MITRE ATT&CK Techniques

Framed as the defensive side of techniques this control is meant to raise the cost of:

  • T1078 — Valid Accounts (this is the control against a stolen/reused credential granting access)
  • T1552 — Unsecured Credentials (the risk this control is explicitly designed to close: a token stored in plaintext or logged accidentally)

🗺 Visual Investigation Diagram

Daemon with zero auth (accepted, documented gap)
    ↓
Generate + persist pairing token (chmod 600)
    ↓
Enforce at every real dispatch point
    ↓
Propagate token through every client
    ↓
Separate, token-free 401 page for unauthenticated requests

⚠ Challenges

The honest limitation: this is one shared secret, not per-device identity. There’s no way to revoke a single compromised device without rotating the token for every legitimate client too. I documented that as an open architectural gap rather than pretending the token model gives per-device revocation it doesn’t.


📚 What I Learned

I learned that reusing a convenient shared helper is a trap the moment that helper starts embedding a secret — the same code path that makes authenticated pages easy to build is exactly the one that must never run for an unauthenticated visitor.


➡ Next Steps

  • Decide whether per-device tokens are worth the added complexity over one shared secret
  • Verify the full flow by hand on a real device, not just build-verify it
  • Keep watching whether the Hermes-notification webhook’s token wiring holds up under real traffic

🧠 Reflection

Retrofitting auth onto something that’s worked fine without it for a while is a good test of discipline — it would have been easy to bolt on a check “somewhere” and call it done. Finding the three real dispatch points, and finding the one place a shared helper couldn’t be reused safely, mattered more than the token generation itself.


🧩 Lessons Learned

What worked

Finding the actual chokepoints (three dispatch methods, one link helper) instead of scattering auth checks across every individual handler.

What broke

Nothing broke, but it came close — reusing the token-embedding link helper on the 401 page would have leaked the token to unauthenticated requests.

Why it broke

Convenience helpers that embed secrets are unsafe by default in any context that hasn’t already proven the caller is authenticated.

Fix / takeaway

Before reusing a shared helper, check what it silently carries with it — not just what it renders.


📈 Skill Progression Context

This supports my cybersecurity progression because it’s a real, small-scale version of access-control engineering: token generation, timing-safe comparison, credential storage matched to sensitivity, and — most importantly — catching a leak path before it shipped instead of after.


😄 TL;DR

Added real pairing-token auth to my agent daemon, and the near-miss was almost leaking the token through the same helper that made the auth convenient in the first place.