Python’s netrc module parses the standard Unix ~/.netrc file, which stores per-host login credentials for FTP, HTTP basic auth, and other protocols. import netrc. Parse file: n = netrc.netrc() — reads ~/.netrc; n = netrc.netrc(path) — reads a custom path. Lookup: login, account, password = n.authenticators(hostname) — returns (login, account, password) or None if not found; falls through to the default entry if no machine-specific entry matches. Attributes: n.hosts → dict of {host: (login, account, password)}; n.macros → dict of {macro_name: [lines]}; n.files — file path. Exceptions: netrc.NetrcParseError — raised for syntax errors (bad permissions, unknown tokens). File format: machine host login user password secret — must have 0600 permissions (enforced on Unix). default entry: default login anonymous password [email protected] — catches all unmatched hosts. Security: the file must be readable only by the owner (-rw-------); ~/.netrc is read automatically by ftp, curl, wget, and other tools. Claude Code generates authenticated FTP clients, credential loaders, CI secret readers, and multi-host authentication managers.
CLAUDE.md for netrc
## netrc Stack
- Stdlib: import netrc
- Parse: n = netrc.netrc() # ~/.netrc
- n = netrc.netrc("/path/to/.netrc")
- Lookup: auth = n.authenticators("ftp.example.com")
- if auth: login, account, password = auth
- All: n.hosts # {host: (login, account, password)}
- n.macros # {name: [lines]}
- Note: File must be chmod 0600; raises NetrcParseError on syntax errors
netrc Credential Manager Pipeline
# app/netrcutil.py — parse, lookup, write, validate, FTP/HTTP auth bridge
from __future__ import annotations
import os
import stat
import tempfile
import textwrap
from dataclasses import dataclass, field
from pathlib import Path
import netrc as _netrc
# ─────────────────────────────────────────────────────────────────────────────
# 1. Core lookup helpers
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class Credential:
host: str
login: str
password: str
account: str | None = None
def __str__(self) -> str:
acct = f" account={self.account!r}" if self.account else ""
return f"Credential(host={self.host!r}, login={self.login!r}{acct})"
def mask_password(self) -> str:
if not self.password:
return "(empty)"
return self.password[:2] + "***" + self.password[-1:]
def load_netrc(path: str | Path | None = None) -> _netrc.netrc | None:
"""
Load a netrc file. Returns a netrc object or None if not found / unreadable.
Example:
n = load_netrc()
if n: print(n.hosts)
"""
try:
if path is None:
return _netrc.netrc()
return _netrc.netrc(str(path))
except (FileNotFoundError, PermissionError):
return None
except _netrc.NetrcParseError:
raise
def lookup(
hostname: str,
path: str | Path | None = None,
) -> Credential | None:
"""
Look up credentials for hostname from ~/.netrc (or custom path).
Returns None if no match found.
Example:
cred = lookup("ftp.example.com")
if cred:
print(cred.login, cred.mask_password())
"""
n = load_netrc(path)
if n is None:
return None
auth = n.authenticators(hostname)
if auth is None:
return None
login, account, password = auth
return Credential(
host=hostname,
login=login or "",
password=password or "",
account=account,
)
def list_credentials(path: str | Path | None = None) -> list[Credential]:
"""
Return all credentials stored in the netrc file.
Example:
for cred in list_credentials():
print(cred.host, cred.login)
"""
n = load_netrc(path)
if n is None:
return []
results = []
for host, (login, account, password) in n.hosts.items():
results.append(Credential(
host=host,
login=login or "",
password=password or "",
account=account,
))
return results
# ─────────────────────────────────────────────────────────────────────────────
# 2. Netrc file writer
# ─────────────────────────────────────────────────────────────────────────────
def write_netrc(
credentials: list[Credential],
path: str | Path | None = None,
default: Credential | None = None,
) -> Path:
"""
Write credentials to a netrc file with correct permissions (0600).
If path is None, writes to ~/.netrc.
Returns the path written.
Example:
write_netrc([
Credential("ftp.example.com", "alice", "s3cr3t"),
Credential("api.example.com", "token", "abc123"),
])
"""
dest = Path(path) if path else Path.home() / ".netrc"
lines = []
for cred in credentials:
lines.append(f"machine {cred.host}")
lines.append(f" login {cred.login}")
lines.append(f" password {cred.password}")
if cred.account:
lines.append(f" account {cred.account}")
lines.append("")
if default:
lines.append("default")
lines.append(f" login {default.login}")
lines.append(f" password {default.password}")
if default.account:
lines.append(f" account {default.account}")
lines.append("")
dest.write_text("\n".join(lines) + "\n", encoding="utf-8")
# Set restrictive permissions (owner read/write only)
dest.chmod(0o600)
return dest
def add_credential(
host: str,
login: str,
password: str,
account: str | None = None,
path: str | Path | None = None,
) -> None:
"""
Add or update a credential in the netrc file.
Creates the file if it does not exist.
Example:
add_credential("ftp.example.com", "alice", "s3cr3t")
"""
existing = list_credentials(path)
# Remove any existing entry for this host
updated = [c for c in existing if c.host != host]
updated.append(Credential(host=host, login=login,
password=password, account=account))
write_netrc(updated, path=path)
def remove_credential(host: str, path: str | Path | None = None) -> bool:
"""
Remove the credential for a host from the netrc file.
Returns True if a credential was removed, False if not found.
Example:
removed = remove_credential("ftp.example.com")
"""
existing = list_credentials(path)
updated = [c for c in existing if c.host != host]
if len(updated) == len(existing):
return False
write_netrc(updated, path=path)
return True
# ─────────────────────────────────────────────────────────────────────────────
# 3. Validation
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class NetrcValidation:
path: str
ok: bool
errors: list[str] = field(default_factory=list)
warnings: list[str] = field(default_factory=list)
host_count: int = 0
def __str__(self) -> str:
status = "OK" if self.ok else "FAIL"
return (f"[{status}] {self.path} "
f"({self.host_count} hosts, "
f"{len(self.errors)} errors, "
f"{len(self.warnings)} warnings)")
def validate_netrc(path: str | Path | None = None) -> NetrcValidation:
"""
Validate a netrc file: parse errors, permission check, empty passwords.
Example:
result = validate_netrc()
print(result)
for err in result.errors:
print(f" ERROR: {err}")
"""
dest = Path(path) if path else Path.home() / ".netrc"
v = NetrcValidation(path=str(dest), ok=True)
if not dest.exists():
v.errors.append(f"File not found: {dest}")
v.ok = False
return v
# Permission check (Unix only)
try:
mode = dest.stat().st_mode
if mode & stat.S_IRWXG or mode & stat.S_IRWXO:
v.warnings.append(
f"Permissions too open: {oct(mode & 0o777)}; recommend 0600"
)
if os.name == "posix":
v.errors.append("File is world/group readable — security risk")
v.ok = False
except OSError:
pass
# Parse
try:
n = _netrc.netrc(str(dest))
v.host_count = len(n.hosts)
for host, (login, account, password) in n.hosts.items():
if not login:
v.warnings.append(f"Host {host!r} has no login")
if not password:
v.warnings.append(f"Host {host!r} has no password")
except _netrc.NetrcParseError as e:
v.errors.append(f"Parse error: {e}")
v.ok = False
return v
# ─────────────────────────────────────────────────────────────────────────────
# 4. Integration bridges
# ─────────────────────────────────────────────────────────────────────────────
def ftp_login_from_netrc(
ftp,
host: str,
path: str | Path | None = None,
) -> bool:
"""
Log in to an existing ftplib.FTP connection using netrc credentials.
Returns True on success, False if no credentials found.
Example:
import ftplib
ftp = ftplib.FTP(host)
if not ftp_login_from_netrc(ftp, host):
ftp.login() # anonymous fallback
"""
cred = lookup(host, path=path)
if cred is None:
return False
ftp.login(user=cred.login, passwd=cred.password,
acct=cred.account or "")
return True
def get_basic_auth_header(
host: str,
path: str | Path | None = None,
) -> dict[str, str] | None:
"""
Return an HTTP Authorization header dict for a host from netrc.
Returns None if no credentials found.
Example:
headers = get_basic_auth_header("api.example.com") or {}
response = urllib.request.urlopen(req)
"""
import base64
cred = lookup(host, path=path)
if cred is None:
return None
token = base64.b64encode(f"{cred.login}:{cred.password}".encode()).decode()
return {"Authorization": f"Basic {token}"}
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import io
print("=== netrc demo ===")
with tempfile.TemporaryDirectory() as tmp:
netrc_path = Path(tmp) / ".netrc"
# ── write_netrc ────────────────────────────────────────────────────────
print("\n--- write_netrc ---")
creds = [
Credential("ftp.example.com", "alice", "wonderland"),
Credential("api.example.com", "mytoken", "abc123xyz"),
Credential("files.corp.com", "alice", "hunter2",
account="uploads"),
]
p = write_netrc(creds, path=netrc_path)
print(f" wrote to {p}")
print(f" permissions: {oct(p.stat().st_mode & 0o777)}")
print(f" content:\n{p.read_text()}")
# ── lookup ─────────────────────────────────────────────────────────────
print("--- lookup ---")
for host in ["ftp.example.com", "api.example.com", "unknown.com"]:
c = lookup(host, path=netrc_path)
print(f" {host}: {c}")
# ── list_credentials ───────────────────────────────────────────────────
print("\n--- list_credentials ---")
for c in list_credentials(path=netrc_path):
print(f" {c.host:<24s} {c.login:<12s} {c.mask_password()}")
# ── add / remove ───────────────────────────────────────────────────────
print("\n--- add_credential + remove_credential ---")
add_credential("new.example.com", "bob", "pass99", path=netrc_path)
print(f" after add: {[c.host for c in list_credentials(path=netrc_path)]}")
removed = remove_credential("ftp.example.com", path=netrc_path)
print(f" removed ftp.example.com: {removed}")
print(f" after remove: {[c.host for c in list_credentials(path=netrc_path)]}")
# ── validate_netrc ─────────────────────────────────────────────────────
print("\n--- validate_netrc ---")
result = validate_netrc(path=netrc_path)
print(f" {result}")
for w in result.warnings:
print(f" WARN: {w}")
# ── get_basic_auth_header ──────────────────────────────────────────────
print("\n--- get_basic_auth_header ---")
import base64
header = get_basic_auth_header("api.example.com", path=netrc_path)
if header:
token = header["Authorization"].split(" ", 1)[1]
decoded = base64.b64decode(token).decode()
print(f" Authorization: Basic {token[:12]}...")
print(f" decoded: {decoded}")
# ── load ~/.netrc if present ───────────────────────────────────────────────
print("\n--- load ~/.netrc (if present) ---")
try:
home_creds = list_credentials()
if home_creds:
print(f" {len(home_creds)} credentials found")
for c in home_creds:
print(f" {c.host}: {c.login}")
else:
print(" ~/.netrc not found or empty")
except _netrc.NetrcParseError as e:
print(f" parse error: {e}")
print("\n=== done ===")
For the keyring (PyPI) alternative — keyring.get_password(service, username) and keyring.set_password(service, username, password) store credentials in the OS native secret store (macOS Keychain, Windows Credential Locker, secret-service on Linux) — use keyring for desktop applications or interactive tools where the user has an OS session and a secret store; use netrc for server automation scripts, CI systems, and command-line tools that follow Unix conventions and need a simple file-based credential store compatible with ftp, curl, wget, and git credential. For the dotenv / environment variable alternative — os.environ["API_KEY"] and python-dotenv (PyPI) load secrets from .env files — use environment variables for container-based deployments and 12-factor app configuration where secrets are injected at runtime; use netrc when you need per-host credential routing (authenticators(hostname) automatically selects the right entry) and compatibility with Unix tooling that reads ~/.netrc natively. The Claude Skills 360 bundle includes netrc skill sets covering Credential with lookup()/list_credentials()/load_netrc(), write_netrc()/add_credential()/remove_credential() file management, NetrcValidation with validate_netrc() security checker, and ftp_login_from_netrc()/get_basic_auth_header() protocol bridges. Start with the free tier to try credential management patterns and netrc pipeline code generation.