Skip to content

Audit report #378

Description

@Swader

Agent Reach is not a normal web server, so the main attack surface is not SQL injection or request floods against a public endpoint. The real surface is more dangerous for this product category: local host mutation, supply-chain installation, credential/cookie extraction, subprocess execution, browser profile access, agent-visible docs, and unbounded URL/media processing.

Production readiness: not recommended for broad agent/operator use until P0/P1 issues below are fixed.
Security posture: high-risk defaults, mostly from installer behavior and credential handling.
DX posture: docs/API mismatch is severe enough that LLMs and humans will call commands that do not exist.
UX posture: users are likely to be surprised by filesystem mutations, auto-installs, silent fallbacks, and stale command docs.
Performance posture: acceptable for ad hoc CLI usage, but slow/hang-prone for CI/MCP/bot loops.

Dynamic checks performed:

  • Imported channel handlers and tested malicious host routing cases.
  • Exercised Config.to_dict() redaction behavior with cookie/session fields.
  • Injected a fake rookiepy module to validate the Xueqiu cookie loading path.
  • Ran the test suite with time limits. The suite collected 162 tests and reached roughly 63% progress before timing out in the current environment, around tests/test_core.py::TestAgentReach::test_doctor_report. This suggests the suite mixes unit tests with real doctor/probe behavior and lacks strict global test budgets/mocks.

Tooling limitations:

  • Local ruff, bandit, pip-audit, semgrep, and mypy were not installed in the container, so this report is primarily manual plus targeted dynamic repros.
  • I did a dependency freshness/vulnerability spot check separately; see the dependency section.

Severity model

  • P0 / Critical: likely host compromise, credential compromise, large trust violation, or default behavior that can break user machines.
  • P1 / High: exploitable security gap, costly privacy/cost/resource surprise, major agent/product failure, or common path denial-of-service.
  • P2 / Medium: important correctness, reliability, hardening, or maintainability issue.
  • P3 / Low: cleanup, polish, weak signals, or future-proofing.

Findings

P0-01 — Installer performs high-risk host mutations and unpinned third-party installs by default

Owners: Security, DX, UX, Team Lead
Files: agent_reach/cli.py
Evidence:

  • install --safe exists, but safe mode is opt-in (cli.py:71-74).
  • _install_system_deps() modifies system package sources and installs packages (cli.py:510-635).
  • It downloads GitHub CLI key material via curl and writes to /usr/share/keyrings and /etc/apt/sources.list.d (cli.py:527-545).
  • It downloads the NodeSource setup script and executes it with bash (cli.py:573-583).
  • It globally installs Node packages such as undici (cli.py:607-609) and later optional channel tools via pipx, uv, or npm -g (cli.py:680-935).
  • Several subprocess return codes are ignored, so partial failure can be reported as success or continue into later steps.

Impact:

A tool whose purpose is to empower agents is automatically modifying the host and adding package sources. That is exactly the path attackers want: compromise a package source, npm package, curl target, DNS, MITM on a misconfigured system, or a local shell environment, and the installer becomes a high-leverage execution chain. Even without compromise, it can break machines by changing Node/npm/apt state.

Tie-breaker verdict: this is the highest-risk issue in the repo. The project’s convenience layer is acting like a bootstrapper with root-level blast radius.

Recommended fix:

  • Make safe mode the default. Rename current behavior to something explicit like --unsafe-system-install or --yes-modify-system.
  • Never execute remote setup scripts automatically. Print commands, or use signed repositories with documented verification.
  • Pin external tools and package versions. Prefer hashes/lockfiles where possible.
  • Check every subprocess return code with check=True or explicit handling.
  • Split installation into layers:
    • agent-reach install --python-only
    • agent-reach install --agents-skill
    • agent-reach install --external-tools --yes
    • agent-reach install --system --yes-i-understand
  • Emit a machine-readable install plan before changes:
{
  "will_modify_system": true,
  "writes": ["/usr/share/keyrings/...", "/etc/apt/sources.list.d/..."],
  "commands": ["apt-get update", "npm install -g ..."],
  "requires_confirmation": true
}

P0-02 — Credential and cookie handling leaks secrets through CLI args, logs, docs, and incomplete masking

Owners: Security, UX, Edge Case Master
Files: agent_reach/config.py, agent_reach/cookie_extract.py, agent_reach/cli.py, docs
Evidence:

  • configure accepts secrets as positional CLI args (cli.py:80-90), which are commonly exposed in shell history, process listings, terminal recordings, LLM tool traces, and CI logs.
  • Config.to_dict() only masks keys containing key, token, password, or proxy (config.py:102-110). It does not mask xhs_cookie, twitter_ct0, bilibili_sessdata, or xueqiu_cookie.
  • Browser extraction stores full cookies for XHS/Xueqiu/Bilibili/Twitter in config (cookie_extract.py:243-289).
  • Cookie extraction serializes whole cookie sets for some platforms, not just minimal needed fields (cookie_extract.py:23-40, cookie_extract.py:137-143).

Dynamic repro:

Config.to_dict() masks twitter_auth_token, but leaks fields like:

{
  "xhs_cookie": "a=secret; web_session=leak",
  "twitter_ct0": "csrf-secret",
  "bilibili_sessdata": "sess-secret",
  "xueqiu_cookie": "xq_a_token=secret"
}

Impact:

These values are account credentials in practice. A leaked cookie or CSRF/session token can let another process or person act as the user, scrape authenticated content, or burn paid API quota. Because this is agent-facing software, “printed config” and “copied command” are not minor risks; they are normal operating paths.

Recommended fix:

  • Add secret-safe configuration methods:
agent-reach configure groq-key --stdin
agent-reach configure twitter-cookies --from-browser chrome
agent-reach configure xhs-cookies --file ./cookies.json
  • Use getpass for interactive secrets.
  • Deprecate raw positional secret values. Keep backward compatibility with a warning for one release.
  • Redact keys containing any of:
SECRET_KEYWORDS = (
    "key", "token", "password", "proxy", "cookie", "session", "sess",
    "csrf", "ct0", "auth", "secret", "credential", "bearer"
)
  • Never show full config by default. Provide --show-secrets only with an explicit danger prompt.
  • Store only minimum cookie fields required per backend, not whole cookies.

P1-03 — Domain routing accepts malicious lookalike hosts

Owners: Security, Edge Case Master, UX
Files: agent_reach/channels/*.py
Evidence:

Multiple channel handlers use substring checks against urlparse(url).netloc.lower().

Examples:

  • github.py:17
  • twitter.py:17
  • youtube.py:33
  • reddit.py:45
  • bilibili.py:44
  • linkedin.py:20
  • v2ex.py:33
  • xueqiu.py:158
  • xiaohongshu.py:158

Dynamic repro:

The following malicious or unrelated domains are currently accepted as trusted platform URLs:

https://github.com.evil.test/owner/repo      -> GitHubChannel.can_handle(...) == True
https://notx.com/path                        -> TwitterChannel.can_handle(...) == True
https://youtube.com.evil.test/watch?v=x      -> YouTubeChannel.can_handle(...) == True
https://reddit.com.evil.test/r/test          -> RedditChannel.can_handle(...) == True
https://bilibili.com.evil.test/video         -> BilibiliChannel.can_handle(...) == True
https://xiaohongshu.com.evil.test/explore    -> XiaoHongShuChannel.can_handle(...) == True
https://linkedin.com.evil.test/in/me         -> LinkedInChannel.can_handle(...) == True
https://v2ex.com.evil.test/t/1               -> V2EXChannel.can_handle(...) == True
https://xueqiu.com.evil.test/S/SH000001      -> XueqiuChannel.can_handle(...) == True

Impact:

A malicious URL can be routed into a platform-specific handler. Depending on the handler, this can cause wrong backend selection, credential/cookie use against attacker-controlled hosts, privacy leaks, or user/agent confusion. It is especially dangerous because the product is designed for agents that may classify and fetch URLs automatically.

Recommended fix:

Centralize URL host validation:

from urllib.parse import urlparse

ALLOWED_SCHEMES = {"http", "https"}

def host_matches(url: str, *domains: str) -> bool:
    parsed = urlparse(url)
    if parsed.scheme.lower() not in ALLOWED_SCHEMES:
        return False
    host = (parsed.hostname or "").rstrip(".").lower()
    domains = tuple(d.lower().rstrip(".") for d in domains)
    return any(host == d or host.endswith("." + d) for d in domains)

Then update handlers:

return host_matches(url, "github.com")
return host_matches(url, "x.com", "twitter.com")
return host_matches(url, "youtube.com", "youtu.be")

Add explicit tests for negative lookalike hosts.


P1-04 — Transcription path allows resource exhaustion, privacy/cost surprises, and leftover temporary data

Owners: Security, Performance, UX
Files: agent_reach/transcribe.py
Evidence:

  • yt-dlp is called on arbitrary URL input with no --no-playlist, no max file size, no max duration, and a 30-minute timeout (transcribe.py:77-98).
  • Local files are accepted directly (transcribe.py:230-234) with no size or duration limit.
  • Temp directories are created with tempfile.mkdtemp(prefix="transcribe-") and intentionally left behind (transcribe.py:214-228).
  • Auto provider mode falls back from Groq to OpenAI per chunk on any TranscribeError (transcribe.py:249-261).

Impact:

A user or agent can accidentally or maliciously transcribe an enormous playlist, a very long video, a local sensitive file, or many chunks. This can burn API quota, fill disk, consume CPU via ffmpeg, and leak media to a paid third-party provider the user did not expect. The temp-dir behavior leaves source/compressed/chunked audio on disk.

Recommended fix:

  • Add default limits:
--max-download-mb 250
--max-duration-min 180
--no-playlist
--keep-temp
--allow-paid-fallback
  • Use TemporaryDirectory() by default and only preserve intermediates under --keep-temp.
  • Add yt-dlp flags:
--no-playlist
--playlist-items 1
--max-filesize <limit>
--socket-timeout 20
--retries 3
  • Probe duration with ffprobe before processing.
  • Make provider fallback explicit. Do not silently send chunks to OpenAI after Groq fails unless the user opted into paid/provider fallback.
  • Categorize errors:
    • 401/403: disable provider for entire run.
    • 429: bounded retry/backoff.
    • 5xx/network: retry, then fallback only if explicitly allowed.

P1-05 — doctor has side effects and can become slow/hang-prone in tests, CI, and MCP contexts

Owners: Performance, UX, DX
Files: agent_reach/cli.py, agent_reach/doctor.py, agent_reach/backends/opencli.py, agent_reach/integrations/mcp_server.py
Evidence:

  • _cmd_doctor() calls _install_skill() after generating the doctor report (cli.py:1447-1464). A diagnostic command mutates the filesystem.
  • opencli_status() shells out to opencli --version and opencli daemon status and scans browser extension dirs (backends/opencli.py:63-136). Multiple channels can call this repeatedly.
  • The MCP server’s async get_status handler calls synchronous doctor_report() directly (integrations/mcp_server.py:44-53).
  • Test suite execution timed out in this environment while running doctor-related tests.

Impact:

Diagnostics should be safe, fast, and side-effect-free. Here they install skills, touch multiple directories, shell out, and perform network/backend checks. In an agent loop or MCP server, this can turn a status call into a latency spike or filesystem mutation. In CI, it makes otherwise simple tests brittle and slow.

Recommended fix:

  • Remove auto skill installation from doctor.
  • Add modes:
agent-reach doctor --fast       # local config + binaries only
agent-reach doctor --network    # external platform probes
agent-reach doctor --json
  • Cache expensive checks per process/run:
@functools.lru_cache(maxsize=1)
def cached_opencli_status() -> OpenCLIStatus:
    return opencli_status()
  • In MCP, run doctor in a worker thread or provide a lightweight cached snapshot.
  • Mark integration tests separately and mock all external probes by default.

P1-06 — scripts/transcribe_xiaoyuzhou.sh uses predictable tmp paths and unbounded network/media operations

Owners: Security, Performance, Edge Case Master
Files: scripts/transcribe_xiaoyuzhou.sh
Evidence:

  • Predictable temp directory: TMPDIR="/tmp/xiaoyuzhou_$$" (scripts/transcribe_xiaoyuzhou.sh:28).
  • Page fetch uses curl -s "$URL" with no timeout, no max size, and no --fail (scripts/transcribe_xiaoyuzhou.sh:55).
  • Audio fetch uses curl -sL -o with no timeout or size bound (scripts/transcribe_xiaoyuzhou.sh:70).
  • Config path is interpolated into an embedded Python one-liner (scripts/transcribe_xiaoyuzhou.sh:34).
  • Uses ffprobe, ffmpeg, and bc, but does not check all dependencies up front.
  • Writes final output to a user-provided path/default under /tmp (scripts/transcribe_xiaoyuzhou.sh:27, :262).

Impact:

A predictable temp path creates collision/symlink/race risk on multi-user machines. Unbounded network/media processing can hang, fill disk, or burn CPU. The script is also more fragile than the Python transcription module and should not be the hardened path.

Recommended fix:

TMPDIR="$(mktemp -d -t xiaoyuzhou.XXXXXX)"
trap 'rm -rf "$TMPDIR"' EXIT INT TERM

Use bounded curl:

curl --fail --show-error --location \
  --connect-timeout 10 --max-time 60 \
  --output "$file" "$URL"

Pass config path through environment:

CONFIG_FILE="$CONFIG_FILE" python3 - <<'PY'
import os, yaml
path = os.environ["CONFIG_FILE"]
...
PY

Prefer porting this script into the Python CLI and deprecating the shell script.


P1-07 — Docs and agent-facing API describe commands that do not exist

Owners: DX, UX, Team Lead
Files: llms.txt, test.sh, CLAUDE.md, README.md, docs/*, agent_reach/cli.py
Evidence:

  • The CLI defines only: setup, install, configure, doctor, uninstall, skill, format, transcribe, check-update, watch, version (cli.py:50-133).
  • llms.txt tells agents to use commands like agent-reach read <url> and agent-reach search-twitter "query" (llms.txt:13).
  • test.sh invokes nonexistent commands: agent-reach read, search, search-github, search-twitter, search-reddit, search-youtube, search-bilibili, search-xhs (test.sh:58-74).
  • CLAUDE.md says every channel implements read(url), search(query), and check(), but the base channel contract does not enforce this, and the actual CLI does not expose those commands.

Impact:

This is not cosmetic. This product is explicitly agent-facing. Stale llms.txt and test docs are an API bug because agents will read them and call invalid commands. Users will also conclude the install is broken when the actual code is merely narrower than the docs claim.

Recommended fix:

Choose one product boundary:

  1. Installer/capability-manager product: remove all read/search claims from docs and tests. Publish exact backend command templates instead.
  2. Unified wrapper product: implement agent-reach read, agent-reach search, and per-platform search commands with a stable channel interface.

Do not leave it halfway. Halfway is the worst option: it maximizes support burden and minimizes trust.

Recommended agent-facing endpoint:

agent-reach capabilities --json

Example output:

{
  "version": "0.x.y",
  "commands": ["doctor", "configure", "transcribe"],
  "channels": {
    "github": {"status": "available", "backend": "gh", "read_template": "gh api ..."},
    "twitter": {"status": "missing", "install_hint": "agent-reach install --channels twitter"}
  }
}

P2-08 — Xueqiu browser-cookie loading path is silently broken

Owners: Edge Case Master, Security, UX
Files: agent_reach/channels/xueqiu.py
Evidence:

_load_cookies_from_browser() builds a http.cookiejar.CookieJar, then calls self.session.cookies.set(...) (xueqiu.py:81-89). CookieJar does not expose the requests.cookies.RequestsCookieJar.set method.

Dynamic repro:

Injecting a fake rookiepy that returns an xq_a_token cookie causes _load_cookies_from_browser() to catch an exception and return False. The cookie jar remains empty. Because the function catches broad exceptions, users receive no actionable diagnosis.

Impact:

A documented authentication path fails silently. Users fall back to unauthenticated behavior, confusing rate limits, or manual cookie config. Security-wise, broad silent failure makes it harder to distinguish “no cookies found” from “cookie code is broken.”

Recommended fix:

Use requests.Session().cookies as a RequestsCookieJar, or construct proper Cookie objects for CookieJar. Prefer the simpler route:

self.session.cookies.set(
    c["name"],
    c["value"],
    domain=c.get("domain") or ".xueqiu.com",
    path=c.get("path") or "/",
)

Add a regression test with fake rookiepy.


P2-09 — Config writes are permission-conscious but not symlink-safe or atomic

Owners: Security, DX
Files: agent_reach/config.py, agent_reach/cookie_extract.py
Evidence:

  • Config dir creation uses mkdir(parents=True, exist_ok=True) without setting 0700 (config.py:37-39).
  • Config file writes use os.open(..., O_WRONLY | O_CREAT | O_TRUNC, 0o600) (config.py:54-62), but do not use O_NOFOLLOW where available and are not atomic.
  • The fallback uses plain open() (config.py:63-67).
  • Cookie extractor uses a similar owner-only helper with a plain open() fallback (cookie_extract.py:151-170).

Impact:

The file mode is good for the common case. But a local attacker or broken environment can exploit symlinks or partial writes. Atomicity matters because these files contain credentials and agents may read them while they are being written.

Recommended fix:

  • Set config directory to 0700 on create and warn if broader.
  • Write to a temp file in the same directory, fsync, chmod 0600, then os.replace().
  • Use O_NOFOLLOW where available.
  • On Unix, lstat before write and reject symlinks.

P2-10 — Docker cookie update path has temp-file and container-selection edge cases

Owners: Security, Edge Case Master, UX
Files: agent_reach/cli.py
Evidence:

  • _configure_xhs_cookies() detects a Docker container by docker ps --filter name=xiaohongshu-mcp --format {{.Names}} and strips stdout (cli.py:1263-1270). If multiple containers match, the name can contain newlines.
  • The temp cookie file is created with NamedTemporaryFile(delete=False) and unlinked only after docker cp succeeds (cli.py:1291-1303). Exceptions before unlink leave cookie material in /tmp.
  • The code restarts the selected container (cli.py:1310-1320) without an exact-match prompt.

Impact:

This is mostly a local footgun, but it deals with live account cookies. Multi-container environments, failed docker cp, or weird container names can leave secrets on disk or restart the wrong service.

Recommended fix:

  • Use try/finally around temp unlink.
  • Require exact container ID selection if more than one match exists.
  • Prefer docker cp - or bind-mounted config paths to avoid temp cookie files.
  • Show a clear preflight summary before restarting containers.

P2-11 — Some HTTP reads and query parameters are unbounded or not encoded

Owners: Security, Performance, Edge Case Master
Files: agent_reach/channels/v2ex.py, agent_reach/channels/xueqiu.py, other channel modules
Evidence:

  • V2EX URL construction inserts node_name and username into paths without URL encoding (v2ex.py:89-93, v2ex.py:175-177).
  • Xueqiu stock symbols and list limits are inserted without strong normalization/bounds (xueqiu.py:196, xueqiu.py:218-230, xueqiu.py:290-303).
  • Several channel methods read response bodies directly without explicit max response size.

Impact:

This is not catastrophic in a CLI, but it is agent-facing code. Agents will pass malformed, adversarial, or copy-pasted values. Unbounded response and parameter handling creates weird failures, slow runs, excessive output, and possible backend abuse.

Recommended fix:

  • Encode all path components with urllib.parse.quote.
  • Clamp all limit arguments with per-method caps.
  • Enforce response byte limits and content-type sanity checks.
  • Use a shared HTTP client wrapper with timeout, retry policy, max bytes, and user-agent.

P2-12 — Optional dependency constraints are not enforced by package installation

Owners: Security, DX
Files: pyproject.toml, constraints.txt
Evidence:

  • pyproject.toml uses broad dependency lower bounds such as yt-dlp>=2024.0 and requests>=2.28.
  • constraints.txt pins concrete versions, but normal package installation will not use it unless the user explicitly installs with -c constraints.txt.
  • The constraints file pins yt-dlp==2025.5.22, while the Python package allows much older or newer versions.

Impact:

The project has two dependency stories: broad runtime ranges and a constraint file most users will never apply. That makes local behavior hard to reproduce and weakens supply-chain control.

Recommended fix:

  • Decide whether this is a library or an application.
    • For a library: keep broad compatible ranges, but test a matrix and document minimums.
    • For a CLI application: use a lockfile or pinned application bundle.
  • Add CI dependency vulnerability scanning.
  • Document installation commands that actually use constraints when intended:
pip install -c constraints.txt agent-reach

P3-13 — SECURITY.md excludes dependency vulnerabilities, but dependencies are the product’s highest-risk layer

Owners: Security, DX
Files: SECURITY.md
Evidence:

SECURITY.md says dependency vulnerabilities are out of scope unless they directly impact project code.

Impact:

For this repository, external tools and packages are not incidental. The product installs and orchestrates them. Dependency and supply-chain vulnerabilities should be first-class security reports.

Recommended fix:

Accept reports for dependencies when Agent Reach pins, recommends, installs, shells out to, or passes credentials through them.


P3-14 — Config.get() prioritizes config file over environment variables

Owners: UX, DX
Files: agent_reach/config.py
Evidence:

Config.get() checks self.data first, then environment variables (config.py:69-78).

Impact:

Many CLI users expect env vars to override config files for one-off runs and CI. Current behavior can surprise users who believe GROQ_API_KEY=... agent-reach transcribe ... overrides a stale file value.

Recommended fix:

Either reverse precedence or document it clearly and add AGENT_REACH_CONFIG_PRECEDENCE=file|env.


P3-15 — Version comparison is simplistic

Owners: DX
Files: agent_reach/cli.py
Evidence:

_is_newer_version() parses dotted digits only (cli.py:1658-1674).

Impact:

Pre-releases, post-releases, build metadata, and non-standard tags may compare incorrectly.

Recommended fix:

Use packaging.version.Version.


Expert pass summaries

Security expert

Primary concern: the project handles credentials and tells users/agents to install external tooling, but the trust boundaries are not explicit. The installer is too powerful by default, and the credential redaction model is incomplete.

Security priorities:

  1. Make host mutation opt-in and auditable.
  2. Stop secrets from entering CLI args and logs.
  3. Fix URL host validation.
  4. Add resource controls around all untrusted URL/media paths.
  5. Improve config write atomicity and symlink safety.

Exploit classes considered:

  • Supply-chain compromise through remote setup scripts and global npm/pipx installs.
  • Cookie exfiltration through logs/config dumps/agent traces.
  • Lookalike-domain routing.
  • Local temp-file collisions and secret residue.
  • Agent-induced resource exhaustion through media URLs and diagnostics.

Performance expert

This code is probably fine for a human running one command occasionally. It is not fine for agents polling status, MCP clients calling status, or CI. The doctor path is too heavy, repeated OpenCLI checks should be cached, and transcribe has no practical resource budget.

Performance priorities:

  1. Make doctor --fast local-only and cache expensive checks.
  2. Add concurrent bounded probes for network checks.
  3. Add max bytes/duration/chunks to transcription.
  4. Clamp API limit parameters and response body sizes.
  5. Remove repeated filesystem mutations from status paths.

UX expert

The worst UX issue is not visual. It is broken expectation management. The docs say commands exist that do not exist. doctor mutates the filesystem. install performs system changes unless the user knows to pass --safe. transcribe --provider auto can switch providers silently.

UX priorities:

  1. Make dangerous actions explicit and reversible.
  2. Make docs truthful before adding features.
  3. Make errors actionable, especially for cookie extraction.
  4. Never silently change provider/cost/privacy domain.
  5. Validate unknown channel names instead of silently ignoring them.

DX expert

The project is currently LLM-hostile despite being agent-oriented. llms.txt is stale, CLAUDE.md describes contracts that are not enforced, and cli.py is doing too much in one 1,800-line module.

DX priorities:

  1. Define the real public API: installer/capability manager vs unified read/search wrapper.
  2. Create a single source of truth for commands and channel capabilities.
  3. Split cli.py into install/config/doctor/skill/transcribe modules.
  4. Add typed channel interfaces and contract tests.
  5. Separate unit tests from integration tests.

Edge Case Master

The interesting bugs are not exotic; they are “obvious after seeing them”:

  • "x.com" in "notx.com" routes notx.com as Twitter/X.
  • github.com.evil.test routes as GitHub.
  • Config.to_dict() masks token, but not cookie, sessdata, or ct0.
  • Xueqiu’s rookiepy path calls .set() on a CookieJar and then swallows the exception.
  • Docker container name discovery can return multiple names and restart the wrong one.
  • A diagnostic command installs skills.
  • A shell script uses /tmp/xiaoyuzhou_$$ for temp data.

Edge-case priorities:

  1. Build adversarial tests for all platform router hostnames.
  2. Add fixture-based cookie extraction tests.
  3. Test multiple matching Docker containers.
  4. Test no-network/no-binary environments.
  5. Test agent-facing docs by running every command shown in llms.txt and test.sh.

Dependency freshness notes

This is not a full dependency audit, but these checks matter:

  • constraints.txt pins yt-dlp==2025.5.22. A later NVD entry reports a Windows command-injection vulnerability affecting yt-dlp versions 2025.06.25 and below when --exec is used, fixed in 2025.07.21. Agent Reach does not appear to use --exec, so this is not directly exploitable from current transcribe.py, but the pinned version is stale and within the affected range.
  • PyPI currently lists a newer yt-dlp release than the pinned constraint.
  • The project’s broad dependency ranges mean users may install versions very different from the tested constraint set.

Recommended action: add automated dependency scanning and decide whether constraints are authoritative.


Immediate patch plan

First 24 hours

  1. Fix host validation centrally and add negative tests for lookalike hosts.
  2. Expand secret redaction to include cookies/session/auth/csrf fields.
  3. Remove skill installation side effects from doctor.
  4. Update llms.txt, test.sh, and docs to remove nonexistent commands or clearly mark them as planned.
  5. Fix Xueqiu rookiepy cookie loading.
  6. Change shell script temp dir to mktemp -d and add bounded curl flags.
  7. Validate --channels; fail or warn on unknown names.
  8. Add pytest timeout and mark integration tests separately.

First week

  1. Make installer safe by default.
  2. Add explicit --yes-modify-system / --unsafe-system-install for host mutations.
  3. Add configure --stdin and interactive secret entry.
  4. Add transcription resource limits and cleanup by default.
  5. Make provider fallback explicit with --allow-paid-fallback.
  6. Cache doctor/opencli checks and add doctor --fast.
  7. Add CI checks: unit tests, ruff, mypy or pyright, dependency scan, and no-network unit test mode.

First two weeks

  1. Introduce agent-reach capabilities --json as the machine-readable contract for agents.
  2. Split cli.py into focused modules.
  3. Define typed channel interfaces and contract tests.
  4. Add a threat model doc focused on agents, cookies, local host mutation, and third-party tools.
  5. Replace shell-script transcription with Python implementation or mark it experimental.
  6. Decide and document whether Agent Reach is an installer/capability layer or a unified read/search wrapper.

Suggested regression tests

def test_host_matches_rejects_lookalikes():
    assert not GitHubChannel().can_handle("https://github.com.evil.test/x/y")
    assert not TwitterChannel().can_handle("https://notx.com/path")
    assert not YouTubeChannel().can_handle("https://youtube.com.evil.test/watch?v=x")


def test_config_redacts_cookie_like_values(tmp_path):
    cfg = Config(config_path=tmp_path / "config.yaml")
    cfg.data = {
        "xhs_cookie": "secret",
        "twitter_ct0": "secret",
        "bilibili_sessdata": "secret",
        "groq_api_key": "secret",
    }
    dumped = cfg.to_dict()
    assert all(v != "secret" for v in dumped.values())


def test_doctor_has_no_filesystem_side_effects(monkeypatch, tmp_path):
    # doctor should report only; skill install belongs to `agent-reach skill --install`
    ...


def test_transcribe_temp_files_cleaned_by_default(monkeypatch, tmp_path):
    ...


def test_unknown_install_channel_errors():
    ...

Final prioritization

The tie-breaker decision is simple: stop adding integrations until the trust model is fixed.

The order is:

  1. Safety defaults: installer, secrets, domain validation.
  2. Truthfulness: docs, llms.txt, test commands, product boundary.
  3. Resource controls: transcription, doctor, shell script network/media paths.
  4. Reliability: Xueqiu cookie path, Docker edge cases, config atomicity.
  5. Maintainability: split CLI, typed contracts, CI, capability JSON.

Agent Reach is salvageable, but the current version is carrying hidden risk in precisely the areas an agent-enablement tool cannot afford: installation, credentials, and machine-readable instructions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions