Back to skill

Security audit

Omni Research

Security checks for vulnerabilities and agentic risk

Overview

This research skill is not proven malicious, but it needs Review because it controls logged-in browser sessions and can send collected content to a configurable API/proxy despite browser-only and zero-key framing.

Install only if you are comfortable letting the skill operate a logged-in Chromium browser through CDP. Use a dedicated browser profile, avoid sensitive account data or confidential research, review ~/.config/omni-research/config.json before use, and treat API mode or synthesis as sending your query and extracted results to the configured proxy endpoint.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (5)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
research.py:54
Finding

Overly Broad DOM Extraction May Capture Unrelated Authenticated Browser Content

Content
View full analysis
0) { const last = prose[prose.length-1].innerText; if (last.length > 100) return last; } // Strategy 2: find answer text from body content const lines = bodyText.split('\n').filter(l => l.trim().length > 40); if (lines.length > 3) return lines.join('\n'); ``` ```python # research.py:104-112 const scroll = document.querySelector('[class*="overflow-y-auto"][class*="scrollbar-gutter"]'); if (scroll && scroll.innerText.length > 50) { // Strip the user query (first line) to get just the response const lines = scroll.innerText.split('\n').filter(l => l.trim()); const responseStart = lines.findIndex((l, i) => i > 0 && l.length > 20); if (responseStart > 0) return lines.slice(responseStart).join('\n'); } return ''; ``` ```python # research.py:142-151 const allDivs = document.querySelectorAll('div'); let best = null, bestLen = 0; for (const d of allDivs) { const t = d.innerText; if (t.length > 200 && t.length < 15000 && !d.querySelector('nav, header, aside')) { if (t.length > bestLen) { bestLen = t.length; best = d; } } } if (best) return best.innerText; return ''; ``` ### Technical Analysis The Skill runs JavaScript inside pages opened under the user's authenticated Perplexity, Grok, and Gemini browser sessions. Its fallback extractors are not reliably limited to the response generated for the current query. The Perplexity fallback ...[truncated 1767 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
research.py:165
Finding

Sensitive Queries and Browser-Derived Content Can Be Sent to an Arbitrary Configured Proxy

Content
View full analysis
str: url = config["cliproxy_url"] key = config["cliproxy_key"] async with httpx.AsyncClient(timeout=config["timeout_api"]) as client: for attempt in range(3): resp = await client.post( f"{url}/chat/completions", headers={"Authorization": f"Bearer {key}"}, json={"model": model, "messages": messages, "max_tokens": 4096}, ) ``` ```python # research.py:198-210 async def synthesize(config: dict, query: str, results: dict[str, str]) -> str: sources_text = "" for name, text in results.items(): truncated = text[:3000] if len(text) > 3000 else text sources_text += f"\n\n### {name}\n{truncated}" # Try synthesis model, fall back to gemini if rate limited for model in [config["synthesis_model"], config["gemini_api_model"]]: try: return await _cliproxy_chat( config, model, [ {"role": "system", "content": "Synthesize these research results into 3-5 bullet points. Note agreements, disagreements, unique insights. Write in the query's language."}, {"role": "user", "content": f"Query: {query}\n\nSources:{sources_text}"}, ], ) ``` ```python # research.py:249-253 except RuntimeError as e: print(f" {e}", file=sys.stderr) sources = [s for s in sources if s in API_SOURCES] or ["gemini-api"] print(f" Falling back to: {', '.join(sources)}", file=sys.stderr) ``` ### Technical Analysis The proxy URL is loaded from a user-writable configuration file and used directly as th ...[truncated 1919 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
research.py:20
Finding

Hardcoded Proxy Credential Is Used When No Configuration File Exists

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Unpinned Dependencies and Unnecessary Global Package Installation Expand Supply-Chain Risk

Content
View full analysis
=0.27 websockets>=12.0 ``` ```python # setup.py:47-51 if check_agent_browser(): print("[OK] agent-browser installed") else: print("[!!] agent-browser not found") print(" Install: npm install -g agent-browser") ``` ### Technical Analysis The Python dependencies specify only minimum versions. Installation can therefore resolve to any future version, including releases that were not reviewed with this Skill. No lockfile or integrity hashes are provided. The setup utility also recommends a global installation of the unpinned npm package `agent-browser`. The reviewed browser implementation uses `httpx`, `websockets`, and direct CDP calls; it does not invoke `agent-browser`. This makes the suggested global package an unnecessary expansion of the trusted computing base. Package installation commonly executes package-controlled build or lifecycle code with the installing user's privileges. Mutable version resolution weakens reproducibility and can expose users to compromised future releases or unexpected behavior changes. ### Attack Path 1. A user installs dependencies from `requirements.txt` or follows the setup instruction. 2. The package manager resolves the newest versions permitted by the open-ended constraints. 3. A future compromised, malicious, or incompatible package release is selected. 4. Package installation hooks or imported runtime code execute under the user's account. 5. For the globally installed npm package, the component becomes available system-wide for that user despite not being required by the reviewed implementation. ### Impact Assessment The exact impact depends on the behavior of a future selected package release; no currently malicious dependency was ...[truncated 363 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
research.py:198
Finding

Untrusted Source Responses Can Inject Instructions into the Synthesis Prompt

Content
View full analysis
str: sources_text = "" for name, text in results.items(): truncated = text[:3000] if len(text) > 3000 else text sources_text += f"\n\n### {name}\n{truncated}" # Try synthesis model, fall back to gemini if rate limited for model in [config["synthesis_model"], config["gemini_api_model"]]: try: return await _cliproxy_chat( config, model, [ {"role": "system", "content": "Synthesize these research results into 3-5 bullet points. Note agreements, disagreements, unique insights. Write in the query's language."}, {"role": "user", "content": f"Query: {query}\n\nSources:{sources_text}"}, ], ) ``` ### Technical Analysis Responses from external AI services are untrusted content. The implementation concatenates them directly into a synthesis prompt without strong delimiters, structured fields, provenance controls, or a system-level instruction that source-contained commands must not be followed. A malicious or compromised source can return text such as instructions to ignore other sources, conceal disagreements, add attacker-selected statements, or alter the required output. The synthesis model may interpret those instructions as part of the active task rather than as quoted research material. The reviewed synthesis model has no local tool access through this code, so the confirmed impact is output manipulation rather than direct system compromise. ### Attack Path 1. A queried external service returns adversarial text, or its source material causes it to reproduce prompt-injection instructions. 2. The text is stored ...[truncated 821 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The documented purpose emphasizes using the user's own browser sessions with 'zero API keys,' but the behavior includes external API usage, local config reads, and synthesis through a remote proxy/model endpoint. This mismatch is dangerous because sensitive prompts, extracted browser-session data, or research results may be sent off-device or accessed through undeclared channels, defeating user expectations and informed consent.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill declares powerful capabilities in practice—network access, shell execution, and file access patterns—without an explicit permission or allowed-tools scope. That makes the trust boundary unclear and can cause a host agent to grant broader access than a user would reasonably expect from the manifest alone.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill advertises 'No API keys' and reliance on existing subscriptions, yet also supports a key-based API mode. While not inherently malicious, this is a security-significant disclosure failure because users may invoke the skill under incorrect assumptions about where credentials are required and how data is routed.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

Connecting to a browser's remote debugging interface gives the skill the ability to open tabs, inject input, read page contents, and act inside already-authenticated sessions. Failing to prominently warn users about this is dangerous because CDP access is highly privileged and can expose account data or perform actions as the user across multiple services.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The eval_js helper executes arbitrary JavaScript in the context of the currently loaded page and returns the extracted value, enabling silent reading of page content. In a live user browser, this can expose sensitive data from authenticated pages, including prompts, search results, account information, or other DOM-accessible content, without visible disclosure to the user.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code opens browser tabs, brings them to the foreground, injects text, presses Enter, and closes tabs without any explicit user consent or runtime confirmation in this module. Because it connects to the user's live browser session via CDP, those actions execute with the user's authenticated state and can trigger searches, form submissions, or other side effects on real sites.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill description promises 'zero API keys' and use of the user's existing subscriptions, but the code embeds and uses a separate bearer token and proxy endpoint. This is a security-relevant transparency failure because users may believe their data stays within their browser sessions when in fact the tool can send prompts and results to an undisclosed third-party service.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
99% confidence
Finding

After collecting responses from browser-authenticated services, the code forwards those results to a separate synthesis API via _cliproxy_chat. This expands data exposure beyond the services the user directly queried and is especially dangerous because the browser-only description does not disclose that page-derived content will be relayed to another external processor.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The synthesis step sends both the user's original query and extracted content from Perplexity/Grok/Gemini to an external API without an explicit warning or consent at the point of use. Because those browser-derived results may contain sensitive user-entered material, account-contextual content, or proprietary research, this creates a meaningful confidentiality and data-handling risk.

Content

No source excerpt is available for this finding.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
70% confidence
Finding

Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Content

Scanner excerpt · browser.py (reported line 178)May include surrounding context.

python
def check_cdp(port: int = 9222) -> bool:
    try:
        import httpx
        resp = httpx.get(f"http://127.0.0.1:{port}/json/version", timeout=2)
        return resp.status_code == 200
    except Exception:
        return False

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
70% confidence
Finding

Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Content

Scanner excerpt · setup.py (reported line 33)May include surrounding context.

python
def check_cdp(port: int = 9222) -> bool:
    try:
        import httpx
        resp = httpx.get(f"http://127.0.0.1:{port}/json/version", timeout=2)
        return resp.status_code == 200
    except Exception:
        return False

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified with a lower bound only (httpx>=0.27), which allows non-reproducible installs and may pull in future releases with breaking changes or newly introduced vulnerabilities. In a security-sensitive skill that automates browser-driven research and network access, weak dependency control increases supply-chain risk and makes it harder to verify whether deployed environments are safe.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
httpx>=0.27
websockets>=12.0

Unverifiable Dependency: httpx has 2 known advisory(ies) (CVE-2021-41945 (Improper Input Validation in httpx); CVE-2021-41945 (Encode OSS httpx <=1.0.0.beta0 is affected by improper input validation in `http)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
82% confidence
Finding

The manifest does not pin httpx, so it is impossible to determine from this file alone whether an affected version with known advisories could be installed. This uncertainty is itself a supply-chain weakness: the environment may resolve to a vulnerable version, and the lack of reproducibility prevents reliable security assurance for a network-capable skill.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is unpinned (websockets>=12.0), so installations may resolve to different versions over time, including releases with undiscovered or newly disclosed security issues. Because this skill relies on browser/CDP-style communication and network messaging, dependency drift in a websocket library can directly affect exposure to denial-of-service or protocol-handling flaws.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
httpx>=0.27
websockets>=12.0

Unverifiable Dependency: websockets has 4 known advisory(ies) (CVE-2018-1000518 (websockets is vulnerable to denial of service by memory exhaustion); CVE-2021-33880 (Observable Timing Discrepancy in aaugustin websockets library); CVE-2018-1000518 (aaugustin websockets version 4 contains a CWE-409: Improper Handling of Highly C) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
83% confidence
Finding

Because websockets is not pinned, the file cannot prove that installed versions are outside the range of known vulnerable releases. In a tool that performs parallel remote queries and likely depends on persistent browser or websocket communication, uncertainty around websocket library safety increases the chance of avoidable denial-of-service or protocol-level exposure.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The system prompt says "Write in the query's language," imposing a locale/language behavior automatically based on input rather than offering the user a choice. The policy only allows such constraints when the skill provides explicit language choice or documents a justified locale restriction.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.