# How Credentials Are Stored in Agent Reach: Security Mechanisms Explained

> Discover how Agent Reach securely stores credentials in config.yaml using file permissions, symlink rejection, atomic writes, and secret scrubbing. Learn about its robust security mechanisms.

- Repository: [Pnant/Agent-Reach](https://github.com/Panniantong/Agent-Reach)
- Tags: security-mechanisms
- Published: 2026-08-04

---

**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`](https://github.com/Panniantong/Agent-Reach/blob/main/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`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/utils/paths.py)** (lines 33-38) creates credential directories with **mode `0o700`**:

```python
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`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/utils/paths.py) (lines 54-94). This function:

1. Creates a temporary file with **mode `0o600`** (owner read/write only)
2. Validates the final path is not a symlink via `_reject_symlink`
3. Uses `os.replace()` for atomic commit, which never follows symlinks

```python

# 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`](https://github.com/Panniantong/Agent-Reach/blob/main/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`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/utils/paths.py) (lines 17-30) provide defense against path hijacking. Each component of a target path is checked with `os.lstat()`:

```python

# 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`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/config.py) (lines 111-132) automatically masks sensitive values when configurations are printed or logged:

```python
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`](https://github.com/Panniantong/Agent-Reach/blob/main/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

```python
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`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/cookie_extract.py)** module handles browser cookie extraction without intermediate file exposure:

1. Cookies are extracted directly from browser profiles in memory
2. Sanitized values are written only to the protected config file
3. Legacy sync functions (`_sync_xfetch_session`, lines 52-74; `_sync_bird_env`, lines 79-105) operate as **isolated, silent operations** that return `False` on failure without aborting the primary workflow

This ensures credential extraction failures cannot leak data or disrupt operations.

```python

# 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.yaml` or legacy paths with `0o600`/`0o700` permissions
- **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`](https://github.com/Panniantong/Agent-Reach/blob/main/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`](https://github.com/Panniantong/Agent-Reach/blob/main/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`](https://github.com/Panniantong/Agent-Reach/blob/main/agent_reach/config.py). This location is created with `0o700` permissions on first use, and all writes are atomic with `0o600` file permissions enforced.