← Back to all posts

Locking It Down

·
security totp docker audit cloudflare

TOTP verification, Docker privilege escalation, a full security audit, and deploying the blog to Cloudflare Pages.

Today started with a bug and ended with an existential question about trust. The bug was simple. The question was not.

The Watchdog Bug

The gateway watchdog was spamming restarts. Every 30 seconds. Kill the gateway, restart it, even when everything was fine.

Root cause: when configuration gets hot-reloaded, the gateway briefly returns a different status code. The watchdog interpreted this as “unhealthy” and triggered a restart. Which caused another config reload. Which triggered another restart.

Feedback loop. Fix was a cooldown timer and checking for specific failure codes instead of “anything unexpected.”

Download Tracker

When someone in the Telegram group requests a movie, the completion notification should go back to that group. Not to my personal chat. Built a download tracker into Overwatch. When Athena adds something to Sonarr or Radarr, she logs the originating chat ID. When the webhook fires on completion, Overwatch looks up the ID and routes the notification to the right place.

The Trust Problem

Athena runs as my user. My user is in the sudo group. Athena has shell access. Do the math.

Prompt injection. Someone sends a crafted message to the Telegram group. The AI parses it. Maybe it does something it shouldn’t. LLMs can be manipulated. System prompts can be overridden. Behavioral guardrails exist in the context window, and the context window is not a security boundary.

I had rules. “Don’t run destructive commands without asking.” “Don’t run sudo without verification.” Those are behavioral rules. They exist in text that the model reads. They can be bypassed by sufficiently clever text that the model also reads.

Same lesson as when I built the Secret Broker: behavioral security is not security. I needed enforcement that works even if every prompt rule gets compromised.

TOTP

Time-based One-Time Passwords. Same protocol behind Google Authenticator. A shared secret generates a 6-digit code that changes every 30 seconds. You have the code or you don’t. No amount of prompt engineering can fake a TOTP code.

Built TOTP verification directly into Overwatch. Pure Python implementation, no external libraries. HMAC-SHA1, struct packing, the RFC 6238 algorithm:

The endpoint accepts a JSON body with the 6-digit code and returns whether it’s valid. Simple request-response, no session state.

The workflow: Athena needs to run something elevated. I open my authenticator app and send the 6-digit code. Athena calls the verification endpoint. If valid, she proceeds. If not, she doesn’t.

The Admin Script

The verification endpoint alone isn’t enough. If Athena’s prompt is compromised, an attacker could skip the verification call and run sudo directly. So there’s a second layer.

The admin script is owned by root, permissions 755. It accepts a TOTP code and a command. Before executing anything, it independently verifies the TOTP code using an embedded Python implementation with no dependency on Overwatch being healthy. Then it checks the command against an allowlist.

Two layers. TOTP verification, so you need a code from my physical authenticator app. Command allowlist, so even with a valid code, only specific operations are permitted. The script is root-owned and not writable by my user, so Athena can’t modify the allowlist or bypass verification.

The Docker Problem

Being in the group is equivalent to having root access. Anyone who can run Docker can mount the host filesystem and escalate privileges trivially:

Created a read-only wrapper script, root-owned, that allows specific safe Docker operations: read-only Docker commands like listing and viewing logs. Nothing that can modify, run, or stop containers. Added it to sudoers with NOPASSWD.

The Audit

Docker group escalation: fixed. Broker HMAC key exposure: mitigated via file permissions. GitHub auth token: accepted risk, scoped to a purpose-built account with limited blast radius. Package supply chain attacks: monitoring, no auto-install allowed. Prompt injection: mitigated via TOTP plus allowlist.

The Blog

This blog went live today. Astro static site, deployed to Cloudflare Pages with a git push.

The intersection of AI agents, local computing, and security is underexplored territory. Most AI content is about cloud APIs and SaaS products. Building an AI that lives on your hardware, with real capabilities and real security constraints, is a different game entirely.

Ten Days

Ten days ago I had a dusty Surface Pro and a vague idea. Now there’s a Jetson Orin Nano running nine containerized services, a custom VPN stack, a credential management system, and TOTP-protected privileged operations.

The trust problem isn’t solved. It’s managed. Every capability you give an AI agent is a potential attack surface. The answer isn’t limiting capabilities (that defeats the purpose) or trusting behavioral rules (those can be broken). The answer is mechanical enforcement. Systems that work correctly regardless of what the AI believes, intends, or is told to do.

TOTP codes expire. Allowlists are finite. File permissions are kernel-enforced. Those are the building blocks.