Back to skill

Security audit

Reach

Security checks for vulnerabilities and agentic risk

Overview

The skill is a mostly coherent live-data query engine, but it adds high-impact account, messaging, self-update, session-mining, and mutable third-party runtime execution capabilities that need review before installation.

Install only if you are comfortable with a data-query skill that can create third-party API accounts, store keys in the agent profile, start third-party MCP subprocesses, use an authenticated LinkedIn browser session including messaging actions, scan recent session history for API discovery, and self-update from GitHub. Prefer disabling self-update/api-mining, pinning MCP dependencies, filtering subprocess environments, and removing LinkedIn messaging before routine use.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T08 · Insecure Dependencies

Error
Location
scripts/sources/_mcp_client.py:27
Finding
Mutable Third-Party MCP Packages Execute with the Agent's Full Environment## Vulnerability Details **File Location**: `scripts/sources/_mcp_client.py:27-39` **Vulnerability Type**: Remote dependency execution with excessive environment exposure **Risk Level**: High ### Vulnerable Code ```python def start(self): import os env = os.environ.copy() if self._env: env.update(self._env) self._proc = subprocess.Popen( self._cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, text=True, bufsize=1, ) self._initialize() ``` The affected MCP commands are configured through mutable remote package references: ```python # scripts/sources/linkedin_mcp.py:31-39 def _get_client(auth: dict) -> _MCPClient: global _client if _client is None or _client._proc is None or _client._proc.poll() is not None: env = {} for key in ("LINKEDIN_USER_DATA_DIR", "LINKEDIN_CHROME_PATH"): val = auth.get(key) or auth.get(key.lower()) if val: env[key] = val _client = _MCPClient(["uvx", "linkedin-scraper-mcp@latest"], env=env if env else None) _client.start() return _client ``` ```python # scripts/sources/reddit_mcp_buddy.py:143-152 def _get_client(auth: dict) -> _MCPClient: global _client if _client is None or _client._proc is None or _client._proc.poll() is not None: env = {} for key in ("REDDIT_CLIENT_ID", "REDDIT_CLIENT_SECRET", "REDDIT_USERNAME", "REDDIT_PASSWORD"): val = auth.get(key) or auth.get(key.lower()) if val: env[key] = val _client = _MCPClient(["npx", "-y", "reddit-mcp-buddy"], env=env if env else None) _client.start() return _client ``` ```python # scripts/sources/yahoo_finance_mcp.py:27-38 def _get_client(auth: dict) -> _MCPClient: global _client if _cl ...[truncated 2727 chars]
Remediation
## Remediation Suggestions 1. Replace `os.environ.copy()` with a minimal environment allowlist. Include only essential runtime variables such as a controlled `PATH`, locale settings, and the credentials required by the selected connector. 2. Do not pass unrelated agent credentials to MCP subprocesses. 3. Pin npm and Python packages to reviewed, immutable versions. Replace `@latest` and unversioned `npx` execution with exact versions. 4. Pin Git dependencies to a reviewed commit hash rather than a mutable default branch. 5. Resolve and install dependencies during an explicit installation step rather than silently fetching executable code during a normal query. 6. Verify downloaded artifacts using hashes, lockfiles, or equivalent integrity metadata. 7. Run MCP servers in a restricted subprocess or sandbox with limited filesystem and network access. 8. Apply the same environment filtering to the duplicate MCP implementation in `scripts/sources/reddit_mcp_buddy.py`.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
SKILL.md:147
Finding
Background API Discovery Instructions Scan Unrelated Session Transcripts## Vulnerability Details **File Location**: `SKILL.md:147-165` **Vulnerability Type**: Excessive cross-session data access and retention **Risk Level**: Medium ### Vulnerable Instructions ```markdown ## Background tasks | Job | Mechanism | Schedule | Command | |---|---|---|---| | `reach:update` | cron | `0 0 * * *` | Self-update from GitHub source | | `reach:api-mine` | cron | `0 4 * * *` | Scan sessions for sites with APIs → `references/discovered-apis.md` | ## Source Discovery (reach:api-mine) The `reach:api-mine` cron job scans **all** session transcripts (not just research) for sites, services, databases, and archives that are used or needed by any skill. For each site found, it evaluates: - [ ] **Does it have an API?** — Check for programmatic access (REST, GraphQL, etc.) - [ ] **Can I access it?** — Free/freemium/paid? API key required? Account signup possible? - [ ] **What data does it provide?** — What endpoints exist? What can you search/retrieve? - [ ] **Does it belong as a preferred source?** — Is the API meaningfully better than the current access method (web scraping, SearXNG, browser) for the skill that found it? A site is only cataloged if it has a **confirmed working API**, the data is useful to an active skill/workflow, and the API is better than the current approach. Deduplicate against both `sources.yml` and `references/discovered-apis.md`. Catalog: `references/discovered-apis.md` — fully evaluated candidates ready for integration into `sources.yml`. ``` ### Technical Analysis The Skill’s stated core purpose is to perform live factual queries against registered data sources. The background instructions expand that scope by directing a scheduled job to inspect all available session transcripts, explicitly including sessions unrelated to research or Reach. No transcript-field allowlist, session ownership restriction, sensitivity filter, or explicit per-session consent requirement is specifie ...[truncated 1898 chars]
Remediation
## Remediation Suggestions 1. Restrict API discovery to sessions explicitly associated with Reach or to sessions whose users have opted into discovery. 2. Do not scan all session transcripts by default. 3. Apply an allowlist of fields needed for API discovery instead of exposing complete transcript content to the workflow. 4. Exclude private, confidential, authentication-related, and internal infrastructure content before extraction. 5. Remove source-session identifiers and other conversation provenance from the persistent catalog unless strictly necessary. 6. Store discovery results in access-controlled operational storage rather than in a broadly distributed Skill reference file. 7. Add retention limits and deletion support for derived discovery records. 8. Require explicit user or administrator authorization before enabling the scheduled mining task.
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (224)

Tainted flow: 'req' from os.environ.get (line 41, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
data = json.dumps(body).encode("utf-8") if body else None
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        return {"error": f"HTTP {e.code}: {e.read().decode('utf-8')}"}
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
Authenticated LinkedIn inbox reading, conversation access, message search, and send_message are active-account actions unrelated to a read-only live fact-query engine. If invoked by an agent, they can expose private communications or perform unauthorized actions on the user's behalf, turning a data lookup tool into an account-acting capability.

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
Exposing `send_message` gives the skill an active external side-effect on behalf of the authenticated user, which is unjustified for a fact-query tool. This can be abused to send spam, social-engineering messages, harassing content, or unauthorized outreach from the user's LinkedIn account, making the risk especially severe in an agentic environment.

Self-Modification

High
Category
Rogue Agent
Content
- Observation Journal emission for every query
- `queries.jsonl` append-only query log
- README.md and CHANGELOG.md per spec-ocas-skill-publishing.md
- `reach:update` daily self-update cron job

### Fixed
- Removed hardcoded `RT_KEY` value from SKILL.md and per-source documentation; all auth via `~/.hermes/.env`
Confidence
95% confidence
Finding
A daily self-update mechanism allows the skill’s code or behavior to change after deployment, undermining review-time assurances and creating a supply-chain modification path. In a skill that already interfaces with many external sources and credentials, automatic updates can silently introduce new capabilities or malicious logic without operator re-approval.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description presents a broad live data query engine that interfaces with numerous external sources for current factual information. The supplied code chunk, however, is only a thin helper script for parsing YAML text locally via PyYAML. It contains no network access, no API clients, no source routing, no query handling, and no domain-specific data retrieval logic. This is a materially different primary purpose, so the description does not accurately represent the behavior of this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
87% confidence
Finding
The code does perform live external API queries, so it is related to the declared purpose at a high level. However, the declared description presents a broad, general-purpose world-data query engine with extensive source coverage and specific usage boundaries. The actual code is much narrower: it is a command-line wrapper around a single service, api.katzilla.dev, for listing agents and invoking actions. It does not itself implement routing across many named external providers, nor does it demonstrate the claimed registry breadth or enforce the declared limitations about research/synthesis. Therefore the description overstates and generalizes the behavior of the supplied code chunk enough to count as a mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding
The description claims a broad live-data engine for many categories of real-time factual queries across dozens of registered sources. The code instead is a single-purpose property lookup script with fixed commands for Redfin/Zillow-style APIs and one SF assessor dataset. While it does query live external APIs, that is only a narrow subset of the declared functionality. The primary purpose, scope of sources, and applicable triggers are materially different from the description.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description presents a domain-specific skill: a real-time external world-data query engine with routing across ~55 registered factual data sources. The supplied code does not implement that behavior. Instead, it provides low-level transport/integration infrastructure for communicating with MCP servers over stdio JSON-RPC, including process startup, initialization, request sending, response parsing, and shutdown. While this could support tools that query live data, this specific code chunk itself does not perform source registry routing, external API selection, factual data retrieval across the described domains, or trigger-specific logic. This is a materially different primary purpose from the declared skill behavior, so it is a mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description presents a broad real-time factual data query engine over a registry of many external sources, with explicit exclusions for entity investigations and non-research synthesis. The supplied code does something materially different: it is a dedicated LinkedIn connector that starts a linkedin-scraper MCP server and exposes LinkedIn profile/company/job lookup, employee search, message search, inbox/conversation retrieval, home feed access, and outbound message sending. This is not a generic world-data router and includes private/social-network investigation and messaging capabilities that are undeclared and inconsistent with the stated purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The code is not a general live world-data query router across many registered sources. It is a narrow source adapter for manuals content only. While product manuals are mentioned in the declared coverage, the implementation relies on autocomplete, search-query handoff, Wayback CDX archive enumeration, direct image fetching, and OCR extraction from manual page images. Those behaviors are materially different from the declared 'real-time external APIs for factual ground truth' framing and contradict the note 'Do not use for web research,' because the search action explicitly instructs the caller to run web_search. The random-page reading feature is also outside the stated purpose of factual external API querying. Therefore the description does not accurately represent this code chunk's actual behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description presents a general-purpose live external API query engine covering dozens of source types and many factual domains. The code instead implements a specialized academic paper-search MCP connector with actions for searching scholarly repositories and downloading PDFs. While scholarly literature is one declared domain, the implemented behavior is only a small subset of the claimed system and its primary purpose is materially narrower than advertised. Additionally, the download capability is undeclared. Therefore the description does not accurately represent what this code chunk actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The code is not a generic live world-data query engine. It is a narrowly scoped Reddit integration that starts an MCP server and calls Reddit-specific tools (`browse_subreddit`, `search_reddit`, `get_post_details`, `user_analysis`, `reddit_explain`). This materially differs from the declared purpose of routing factual real-time queries across dozens of authoritative sources. The mismatch is especially strong because the code includes capabilities the description explicitly excludes, such as entity/user analysis and explanatory behavior. While both involve external data access, the actual resource accessed and the use cases supported are substantially different from the declared description and triggers.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The code does not implement a general-purpose live world-data query engine across a registry of many factual sources. Instead, it is a narrow connector for accessing academic papers through Sci-Hub mirrors, with CrossRef used only to resolve titles/keywords to DOIs. Its primary purpose is research-paper discovery and PDF retrieval, including downloading files locally and using DNS-over-HTTPS plus mirror rotation to work around blocked domains. Those are materially different capabilities and intent from the declared description, which emphasizes broad factual API queries and explicitly says not to use it for research.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The code clearly performs live external API queries, so it partially aligns with the 'real-time data' aspect of the description. However, its actual scope is much narrower: it is a dedicated weather source module with fixed actions for conditions, forecasts, alerts, METAR, global weather, and severe outlooks. There is no evidence of a multi-domain registry, broad source routing, or support for finance, courts, scholarly literature, news, property, satellite imagery, product manuals, or other declared domains. This is a material description-behavior mismatch in primary purpose and supported capabilities, not just an implementation detail.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description presents this skill as a general-purpose live external-data query engine covering many categories and sources. The supplied code chunk, however, only implements a wrapper around the WeLib API for searching books and academic papers and fetching item details. There is no evidence of source registry routing, support for dozens of data providers, or capabilities related to weather, government, finance, courts, property, satellite imagery, or product manuals. This is a materially different and much narrower primary purpose, so the description does not accurately represent the code.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description presents this skill as a general-purpose live external-data query engine covering many source categories and domains. The supplied code does not implement that broad behavior. Instead, it is a specialized adapter for a single Yahoo Finance MCP server, with actions limited to equity/market-related data. While finance is one of the domains mentioned in the description, the actual code's primary purpose is much narrower than declared, and it lacks the claimed multi-source routing behavior. This is a material description-to-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared description promises a real-time external API query engine with broad factual data access across many source categories. The actual code shown only runs `python3 ~/.hermes/scripts/skill_update.py ocas-reach`, which is a local updater wrapper. Its primary purpose is operational maintenance of a skill, not live data retrieval. This is a material description-behavior mismatch.

Self-Modification

High
Category
Rogue Agent
Content
| Job | Mechanism | Schedule | Command |
|---|---|---|---|
| `reach:update` | cron | `0 0 * * *` | Self-update from GitHub source |
| `reach:api-mine` | cron | `0 4 * * *` | Scan sessions for sites with APIs → `references/discovered-apis.md` |

## Source Discovery (reach:api-mine)
Confidence
97% confidence
Finding
A scheduled self-update from GitHub gives the skill an autonomous code-modification path. If the upstream repository, release channel, or update transport is compromised, the agent can ingest and execute attacker-controlled changes without a human review step, turning this skill into a supply-chain entry point.

Self-Modification

High
Category
Rogue Agent
Content
**Key principle:** Not a general search index. Only sites that specific skills already use or need, where an API would reduce friction vs. the current method.

`reach.init` registers `reach:update` on first invocation. No operational background tasks beyond self-update — Reach is purely reactive to user/agent queries.

**Session-retention limitation (Jun 18, 2026)** — The session database (via `session_search`) only retains recent sessions (typically 48-72h of FTS5-indexed content). Older research sessions — even those with significant API discoveries — become unsearchable once they age out. This means:
- The api-mine cron can only discover APIs from sessions that are still in the active session store
Confidence
96% confidence
Finding
The manifest states that reach.init registers a self-update task on first invocation, meaning simply using the skill can establish persistent code-changing behavior. That lowers the bar for unauthorized modification and increases blast radius because a one-time invocation can create an ongoing supply-chain risk.

Self-Modification

High
Category
Rogue Agent
Content
- A "0 new APIs" result from the cron is NORMAL and expected when sessions are current — it means the catalog is up-to-date, not that the cron is broken
- **Cron-skew (Jun 27, 2026)**: During periods when the agent runs primarily as cron jobs, there may be zero interactive sessions to mine. `[SILENT]` is correct — see `references/api-mine-cron-notes.md` § Cron-Skew.

## Self-Update

See `references/self-update-reach.md`.
Confidence
95% confidence
Finding
The explicit Self-Update section confirms the skill includes code-changing behavior as a documented feature. Self-modification is dangerous in agent environments because it can bypass normal review, alter future behavior, and persist malicious or erroneous changes across runs.

Self-Modification

High
Category
Rogue Agent
Content
## Self-Update

See `references/self-update-reach.md`.

## Validation rules
Confidence
95% confidence
Finding
Referencing a dedicated self-update procedure indicates an intended mechanism for modifying the skill after deployment. In the context of a skill with shell, network, file-write, and broad invocation potential, this materially increases compromise impact and persistence potential.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The document authorizes account creation, credential capture, email verification, local secret storage, and post-registration validation for a skill whose declared purpose is only live factual API querying. That is a material scope expansion: it gives the skill persistent access acquisition and secret-handling capabilities that can be abused to create accounts and accumulate credentials across many third-party services without a narrowly scoped user approval step.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
Granting the skill authority to open browser sessions, submit registration forms, and read Gmail for verification codes gives it cross-system access well beyond data retrieval. This creates an unnecessary path to persistent third-party account creation and email-based confirmation flows, which could be leveraged to exfiltrate or misuse access if the skill is prompted adversarially or behaves incorrectly.

YARA rule 'agent_skill_remote_bootstrap_execution': Remote script or code download followed by execution/bootstrap installation [agent_skills]

High
Category
YARA Match
Content
lready present)
- `numpy` ✓ (already present)
- `metrust` ✓ (on PyPI, v0.4.7)
- `rusbie` ✗ (GitHub-only, unpublished to PyPI)
- `rustweather` ✗ (GitHub-only)
- `wrf-rust` ✗ (GitHub-only)
- `cfrust` ✗ (GitHub-only)

### Rust binaries (need cargo to build)
- `radar-render` from `rustdar`
- `run_case` from `ecape-rs`
- `nexrad-render-cli` (bundled source)

## Install Result

**Failed.** `pip install git+https://github.com/FahrenheitResearch/hermes-weather-plugin.git` failed because `rustplots>=0.1.0` (a dependency of `rustweather`) is not published to PyPI. Even with Rust toolchain present, the build would fail.

## Tools Breakdown

### Data tools (would work if install succeeded)
| Tool | API | Reach equivalent |
|------|-----|-----------------|
| `wx_conditions` | NWS API | `noaa_nws` |
| `wx_forecast` | NWS API | `noaa_nws` |
| `wx_alerts` | NWS API | `noaa_nws` |
| `wx_metar` | METAR | Partial `noaa_nws` |
| `wx_brief` | Composite | Composite |
| `wx_global` | Open-Meteo |
Confidence
85% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Self-Modification

High
Category
Rogue Agent
Content
# Reach — Self-Update Procedure

Standard GitHub tarball update via gh CLI. Runs silently.
Confidence
97% confidence
Finding
A self-modification capability is especially risky in a skill whose declared role is only to query external data sources. Allowing the skill to update itself creates a path for persistence, scope expansion, and supply-chain compromise, and the surrounding instructions indicate the modification can occur through automated remote retrieval rather than controlled installation.

Static analysis

Detected: suspicious.insecure_tls_verification

HTTPS certificate verification is disabled.

Warn
Code
suspicious.insecure_tls_verification
Location
scripts/sources/scihub.py:155