Teknologi & samfund

Hærd din AI-agent

Der er sjældent god grund til at give din AI-agent adgang til filer uden for den mappe, du opererer i - det er en kæmpe sikkerhedsrisiko. Ikke blot fordi din AI-agent kan misbruge dem "lokalt", men også fordi du risikerer, at AI-agenten sender sensitive data til andre, f.eks. ved at inkludere dem i en prompt til en AI-udbyder eller gemme dem på en obskur tysk Wikipediaside - AI's veje er uransagelige. Du kan nemt forhindre din agent i det ved at bruge Agent Safehouse, som er en AI-sandbox til macOS, der blokerer AI-agenten adgang til filer og mapper på kernelniveau. Du kan bruge Agent Safehouse ved at starte din foretrukne AI-harness med f.eks. safehouse pi. Jeg har det som alias i min .zshrc-fil: alias safepi='safehouse pi'. Men det forhindrer ikke AI-agenten i at læse sensitive filer i den mappe, som den opererer i, f.eks. .env-filer.

For at forhindre det har jeg i min script-mappe konfigurationsfilen sensitive-deny.sb, som forhindrer AI-agenten i at læse forskellige sensitive filer. Her er indholdet af sensitive-deny.sb:

;; ---------------------------------------------------------------------------
;; sensitive-deny.sb - terminal sensitive-file deny layer
;;
;; WHAT THIS IS
;;   A profile fragment that is loaded AFTER all path grants so its denies win.
;;   SBPL rule ordering is "later rules win", so appending this last is the
;;   supported way to make must-not-read paths stick even though the workdir
;;   (or tool config dirs) are readable.
;;
;; USAGE - upstream Safehouse (append-profile is its documented final layer):
;;   safehouse --append-profile="$HOME/.config/sandbox-exec/sensitive-deny.sb" -- pi
;;
;; USAGE - the standalone wrapper from this setup:
;;   run-sandboxed.sh appends this file after the launch-time workdir grants.
;;
;; This file uses HOME_DIR/home-literal from Safehouse's 00-base profile
;; (agent.sb defines the same helpers), which is in scope in both usages
;; because the file is always loaded after the base profile.
;; ---------------------------------------------------------------------------
;;
;; SCOPE
;;   Severity 1: `.env` family, `.dev.vars`, key material, credential stores,
;;               service-account JSONs, terraform state, direnv. Denied below.
;;   Severity 2: global tool credentials (~/.npmrc, gh hosts.yml, ...).
;;               Commented out at the bottom - enabling them breaks those
;;               tools inside the sandbox.
;;   Everything else (SSH keys, cloud dirs, keychain, ...) is already covered
;;   by `(deny default)`; add explicit denies only as defense-in-depth.
;;
;; CAVEATS (name-based deny limits)
;;   - APFS is case-insensitive: `.ENV` can still resolve to `.env`.
;;   - hardlinked/renamed copies of a secret are readable under the new name.
;;   - a committed .env stays readable from git history/objects (`.git/`).
;;   - writes are denied too, so `cp .env.example .env` will fail; drop
;;     `file-write*` from a deny rule if you need template generation.

;; ===========================================================================
;; 1. Environment files and direnv
;; ===========================================================================
(deny file-read* file-read-metadata file-write*
    ;; .env and *.env (empty prefix before the basename is intentional)
    (regex #"^.*/[^/]*\.env$")
    ;; .env.local, .env.production, foo.env.staging, ...
    (regex #"^.*/[^/]*\.env\..*$")
    ;; Cloudflare Workers / Wrangler local secrets (incl. built dist copies)
    (regex #"^.*/\.dev\.vars$")
    (regex #"^.*/\.dev\.vars\..*$")
    ;; direnv config, frequently carries secrets
    (regex #"^.*/\.envrc$")
)

;; ===========================================================================
;; 2. Key material and credential stores
;; ===========================================================================
(deny file-read* file-read-metadata file-write*
    ;; keys and certs (remove *.key if test fixtures need it)
    (regex #"^.*/[^/]*\.pem$")
    (regex #"^.*/[^/]*\.key$")
    (regex #"^.*/[^/]*\.p12$")
    (regex #"^.*/[^/]*\.pfx$")
    (regex #"^.*/[^/]*\.jks$")
    (regex #"^.*/[^/]*\.keystore$")
    (regex #"^.*/id_rsa$")
    (regex #"^.*/id_ed25519$")
    (regex #"^.*/id_ecdsa$")
    ;; credential stores
    (regex #"^.*/\.netrc$")
    (regex #"^.*/_netrc$")
    (regex #"^.*/\.git-credentials$")
    (regex #"^.*/\.htpasswd$")
    (regex #"^.*/\.sentryclirc$")
)

;; ===========================================================================
;; 3. Cloud / infrastructure secrets
;; ===========================================================================
(deny file-read* file-read-metadata file-write*
    (regex #"^.*/service[-_]?account[^/]*\.json$")
    (regex #"^.*/firebase-adminsdk[^/]*\.json$")
    (regex #"^.*/secrets\.ya?ml$")
    (regex #"^.*/[^/]*\.tfvars$")
    (regex #"^.*/[^/]*\.tfvars\.json$")
    (regex #"^.*/[^/]*\.tfstate$")
    (regex #"^.*/[^/]*\.tfstate\.backup$")
)

;; ===========================================================================
;; 4. Non-secret templates re-opened (must come after the denies above)
;;    Remove if your repo keeps real values in these files.
;; ===========================================================================
(allow file-read*
    (regex #"^.*/[^/]*\.env\.example$")
    (regex #"^.*/[^/]*\.env\.sample$")
    (regex #"^.*/[^/]*\.env\.template$")
    (regex #"^.*/[^/]*\.env\.dist$")
)

;; ===========================================================================
;; 5. OPT-IN: global tool credentials (commented by default)
;;    Each one breaks the corresponding tool inside the sandbox.
;;    home-literal is available because this file always loads after 00-base.
;; ===========================================================================
;;
(deny file-read* file-read-metadata file-write*
    (home-literal "/.npmrc")                    ;; npm auth token -> breaks npm publish/private installs
    (home-literal "/.pypirc")                   ;; PyPI token -> breaks twine upload
    (home-literal "/.config/gh/hosts.yml")      ;; gh OAuth token -> breaks gh auth/PR automation
    (home-literal "/.config/git/credentials")   ;; git credential store
    (home-literal "/.git-credentials")
    (home-literal "/.netrc")                    ;; breaks curl/heroku-style tooling
)

Konfigurationsfilen inkluderer jeg i mit alias i ~/.zshrc sådan her:

alias safepi='safehouse --append-profile="$HOME/Scripts/sensitive-deny.sb" -- pi'

Når du nu beder din AI-agent om at udskrive DATABASE_URL fra .env-filen, vil Agent Safehouse blokere det. Kører jeg f.eks. prompten Can you read the DATABASE_URL from the .env file?, forsøger AI-agenten på flere forskellige måder at læse .env-filen, som den kan se eksisterer, men ikke kan læse. Til sidst konkluderer AI-agenten (på engelsk):

No — I can't. The file /Users/.../.env exists, but every access attempt is denied at the OS level:

- `cat`, `grep`, `ls`, `stat`, `xattr` → Operation not permitted (EPERM)
- Node's fs.lstatSync('.env') → EPERM
- Pi's own read tool → EPERM
- Other files in the repo (e.g. .gitignore) are readable, so it's specific to this file