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:

  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

# 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.

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@hosthost)
  • Query parameter secrets (token=abc123token=<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"

The 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.


# 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().

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →