Skip to content

Commit 4777b10

Browse files
committed
Add support for secrets stored in authinfo and netrc files
I would like to mamange my ECA config and commands with Guix and don't want any secrets in those files. I could use keyEnv for this, but all my other secrets are already in `~/.authinfo.gpg` which I would like to use. This PR adds support for reading secrets from the `~/.authinfo` and `~/.netrc` files. It supports GPG encrypted and Windows variants.
1 parent 8a49abc commit 4777b10

File tree

15 files changed

+3975
-12
lines changed

15 files changed

+3975
-12
lines changed

AGENTS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,18 @@ Code Style
2424
- Unit tests should have a single `deftest` for function to be tested with multiple `testing`s for each tested case.
2525
- Unit tests that use file paths and uris should rely on `h/file-path` and `h/file-uri` to avoid windows issues with slashes.
2626

27+
Secrets Management
28+
- `src/eca/secrets/netrc.clj` - Netrc format parser (multi-line format: machine/login/password/port keywords)
29+
- `src/eca/secrets/authinfo.clj` - Authinfo format parser (single-line format: space-separated key-value pairs)
30+
- `src/eca/secrets.clj` - Main secrets manager for credential file operations:
31+
- File discovery and priority order (.authinfo.gpg → .netrc.gpg → .authinfo → _authinfo → .netrc → _netrc)
32+
- Cross-platform path construction using io/file (handles / vs \ separators automatically)
33+
- GPG decryption with caching (5-second TTL) and timeout (30s, configurable via GPG_TIMEOUT env var)
34+
- keyRc format parsing: [login@]machine[:port] (named after Unix "rc" config file tradition)
35+
- Credential matching logic (exact login match when specified, first match otherwise)
36+
- Permission validation (Unix: warns if not 0600; Windows: skipped)
37+
- Authentication flow: config `key` → credential files `keyRc` → env var `keyEnv` → OAuth
38+
- Security: passwords never logged; GPG decryption via clojure.java.process; cache with short TTL; subprocess timeout protection
39+
2740
Notes
2841
- CI runs: bb test and bb integration-test. Ensure these pass locally before PRs.

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Add support for secrets stored in authinfo and netrc files
6+
57
## 0.64.1
68

79
- Fix duplicated arguments on `toolCallPrepare` for openai-chat API models. https://github.com/editor-code-assistant/eca-emacs/issues/56

docs/configuration.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,33 @@ There are multiples ways to configure ECA:
5252

5353
For providers and models configuration check the [dedicated models section](./models.md#custom-providers).
5454

55+
### Credential Files (netrc/authinfo)
56+
57+
Providers can use `keyRc` to read credentials from `.netrc` or `.authinfo` files (named after the Unix "rc" configuration file tradition):
58+
59+
```javascript
60+
{
61+
"providers": {
62+
"openai": {
63+
"keyRc": "api.openai.com"
64+
},
65+
"anthropic": {
66+
"keyRc": "[email protected]" // with login for multi-account
67+
}
68+
}
69+
}
70+
```
71+
72+
The value of `keyRc` is the machine name to look up in credential files. Supports format: `[login@]machine[:port]`
73+
74+
**Benefits:**
75+
- Secure credential storage (especially with `.authinfo.gpg` encryption)
76+
- Standardized format used by many Unix tools (curl, git, etc.)
77+
- Multi-account support via login prefixes
78+
- Single location for managing credentials across multiple tools
79+
80+
See [Credential File Authentication](./models.md#credential-file-authentication) for file formats, GPG encryption setup, and detailed configuration examples.
81+
5582
## Tools
5683

5784
### MCP
@@ -379,6 +406,7 @@ To configure, add your OTLP collector config via `:otlp` map following [otlp aut
379406
urlEnv?: string;
380407
key?: string; // when provider supports api key.
381408
keyEnv?: string;
409+
keyRc?: string; // credential file lookup in format [login@]machine[:port]
382410
completionUrlRelativePath?: string;
383411
models: {[key: string]: {
384412
extraPayload?: {[key: string]: any}

docs/models.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,171 @@ Example:
5050
- `OPENAI_API_KEY` for OpenAI
5151
- `ANTHROPIC_API_KEY` for Anthropic
5252

53+
### Credential File Authentication
54+
55+
ECA supports reading API credentials from `.authinfo` and `.netrc` files via the `keyRc` configuration, providing a secure and standardized way to manage authentication credentials without storing them in configuration files or environment variables.
56+
57+
> **Why "keyRc"?** The name follows the `key*` naming convention (like `key` and `keyEnv`) and references the Unix "rc" (run commands) file tradition. Files like `.bashrc`, `.vimrc`, and `.netrc` use the "rc" suffix to indicate configuration files that are read at startup or runtime.
58+
59+
**Configuration:**
60+
61+
Add `keyRc` to your provider configuration:
62+
63+
```javascript
64+
{
65+
"providers": {
66+
"openai": {
67+
"url": "https://api.openai.com",
68+
"keyRc": "api.openai.com" // simple machine name
69+
},
70+
"anthropic": {
71+
"url": "https://api.anthropic.com",
72+
"keyRc": "[email protected]" // with login for multi-account
73+
}
74+
}
75+
}
76+
```
77+
78+
**keyRc Format:**
79+
80+
The `keyRc` value supports multiple formats for flexible credential matching:
81+
82+
- `"machine"` - Simple machine name (e.g., `"api.openai.com"`)
83+
- `"login@machine"` - Specific login for multi-account scenarios (e.g., `"[email protected]"`)
84+
- `"machine:port"` - Machine with specific port (e.g., `"api.custom.com:8443"`)
85+
- `"login@machine:port"` - Full format with login and port (e.g., `"[email protected]:443"`)
86+
87+
**Supported Files (checked in order):**
88+
89+
ECA checks credential files in the following priority order:
90+
91+
1. `~/.authinfo.gpg` - GPG-encrypted authinfo (most secure)
92+
2. `~/.netrc.gpg` - GPG-encrypted netrc
93+
3. `~/.authinfo` - Plaintext authinfo format (Unix/macOS)
94+
4. `~/_authinfo` - Plaintext authinfo format (Windows)
95+
5. `~/.netrc` - Plaintext netrc format (Unix/macOS)
96+
6. `~/_netrc` - Plaintext netrc format (Windows)
97+
98+
The first matching credential found is used.
99+
100+
**File Formats:**
101+
102+
*Netrc format* (multi-line):
103+
```
104+
machine api.openai.com
105+
login apikey
106+
password sk-proj-...
107+
108+
machine api.anthropic.com
109+
login work
110+
password sk-ant-...
111+
```
112+
113+
*Authinfo format* (single-line):
114+
```
115+
machine api.openai.com login apikey password sk-proj-... port 443
116+
machine api.anthropic.com login work password sk-ant-... port 443
117+
```
118+
119+
**Requirements:**
120+
121+
- The `login` field is **required** for all credential entries
122+
- Entries without a `login` field will be ignored
123+
- The `port` field is optional
124+
125+
**GPG Encryption:**
126+
127+
To use encrypted credentials, you can encrypt either authinfo or netrc format files:
128+
129+
*Encrypting authinfo format:*
130+
```bash
131+
# Create/edit authinfo file
132+
echo "machine api.openai.com login apikey password sk-..." > ~/.authinfo
133+
134+
# Encrypt it with GPG
135+
gpg --output ~/.authinfo.gpg --symmetric ~/.authinfo
136+
137+
# Remove plaintext (optional but recommended)
138+
rm ~/.authinfo
139+
```
140+
141+
*Encrypting netrc format:*
142+
```bash
143+
# Create/edit netrc file
144+
cat > ~/.netrc << 'EOF'
145+
machine api.openai.com
146+
login apikey
147+
password sk-...
148+
EOF
149+
150+
# Encrypt it with GPG
151+
gpg --output ~/.netrc.gpg --symmetric ~/.netrc
152+
153+
# Remove plaintext (optional but recommended)
154+
rm ~/.netrc
155+
```
156+
157+
ECA will automatically decrypt `.authinfo.gpg` or `.netrc.gpg` files using the `gpg` command. Make sure `gpg` is installed and `gpg-agent` is configured for passphrase caching.
158+
159+
**GPG Timeout:**
160+
161+
GPG decryption has a 30-second timeout by default to prevent hanging on slow or unresponsive GPG processes. You can customize this via the `GPG_TIMEOUT` environment variable (in seconds):
162+
163+
```bash
164+
# Set custom timeout (e.g., 60 seconds)
165+
export GPG_TIMEOUT=60
166+
```
167+
168+
**Authentication Priority Order:**
169+
170+
When resolving credentials, ECA checks sources in this order:
171+
172+
1. **Config file** - explicit `key` in provider config (highest priority)
173+
2. **Credential files** - `keyRc` setting pointing to machine name
174+
3. **Environment variable** - value from `keyEnv` setting
175+
4. **OAuth flow** - for providers that support it (e.g., GitHub Copilot)
176+
177+
This ensures explicit configuration takes precedence while providing credential files as a convenient option.
178+
179+
**Security:**
180+
181+
- Plaintext files (`.authinfo`, `.netrc`) should have restricted permissions (0600 on Unix)
182+
- ECA will warn if permissions are too open
183+
- `.authinfo.gpg` provides encryption at rest for maximum security
184+
- Keep credential files out of version control
185+
- Passwords are never logged or leaked in error messages
186+
- GPG subprocess has timeout protection (30s default) to prevent hanging
187+
188+
**Multi-Account Support:**
189+
190+
You can store multiple credentials for the same provider using different login values:
191+
192+
```
193+
machine api.anthropic.com login work password sk-ant-work-...
194+
machine api.anthropic.com login personal password sk-ant-personal-...
195+
```
196+
197+
Then specify which account to use in your config:
198+
199+
```javascript
200+
{
201+
"providers": {
202+
"anthropic-work": {
203+
"url": "https://api.anthropic.com",
204+
"keyRc": "[email protected]"
205+
},
206+
"anthropic-personal": {
207+
"url": "https://api.anthropic.com",
208+
"keyRc": "[email protected]"
209+
}
210+
}
211+
}
212+
```
213+
214+
**Supported Providers:**
215+
216+
All providers with API key authentication can use credential files, including OpenAI, Anthropic, Google, custom providers, and more.
217+
53218
## Custom providers
54219

55220
ECA allows you to configure custom LLM providers that follow API schemas similar to OpenAI or Anthropic. This is useful when you want to use:

0 commit comments

Comments
 (0)