Aislamiento Multicuenta de GitHub en macOS y Linux
Updated: 20 de julio de 2026
Actualización 2026-07-20: guía reescrita para soportar N cuentas (no solo 2), firma de commits con SSH y GPG,
allowed_signerspara verificación, cuenta principal como default seguro, soporte cross-platform (macOS y Linux), y corrección de la sección de credenciales (el token vive en Keychain/secret-service, no en disco).
Cuando usás la misma máquina para varios mundos —personal, trabajo, side-projects— es cuestión de tiempo terminar mezclando identidades: un commit firmado con el correo equivocado, un push a la organización corporativa desde tu cuenta personal o, en el peor de los casos, una credencial expuesta donde no debía estar.
Decidí reconstruir mi configuración desde cero. Esta es la guía paso a paso para lograr un aislamiento total y automatizado de Git y GitHub CLI (gh) apoyándome en una premisa simple: separar físicamente los entornos por carpetas y dejar que las herramientas resuelvan la identidad correcta de forma invisible.
“La comodidad de una configuración global es exactamente la misma puerta que usa un atacante para moverse lateralmente entre tus cuentas.”
El objetivo no es solo evitar errores tontos, sino reducir la superficie de ataque: que un proceso malicioso ejecutado en tu carpeta personal no tenga forma de tocar tus repositorios de trabajo, y viceversa.
Prerrequisitos
Esta guía funciona en macOS y Linux (Debian/Ubuntu). Necesitás:
| Herramienta | macOS | Linux (Debian/Ubuntu) |
|---|---|---|
| Git | brew install git |
sudo apt-get install -y git |
GitHub CLI (gh) |
brew install gh |
Instalación oficial (repo apt) |
| OpenSSH | incluido en macOS | sudo apt-get install -y openssh-client |
| GnuPG | brew install gnupg |
sudo apt-get install -y gnupg |
direnv |
brew install direnv |
sudo apt-get install -y direnv |
| Clipboard | pbcopy (incluido) |
sudo apt-get install -y xclip |
En Linux headless (SSH sin GUI), la generación de claves GPG requiere
--batch --passphrase ""para evitarpinentry-curses. El script companion lo hace automáticamente.
Manual o Automatizado: Elegí tu Camino
Esta guía te lleva paso a paso por cada archivo, comando y decisión, para que entendás exactamente qué está pasando en tu máquina. Si preferís ver la mecánica antes de confiarle nada a un script, seguí leyendo.
Si ya sabés cómo funciona la cadena o simplemente querés un setup rápido, hay una alternativa: un script interactivo que hace exactamente lo mismo que describo a continuación. Te pregunta cuántas cuentas querés configurar, emails, carpetas y claves de firma, detecta configuraciones previas, ofrece resetearlas con backup automático, y ejecuta cada paso sin que vos tengas que copiar y pegar comandos.
curl -sSL https://raw.githubusercontent.com/galiprandi/me/main/scripts/setup-github-isolation.sh | bash
⚠️ Por seguridad, revisá el contenido antes de ejecutarlo:
curl -sSL ... | cat.
La guía a mano sigue siendo la mejor forma de interiorizar el mecanismo; el script es la mejor forma de no olvidar ningún paso cuando cambiás de máquina. Elegí la que prefieras.
Convención de ejemplo: 3 cuentas
Para que la guía sea concreta sin atarse a un caso particular, vamos a usar 3 cuentas como ejemplo a lo largo de todo el recorrido:
| Cuenta | Carpeta | Host SSH | GH_CONFIG_DIR | |
|---|---|---|---|---|
| Work (principal) | work@mail.com |
~/Github/Work |
github.com |
~/.config/gh-work |
| Personal | personal@mail.com |
~/Github/Personal |
github.com-personal |
~/.config/gh-personal |
| Side-project | side@mail.com |
~/Github/Side |
github.com-side |
~/.config/gh-side |
La cuenta principal (Work) es la que vive como Host github.com real y como default global de git y gh. Las demás son overrides. Si tu principal es la personal, invertí los roles. El mecanismo es el mismo para 2, 3 o N cuentas: repetí el bloque por cada cuenta adicional.
1. Estructura de Carpetas
El pilar de esta configuración es separar físicamente tus repositorios. Toda la automatización posterior (Git, SSH, gh) se apoya en dónde está parado el repositorio. Creá un directorio limpio por cuenta en tu Home:
mkdir -p ~/Github/Work
mkdir -p ~/Github/Personal
mkdir -p ~/Github/Side
A partir de ahora la regla es inquebrantable: cada cuenta vive bajo su carpeta. La frontera entre los mundos es esa ruta.
2. Generación de Llaves SSH Seguras
No dependas de tokens HTTPS globales expuestos en variables de entorno. Vamos a usar llaves SSH ED25519 protegidas por passphrase, una por identidad. Generá una por cuenta asignando una passphrase fuerte cuando te la pida:
# Work (principal)
ssh-keygen -t ed25519 -C "work@mail.com" -f ~/.ssh/id_ed25519_work
# Personal
ssh-keygen -t ed25519 -C "personal@mail.com" -f ~/.ssh/id_ed25519_personal
# Side-project
ssh-keygen -t ed25519 -C "side@mail.com" -f ~/.ssh/id_ed25519_side
Agregá las llaves al agente SSH (con integración al Keychain en macOS para no reescribir la passphrase en cada sesión):
# macOS
ssh-add --apple-use-keychain ~/.ssh/id_ed25519_work
ssh-add --apple-use-keychain ~/.ssh/id_ed25519_personal
ssh-add --apple-use-keychain ~/.ssh/id_ed25519_side
# Linux
ssh-add ~/.ssh/id_ed25519_work
ssh-add ~/.ssh/id_ed25519_personal
ssh-add ~/.ssh/id_ed25519_side
Finalmente, copiá el contenido de cada archivo .pub y cargalo en la cuenta de GitHub correspondiente (Settings → SSH and GPG keys):
# macOS
pbcopy < ~/.ssh/id_ed25519_work.pub # pegá en tu cuenta de trabajo
pbcopy < ~/.ssh/id_ed25519_personal.pub # pegá en tu cuenta personal
pbcopy < ~/.ssh/id_ed25519_side.pub # pegá en tu cuenta side-project
# Linux (xclip)
xclip -selection clipboard < ~/.ssh/id_ed25519_work.pub
# o: cat ~/.ssh/id_ed25519_work.pub y copiar manualmente
3. Enrutamiento automático en SSH (~/.ssh/config)
Todas las cuentas viven en el mismo host (github.com), así que necesitamos un alias que le diga a SSH qué llave usar para cada identidad. La cuenta principal se queda con el host real; las demás reciben aliases. Editá o creá ~/.ssh/config:
# Cuenta principal (Work)
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
AddKeysToAgent yes
# Cuenta Personal
Host github.com-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
AddKeysToAgent yes
# Cuenta Side-project
Host github.com-side
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_side
IdentitiesOnly yes
AddKeysToAgent yes
La directiva IdentitiesOnly yes es clave para la seguridad: obliga a SSH a usar exclusivamente la llave declarada y le prohíbe ofrecer todas las identidades del agente al servidor. Sin ella, SSH podría autenticarte con la llave equivocada y romper el aislamiento.
4. Configuración Inteligente de Git (~/.gitconfig)
A diferencia de guías anteriores que recomendaban un global sin email, acá vamos a definir la cuenta principal como default seguro: el global tiene email, firma habilitada y allowed_signers configurado. Así, cualquier repo clonado fuera de las carpetas cubiertas commitea con tu identidad principal, firmado, en lugar de quedar sin identidad. Las demás cuentas son overrides vía includeIf.
Configuración Global (~/.gitconfig):
[user]
name = Tu Nombre
email = work@mail.com
signingkey = 2970AAB9B51D85F3 # tu GPG key ID (ver sección 5)
[commit]
gpgsign = true
[gpg]
format = openpgp
program = gpg
[gpg "ssh"]
allowedSignersFile = ~/.config/git/allowed_signers
# Overrides por carpeta
[includeIf "gitdir:~/Github/Personal/"]
path = ~/.gitconfig-personal
[includeIf "gitdir:~/Github/Side/"]
path = ~/.gitconfig-side
La cuenta principal (Work) no necesita
includeIf: ya es el default. Solo las cuentas secundarias se declaran como overrides.
Configuración Personal (~/.gitconfig-personal):
[user]
name = Tu Nombre
email = personal@mail.com
signingkey = ~/.ssh/id_ed25519_personal.pub
[commit]
gpgsign = true
[gpg]
format = ssh
# Intercepta clones estándar y usa el alias SSH correcto
[url "git@github.com-personal:"]
insteadOf = "git@github.com:"
Configuración Side-project (~/.gitconfig-side):
[user]
name = Tu Nombre
email = side@mail.com
signingkey = ~/.ssh/id_ed25519_side.pub
[commit]
gpgsign = true
[gpg]
format = ssh
[url "git@github.com-side:"]
insteadOf = "git@github.com:"
Buenas prácticas: la regla insteadOf automatiza todo lo demás. Cuando estés dentro de ~/Github/Personal/, podés correr git clone git@github.com:usuario/repo.git y Git reescribirá la URL para usar el alias github.com-personal, eligiendo la llave personal de forma invisible. Nunca más vas a tener que recordar qué host usar.
Para verificar que la identidad se resuelve bien, parate en cada carpeta y preguntale a Git:
cd ~/Github/Work/algun-repo && git config user.email # work@mail.com
cd ~/Github/Personal/otro-repo && git config user.email # personal@mail.com
cd ~/Github/Side/otro-repo && git config user.email # side@mail.com
cd /tmp && git config user.email # work@mail.com (default)
5. Firma de Commits (SSH + GPG)
Esta es la sección que diferencia a esta guía de cualquier otra “multi-account GitHub” que vas a encontrar. Firmar commits no es decorativo: es el mecanismo que prueba que un commit vino de quien dice venir. Sin firma, un atacante con acceso al repo puede inyectar commits y no hay forma de distinguirlos de los tuyos. Con firma, git log --show-signature te lo dice en segundos.
Git soporta dos formatos de firma: SSH (más simple, reusa tus llaves ed25519) y GPG/OpenPGP (más tradicional, requiere una clave GPG separada). Podés mezclar: una cuenta con GPG y otras con SSH. En el ejemplo, Work usa GPG y Personal/Side usan SSH.
5.1. Firma SSH (recomendado para empezar)
Si ya generaste tus llaves ed25519 en el paso 2, ya tenés todo. Solo necesitás:
-
Subir la llave pública a GitHub como signing key (no solo como SSH key): Settings → SSH and GPG keys → New SSH key → Key type: Signing Key. Subí el mismo
.pubque cargaste antes, pero marcado como signing. -
Declarar la llave en tu perfil git (ya lo hicimos en la sección 4):
[user] signingkey = ~/.ssh/id_ed25519_personal.pub [gpg] format = ssh -
Configurar
allowed_signerspara que git pueda verificar firmas SSH (no solo crearlas). Creá~/.config/git/allowed_signerscon una línea por email:work@mail.com namespaces="git" ssh-ed25519 AAAAC3... work@mail.com personal@mail.com namespaces="git" ssh-ed25519 AAAAC3... personal@mail.com side@mail.com namespaces="git" ssh-ed25519 AAAAC3... side@mail.comEl formato es:
email namespaces="git" <tipo> <clave> <comentario>. La clave es el contenido del.pubcorrespondiente. Y declaralo en el global:[gpg "ssh"] allowedSignersFile = ~/.config/git/allowed_signers
5.2. Firma GPG/OpenPGP (para la cuenta principal)
Si querés firma GPG para tu cuenta principal (más tradicional, soportado por más herramientas):
-
Generar una clave GPG (si no tenés una):
# macOS (usa pinentry-mac si está instalado, o prompt nativo) gpg --quick-generate-key "work@mail.com" ed25519 sign 0 # Linux headless (SSH sin GUI): usar --batch para evitar pinentry-curses gpg --batch --passphrase "" --quick-generate-key "work@mail.com" ed25519 sign 0Listá tus claves y copiá el ID:
gpg --list-secret-keys --keyid-format=long # sec ed25519/2970AAB9B51D85F3 ...El ID es la parte después de
/(ej:2970AAB9B51D85F3). -
Subir la clave pública a GitHub: Settings → SSH and GPG keys → New GPG key.
# macOS gpg --armor --export 2970AAB9B51D85F3 | pbcopy # Linux gpg --armor --export 2970AAB9B51D85F3 | xclip -selection clipboard -
Declarar en tu global (ya lo hicimos en la sección 4):
[user] signingkey = 2970AAB9B51D85F3 [gpg] format = openpgp program = gpg
5.3. Verificación
Confirmá que las firmas se crean y se validan correctamente:
# En un repo personal, hacé un commit y verificá la firma
cd ~/Github/Personal/algun-repo
git commit --allow-empty -m "test firma SSH"
git log --show-signature -1
# Debe decir: Good "git" signature for personal@mail.com
# El status %G? debe ser "G" (good)
# En un repo de trabajo
cd ~/Github/Work/algun-repo
git commit --allow-empty -m "test firma GPG"
git log --show-signature -1
# Debe decir: Good signature from work@mail.com
Si el status es U (unknown) o X (expired), revisá que la clave esté subida a GitHub como signing key y que allowed_signers tenga la entrada correcta.
6. Separación Completa de GitHub CLI (gh)
El CLI de GitHub guarda las credenciales de forma global en ~/.config/gh, lo que rompe el aislamiento apenas lo usás en varios mundos. La solución es darle a gh un directorio de configuración distinto por entorno mediante la variable GH_CONFIG_DIR, y automatizar el cambio con direnv.
1. Crear carpetas de configuración separadas:
mkdir -p ~/.config/gh-work
mkdir -p ~/.config/gh-personal
mkdir -p ~/.config/gh-side
2. Autenticar cada entorno de forma aislada (elegí SSH como protocolo en todos los casos):
# Autenticar Work (principal)
export GH_CONFIG_DIR=~/.config/gh-work
gh auth login
# Autenticar Personal
export GH_CONFIG_DIR=~/.config/gh-personal
gh auth login
# Autenticar Side-project
export GH_CONFIG_DIR=~/.config/gh-side
gh auth login
3. Symlink del default a la cuenta principal:
Para que gh fuera de las carpetas cubiertas use la cuenta principal (consistente con el default de git), creá un symlink:
ln -sfn gh-work ~/.config/gh
Ahora ~/.config/gh apunta a gh-work. Cualquier gh ejecutado sin direnv usa la cuenta principal. Single source of truth: el token vive una sola vez en el Keychain (macOS) o en el almacén del sistema (Linux via secret-service/pass).
4. Instalar y activar direnv:
# macOS
brew install direnv
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
# Linux (Debian/Ubuntu)
sudo apt-get install -y direnv
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc # o zsh si usás zsh
5. Automatizar el cambio de entorno con un archivo .envrc por carpeta:
# Work (principal) — no necesita .envrc porque es el default vía symlink
# Personal
echo 'export GH_CONFIG_DIR=~/.config/gh-personal' > ~/Github/Personal/.envrc
direnv allow ~/Github/Personal/.envrc
# Side-project
echo 'export GH_CONFIG_DIR=~/.config/gh-side' > ~/Github/Side/.envrc
direnv allow ~/Github/Side/.envrc
A partir de acá, cada vez que tu terminal entre a una de esas carpetas, direnv exportará automáticamente el GH_CONFIG_DIR correcto y gh operará con la cuenta indicada. Comprobalo con gh auth status parado en cada carpeta.
cd ~/Github/Personal
↓
direnv lee .envrc
↓
export GH_CONFIG_DIR=~/.config/gh-personal
↓
git usa ~/.gitconfig-personal (includeIf)
gh usa ~/.config/gh-personal
ssh usa github.com-personal (IdentityFile)
7. Manejo Seguro de Credenciales (Anti-Supply Chain)
Esta es la sección que de verdad importa después de un incidente. Los ataques modernos de supply chain no buscan tu contraseña: buscan exfiltrar variables de entorno y tokens en texto plano que cualquier dependencia maliciosa pueda leer durante un npm install, un postinstall o un script de build. La regla de oro es simple: ninguna credencial debe vivir nunca en texto plano dentro de una variable de entorno o de un archivo legible.
7.1. Nunca uses un GITHUB_TOKEN global
El antipatrón más peligroso es definir un helper en tu .gitconfig que lea un token desde una variable global, por ejemplo:
# ❌ NO HAGAS ESTO
[credential]
helper = "!f() { echo \"password=${GITHUB_TOKEN}\"; }; f"
Si tenés esto, cualquier proceso que corra en tu sesión (incluida una dependencia comprometida) puede leer $GITHUB_TOKEN con un simple echo $GITHUB_TOKEN y robarse tu acceso a todos tus repositorios. Eliminá cualquier export GITHUB_TOKEN=... de tu .zshrc, .bashrc o .env y revocá los Personal Access Tokens que ya no necesites desde Settings → Developer settings → Tokens.
7.2. Delegá la autenticación HTTPS al gh CLI
Para los casos en que necesités HTTPS (algunos pipelines o repos que no soportan SSH), no inventes tu propio helper: delegá en el credential helper nativo de gh. Este lee el token desde el almacenamiento seguro de cada GH_CONFIG_DIR y nunca lo expone como variable de entorno. Agregá este bloque a tus configs de cuentas secundarias.
En ~/.gitconfig-personal y ~/.gitconfig-side añadí:
[credential "https://github.com"]
helper =
helper = !gh auth git-credential
La primera línea
helper =(vacía) es intencional: resetea cualquier helper heredado de la configuración global antes de declarar el degh, evitando que un helper inseguro definido más arriba siga activo.
La magia del aislamiento se mantiene: como direnv ya cambió el GH_CONFIG_DIR según la carpeta, gh auth git-credential entregará el token de la cuenta correcta sin que el secreto pase jamás por una variable de entorno.
⚠️ Limitación importante: este helper solo funciona si
GH_CONFIG_DIRestá cargado, lo cual depende dedirenv. En contextos no-interactivos —IDEs (VS Code, IntelliJ), clientes git GUI (Tower, Fork), cron, hooks de CI local—direnvno se carga ygh auth git-credentialusa el default (~/.config/gh→ cuenta principal), entregando el token equivocado. Por eso la guía recomienda SSH exclusivamente para interacción manual: SSH no depende dedirenvporque el routing vive en~/.ssh/configy~/.gitconfig, que sí se leen en todos los contextos. Para HTTPS en pipelines, seteáGH_CONFIG_DIRexplícitamente en el entorno del pipeline.
7.3. Confirmá dónde viven realmente tus tokens
En macOS moderno, gh no guarda el token en hosts.yml del GH_CONFIG_DIR: lo guarda en el Keychain del sistema. En Linux, gh puede usar secret-service (D-Bus), pass, o guardar el token en texto plano en hosts.yml si no hay un gestor de secretos disponible. El archivo hosts.yml siempre contiene metadatos (usuario, protocolo). Verificá:
GH_CONFIG_DIR=~/.config/gh-personal gh auth status
# macOS: ✓ Logged in to github.com account X (keyring)
# Linux: ✓ Logged in to github.com account X (keyring) ← si hay secret-service/pass
# ✓ Logged in to github.com account X ← si no hay gestor, token en hosts.yml
El hardening de tokens depende del OS:
macOS (Keychain):
- No compartas el Keychain entre cuentas:
ghguarda cada token bajo un nombre distinto porGH_CONFIG_DIR, así que están naturalmente separados. - Auditá qué tokens hay:
security find-generic-password -s "gh:github.com" -g 2>&1 | head(pedirá permiso de Keychain). - Revocá tokens huérfanos: si dejaste de usar una cuenta,
gh auth logoutdentro de suGH_CONFIG_DIRlimpia el Keychain.
Linux (secret-service / pass / texto plano):
-
Instalá un gestor de secretos:
sudo apt-get install -y gnome-keyring passpara queghusesecret-serviceen lugar de texto plano. -
Si no hay gestor: el token vive en
hosts.yml. Asegurate dechmod 600 ~/.config/gh-*/hosts.yml. -
Auditá:
cat ~/.config/gh-*/hosts.ymlpara ver qué cuentas tienen tokens. -
Revocá tokens huérfanos:
gh auth logoutdentro de cadaGH_CONFIG_DIR. -
chmod 600sigue siendo útil parahosts.ymlyconfig.yml(metadatos), pero no protege el token en sí si no hay gestor de secretos.
Si querés un blindaje extra, podés usar gh auth login con --with-token leyendo desde un gestor de secretos (como 1Password CLI) en lugar de dejar el token en el Keychain/almacén, de modo que ni siquiera el sistema lo guarde de forma persistente.
7.4. Protegé tus llaves SSH con passphrase
Las llaves SSH del paso 2 son tu última línea de defensa. Una llave privada sin passphrase es un archivo que cualquier malware puede copiar y reutilizar en otra máquina. Con una passphrase fuerte, aunque te roben el archivo id_ed25519_personal, no podrán usarlo sin descifrarlo.
El agente SSH con integración al Keychain (macOS, paso 2) o al agente nativo (Linux) te da lo mejor de ambos mundos: trabajás cómodo sin reescribir la frase, pero la llave nunca queda utilizable en frío sobre el disco. Para confirmar que el agente solo tiene cargadas las llaves esperadas:
ssh-add -l # debería listar únicamente tus llaves ed25519
7.5. Resumen de las reglas estrictas
- No uses tokens globales: evitá
GITHUB_TOKENexportado y helpers que lean variables en texto plano. - Delegá en herramientas nativas:
gh auth git-credentialresuelve HTTPS de forma segura y cambia de contexto según la carpeta gracias adirenv(con la limitación de contextos no-interactivos). - Preferí SSH sobre HTTPS: SSH no depende de
direnvy funciona en IDEs, GUIs y cron sin configuración adicional. - Firmá todos los commits: SSH para cuentas secundarias, GPG para la principal. Configurá
allowed_signerspara verificación. - Usá passphrases: ninguna llave SSH debe quedar utilizable sin descifrar sobre el disco.
- Auditá el Keychain/almacén, no solo los archivos: los tokens viven ahí, no en
hosts.yml.
Cierre: el aislamiento como hábito, no como evento
Reconstruir esta configuración después de un incidente me dejó una certeza: la seguridad no es un setup que hacés una vez, sino una frontera que las herramientas defienden por vos. Separás por carpetas, dejás que includeIf, insteadOf y direnv resuelvan la identidad, firmás cada commit, y eliminás cada lugar donde un secreto pudiera vivir en texto plano.
El resultado es un entorno donde es prácticamente imposible cometer el error de mezclar identidades, donde cada commit lleva una firma verificable, y donde una dependencia comprometida en tu mundo personal no tiene ningún camino hacia tus repositorios de trabajo. Esa es la verdadera comodidad: no la de un token global a mano, sino la de saber que el aislamiento trabaja solo, en silencio, cada vez que cambiás de carpeta.
