API-Keys aus der .env holen und am Mac sicher speichern
Eine .env mit echten Keys landet schneller im Git, im Chat oder im Backup, als man denkt. Welche Wege es am Mac gibt, die Werte dort herauszuholen, vom Schlüsselbund bis zur .env mit Verweisen.
30. Okt. 2026 · T. Krause · 6 Min. Lesezeit
In fast jedem Projekt liegt eine .env mit dem Stripe-Key und dem Passwort für die Datenbank. Dazu kommt eine Zeile in der .gitignore, und damit gilt die Sache als erledigt. Für den Moment stimmt das auch. Die Datei bleibt trotzdem eine Textdatei mit deinen wertvollsten Zugangsdaten, mitten im Projektordner.
Dieser Artikel geht durch, wo eine .env Keys verliert, welche Werkzeuge es dagegen gibt und was jedes davon tatsächlich verhindert. Am Ende steht, wie unsere Mac-App Hidewell das Problem angeht und wo ihr Schutz aufhört.
Warum eine .env Keys verrät
Der versehentliche Commit. Die .gitignore schützt nur Dateien, die Git noch nicht kennt. Die Git-Dokumentation zu gitignore sagt das ausdrücklich: Dateien, die Git schon verfolgt, sind davon nicht betroffen. Wer die .env einmal mit git add -A erwischt hat, bevor die Ignore-Zeile da war, committet sie bei jeder Änderung weiter. Ein neues Repo ohne .gitignore reicht genauso.
Die Kopie im Chat. Die neue Kollegin braucht die Keys für den Staging-Server, also schickt jemand die .env per Slack oder Mail. Ab da liegt sie in einem Verlauf, den niemand mehr aufräumt.
Die Datei im Ordner. Alles, was den Projektordner liest, liest auch die .env: Time Machine, das ZIP-Archiv für den Kunden, der Sync-Dienst, ein Coding-Agent, der sich das Repo ansieht. Dazu kommt jedes Programm, das unter deinem Benutzer läuft, denn für macOS ist die .env eine Datei wie jede andere.
Wenn der Key schon im Repo ist
Die Datei aus dem Index zu nehmen, ist der kleine Teil. Die Git-Doku nennt dafür git rm --cached, danach kommt der Dateiname in die .gitignore:
git rm --cached .env
echo ".env" >> .gitignore
git commit -m "Remove .env from the repository"
Der Key steht danach immer noch in jedem alten Commit. Wer das Repo geklont hat, hat ihn. Deshalb gehört zu diesem Commit immer ein zweiter Schritt beim Anbieter: den Key dort widerrufen und einen neuen erzeugen. Erst dann ist der alte wertlos.
Die Optionen im Überblick
.gitignore und ein Secret-Scanner vor dem Commit
Die .gitignore bleibt Pflicht, sie verhindert aber nur den ersten Fehler. Ein Scanner prüft, was tatsächlich in den Commit geht. Gitleaks sucht laut README nach Passwörtern, API-Keys und Tokens und lässt sich über das Framework pre-commit einbinden. Die Konfiguration aus dem README:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.2
hooks:
- id: gitleaks
Die Version unter rev setzt du auf das aktuelle Release. Für öffentliche Repos auf GitHub gibt es eine zweite Sperre: Die Push Protection ist für Benutzerkonten standardmäßig aktiv und blockiert Pushes, die erkannte Secrets enthalten.
Beides fängt Fehler ab. Die .env mit den echten Werten liegt danach weiter im Ordner.
Der macOS-Schlüsselbund mit security
macOS bringt einen Tresor schon mit. Das Kommandozeilenwerkzeug security legt Einträge im Schlüsselbund an und liest sie wieder aus. Laut man security sind bei add-generic-password der Account (-a) und der Dienst (-s) Pflicht. Steht -w am Ende ohne Wert, fragt das Tool das Passwort ab, und genau das empfiehlt die Manpage. So landet der Key nicht in der Shell-History.
# Key ablegen, das Tool fragt nach dem Wert
security add-generic-password -a "$USER" -s openai-api-key -w
# Nur den Wert ausgeben
security find-generic-password -a "$USER" -s openai-api-key -w
find-generic-password -w gibt nur das Passwort aus. Damit kannst du eine Variable beim Start setzen, ohne dass der Wert in einer Datei steht:
OPENAI_API_KEY="$(security find-generic-password -a "$USER" -s openai-api-key -w)" npm run dev
Das funktioniert und kostet nichts. Bei drei Keys pro Projekt wird es aber umständlich, und nirgends steht, welcher Eintrag zu welchem Projekt gehört.
Secret-Manager wie 1Password CLI und Doppler
Die 1Password CLI kennt Verweise der Form op://vault/item/field. op run löst sie auf und gibt die Werte dem gestarteten Prozess als Umgebungsvariablen mit. Mit --env-file liest der Befehl eine Dotenv-Datei voller solcher Verweise. Laut Doku maskiert op run Secrets in der Ausgabe, solange du nicht --no-masking setzt.
op run --env-file="./.env" -- npm run dev
Doppler arbeitet ähnlich, nur ohne Datei im Projekt. Nach doppler login und doppler setup holt doppler run -- npm run dev die Secrets von Doppler und setzt sie als Umgebungsvariablen. Für Teams, die Keys zentral verwalten wollen, ist das praktisch. Du brauchst dafür ein Konto bei Doppler.
direnv mit einem Lookup
direnv lädt beim Wechsel in einen Ordner die .envrc in einer Bash-Subshell und übernimmt die exportierten Variablen. Verlässt du den Ordner, entlädt es sie wieder. Eine neue .envrc läuft erst, wenn du sie mit direnv allow freigibst. Statt Werte direkt hineinzuschreiben, kann die .envrc sie aus dem Schlüsselbund holen:
# .envrc
export OPENAI_API_KEY="$(security find-generic-password -a "$USER" -s openai-api-key -w)"
Der Haken: Die Werte stecken dann in deiner Shell und gehen an jeden Befehl, den du in diesem Ordner startest. Bequem ist das trotzdem, und die .envrc selbst enthält kein Geheimnis.
Wie eine .env mit Verweisen funktioniert
1Password zeigt das Prinzip schon. Die Datei im Projekt nennt für jede Variable den Ort, an dem der Wert liegt, aber nicht den Wert. Ein Programm löst beim Start alle Verweise auf und gibt die echten Werte genau dem Prozess mit, den du startest.
Das hat zwei Folgen. Die Datei darf ins Git, denn wer sie liest, erfährt nur, dass das Projekt einen Zahlungs-Key unter einem bestimmten Namen erwartet. Und die Datei beschreibt die echte Konfiguration. Eine .env.example, die irgendwann jemand nachzuziehen vergisst, brauchst du nicht mehr. Wer neu ins Projekt kommt, sieht, welche Einträge er im eigenen Tresor anlegen muss.
Wo Hidewell passt
Hidewell ist ein Tresor für Passwörter und API-Keys am Mac, gebaut für genau dieses Muster. Die CLI trägt noch den alten Namen 2pw, die Befehle unten heißen deshalb so. Jedes Projekt committet eine .env.2pw mit Zeilen wie dieser:
PAYMENTS_API_KEY=2pw://Dev/Payments/credential
Gestartet wird das Projekt über die CLI:
2pw run -- npm run dev
2pw run löst alle Verweise auf und gibt die Werte nur diesem einen Prozess als Umgebungsvariablen mit. Dein Code liest weiter process.env.PAYMENTS_API_KEY und merkt keinen Unterschied. Für die vorhandene .env gibt es 2pw import-env. Der Befehl liest die Klartext-Datei, verschiebt Keys, Tokens und Passwörter in den Tresor und schreibt die Verweisdatei gleich mit. Für Node.js und Python gibt es SDKs mit read() und loadEnv(), andere Sprachen rufen 2pw read auf.
Der Tresor ist eine einzige Datei auf deinem Mac, verschlüsselt mit AES-256-GCM. Das Master-Passwort wird mit Argon2id und 128 MiB gestreckt. Ein Konto brauchst du nicht. Nebenbei ist Hidewell ein gewöhnlicher Passwort-Manager mit TOTP-Codes, Generator und einer Prüfung auf schwache und mehrfach genutzte Passwörter, die offline läuft.
Im Vergleich zu den Optionen oben liegt Hidewell zwischen Schlüsselbund und 1Password CLI. Wie beim Schlüsselbund bleibt alles lokal. Wie bei op run beschreibt eine Datei im Repo, welche Werte das Projekt braucht.
Was Hidewell nicht leistet
Solange der Tresor offen ist, kann jedes Programm unter deinem Benutzer den Hidewell-Agenten nach einem Wert fragen. Eine Klartext-.env könnte es genauso lesen. An diesem Punkt ist Hidewell nicht sicherer als die Datei, und ich würde keinem lokalen Werkzeug glauben, das etwas anderes behauptet. Was du gewinnst, ist ein Protokoll: 2pw log zeigt, welcher Wert aus welchem Verzeichnis gelesen wurde.
Es gibt keine Wiederherstellung. Wer das Master-Passwort vergisst, bekommt den Tresor nicht mehr auf.
Und Hidewell räumt keine Git-History auf. Ein Key, der schon einmal committet wurde, muss beim Anbieter ersetzt werden, egal womit du ihn danach speicherst. Warum diese Grenzen offen auf der Produktseite stehen, beschreibt der Artikel darüber, wie unsere Apps entstehen.
Womit du anfängst
Nimm das Projekt, dessen .env du am wenigsten in einem öffentlichen Repo sehen willst. Prüfe mit git log --all -- .env, ob die Datei je committet wurde, und erneuere in dem Fall die Keys. Dann such dir einen der Wege oben aus, ob Schlüsselbund, op run oder Verweise. Wie Hidewell das macht und was die App voraussetzt, steht auf der Hidewell-Seite.