How Credentials Are Stored in Agent Reach: Security Mechanisms Explained
Agent Reach stores credentials in ~/.agent-reach/config.yaml with 0o600 file permissions, uses symlink rejection to prevent path hijacking, performs atomic writes to avoid corruption, and scrubs all secrets from logs before display.
Agent Reach is an open-source tool that manages API keys, OAuth tokens, and browser cookies for automated platform interactions. Understanding how it handles credential storage and security is essential for users who need to protect sensitive authentication data. This article examines the specific mechanisms implemented in the Panniantong/Agent-Reach repository to keep secrets safe.
Where Credentials Are Stored
Central Configuration File
The primary storage location for user-provided secrets is ~/.agent-reach/config.yaml, managed by agent_reach/config.py. This file holds:
- API keys (
exa_api_key,openai_api_key,github_token) - Platform-specific cookies (
bilibili_sessdata,xueqiu_cookie) - Authentication tokens and session identifiers
Legacy Integration Files
For compatibility with external tools, Agent Reach also writes to:
| File Path | Purpose | Stored Values |
|---|---|---|
~/.config/xfetch/session.json |
Legacy Xfetch Twitter client | auth_token, ct0 |
~/.config/bird/credentials.env |
Bird CLI environment | Shell-quoted credentials |
All paths are resolved within the user's home directory and protected with identical security primitives.
File System Security Measures
Owner-Only Directory Creation
The make_private_dir function in agent_reach/utils/paths.py (lines 33-38) creates credential directories with mode 0o700:
from pathlib import Path
import os
def make_private_dir(path: Path) -> None:
"""Create a directory readable and writable only by the owner."""
path.mkdir(parents=True, exist_ok=True)
os.chmod(path, 0o700)
This prevents other system users from listing or accessing credential storage locations.
Atomic File Writes with Permission Enforcement
Writes to credential files use atomic_write_private_text in agent_reach/utils/paths.py (lines 54-94). This function:
- Creates a temporary file with mode
0o600(owner read/write only) - Validates the final path is not a symlink via
_reject_symlink - Uses
os.replace()for atomic commit, which never follows symlinks
# From agent_reach/utils/paths.py - simplified demonstration
import tempfile
import os
from pathlib import Path
def atomic_write_private_text(target: Path, content: str) -> None:
# Create temp file in same directory for atomic rename
fd, temp_path = tempfile.mkstemp(dir=target.parent, suffix='.tmp')
try:
os.fchmod(fd, 0o600) # Owner-only before any data hits disk
os.write(fd, content.encode('utf-8'))
os.close(fd)
# Verify target isn't a symlink before replacing
_reject_symlink(target)
os.replace(temp_path, target)
except Exception:
os.unlink(temp_path)
raise
YAML Configuration Atomic Writes
The Config._atomic_write_yaml method in agent_reach/config.py (lines 45-78) applies the same atomic pattern specifically for the YAML config file, ensuring partial writes never leave the configuration in a corrupted state.
Symlink Attack Prevention
The _reject_symlink and ensure_no_symlink_path functions in agent_reach/utils/paths.py (lines 17-30) provide defense against path hijacking. Each component of a target path is checked with os.lstat():
# Conceptual usage from the codebase
from agent_reach.utils.paths import ensure_no_symlink_path
from pathlib import Path
config_path = Path.home() / ".agent-reach" / "config.yaml"
ensure_no_symlink_path(config_path) # Raises PrivatePathError if any component is a symlink
If any path component resolves to a symbolic link, PrivatePathError is raised immediately. This prevents attackers from redirecting credential writes to attacker-controlled locations.
Masking and Log Protection
Configuration Display Masking
The Config.to_dict method in agent_reach/config.py (lines 111-132) automatically masks sensitive values when configurations are printed or logged:
from agent_reach.config import Config
cfg = Config()
cfg.set("openai_api_key", "sk-abc123xyz789")
print(cfg.to_dict())
# Output: {'openai_api_key': 'sk-abc******'} # Truncated placeholder
Keys containing key, token, secret, cookie, password, or api_key are detected and their values replaced with truncated placeholders.
Runtime URL Scrubbing
All user-visible output passes through scrub_url_credentials in agent_reach/utils/text.py (lines 24-28). This function removes:
- URL credentials (
user:password@host→host) - Query parameter secrets (
token=abc123→token=<redacted>) - Common API key patterns
from agent_reach.utils.text import scrub_url_credentials
dirty_url = "https://api.example.com/data?api_key=secret123&user=admin"
clean = scrub_url_credentials(dirty_url)
print(clean)
# Output: "https://api.example.com/data?api_key=<redacted>&user=admin"
Cookie Extraction Security
The agent_reach/cookie_extract.py module handles browser cookie extraction without intermediate file exposure:
- Cookies are extracted directly from browser profiles in memory
- Sanitized values are written only to the protected config file
- Legacy sync functions (
_sync_xfetch_session, lines 52-74;_sync_bird_env, lines 79-105) operate as isolated, silent operations that returnFalseon failure without aborting the primary workflow
This ensures credential extraction failures cannot leak data or disrupt operations.
# Example: Browser cookie extraction with secure sync
from agent_reach.cookie_extract import configure_from_browser
from agent_reach.config import Config
cfg = Config()
result = configure_from_browser(
browser="chrome",
config=cfg,
platform="twitter_xreach",
)
# Credentials atomically written; no raw cookie files left on disk
Summary
Agent Reach implements defense-in-depth for credential security:
- Storage: All secrets in
~/.agent-reach/config.yamlor legacy paths with0o600/0o700permissions - Atomicity: Temporary file creation with
os.replace()prevents partial writes - Symlink resistance: Path component validation blocks redirection attacks
- Display safety: Automatic masking of sensitive keys in
to_dict() - Log protection: URL and parameter scrubbing via
scrub_url_credentials()
Frequently Asked Questions
What file permissions does Agent Reach use for credential files?
Agent Reach uses mode 0o600 (owner read/write only) for all credential files and 0o700 for credential directories. These permissions are enforced atomically—set on temporary files before any data is written, then committed via os.replace().
How does Agent Reach prevent symlink attacks on credential files?
The ensure_no_symlink_path function in agent_reach/utils/paths.py scans each path component with os.lstat() and raises PrivatePathError if any symlink is detected. This check runs before file creation and before atomic replacement, ensuring credentials are never written to attacker-controlled locations.
Can API keys appear in Agent Reach logs or error messages?
No. The scrub_url_credentials function in agent_reach/utils/text.py processes all strings before display. It removes URL credentials (user:pass@), redacts query parameters like token= and api_key=, and masks sensitive config values via the to_dict() method.
Where is the main credential configuration file located?
The primary file is ~/.agent-reach/config.yaml, managed by agent_reach/config.py. This location is created with 0o700 permissions on first use, and all writes are atomic with 0o600 file permissions enforced.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →