Back to skill

Security audit

alexey-brave-search

Security checks for vulnerabilities and agentic risk

Overview

This skill is a useful web-search/content tool, but it materially under-discloses how it performs searches and allows broad outbound URL fetching without clear safeguards.

Install only if you are comfortable with a skill that sends search queries to Brave, scrapes Brave result pages rather than using the advertised API, and can fetch arbitrary web pages from the agent's network context. Avoid using it in environments that can reach private admin services, cloud metadata endpoints, or sensitive internal sites unless outbound network controls are in place.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
content.js:34
Finding
Unrestricted URL Fetching Enables Server-Side Request Forgery## Vulnerability Details **File Location**: `content.js:8, 34-43`; `search.js:120-127, 156` **Vulnerability Type**: Server-Side Request Forgery (SSRF) **Risk Level**: Medium ### Vulnerable Code `content.js:8`: ```js const url = process.argv[2]; ``` `content.js:34-43`: ```js try { const response = await fetch(url, { headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.9", }, signal: AbortSignal.timeout(15000), }); ``` `search.js:120-127`: ```js async function fetchPageContent(url) { try { const response = await fetch(url, { headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", }, signal: AbortSignal.timeout(10000), }); ``` `search.js:156`: ```js result.content = await fetchPageContent(result.link); ``` ### Technical Analysis The `content.js` entry point accepts a URL directly from a command-line argument and passes it to `fetch()` without validating its protocol, hostname, resolved IP address, port, or redirect destinations. Consequently, an untrusted caller can direct the process to issue HTTP requests from the Agent's network environment. The optional content-fetching behavior in `search.js` has the same underlying weakness. URLs extracted from search results are passed to `fetchPageContent()` without destination validation. Because `fetch()` follows HTTP redirects by default, an initially public URL may redirect to a loopback, link-local, private-network, or cloud metadata address. Neither implementation blocks IPv4 or IPv6 private ranges, loopback addresses, link-local address ...[truncated 2065 chars]
Remediation
## Remediation Suggestions 1. Parse inputs with the standard `URL` class and permit only explicitly required protocols, preferably `https:` and, if necessary, `http:`. 2. Reject URLs containing embedded credentials, malformed hostnames, unsupported ports, or non-HTTP schemes. 3. Resolve the destination hostname before connecting and reject all loopback, private, link-local, multicast, unspecified, documentation, and reserved IPv4 and IPv6 ranges. 4. Disable automatic redirects or process redirects manually. Apply the complete protocol, hostname, port, and resolved-address validation to every redirect target. 5. Defend against DNS rebinding by ensuring the validated address is the address used for the connection, or by using a hardened outbound proxy that enforces destination policy. 6. Prefer an explicit allowlist of trusted domains when the expected set of content sources is known. 7. Block cloud metadata destinations explicitly, including link-local metadata addresses and provider-specific metadata hostnames. 8. Apply strict response-size limits while streaming the body rather than calling `response.text()` without a bound. 9. Validate response content types and reject unexpected binary or active content. 10. Enforce outbound firewall or proxy rules so the Skill cannot reach internal networks or metadata services even if application-level validation is bypassed. 11. Return generic request errors where possible to reduce internal host and port enumeration through status, timing, and connection-error differences.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
If the code claims Brave Search API use but actually scrapes Brave web pages and fetches arbitrary third-party URLs, the skill is materially more powerful and less predictable than advertised. That increases the risk of policy bypass, unexpected data flows to external sites, and review evasion because core network behavior is hidden behind inaccurate documentation.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
If the code claims Brave Search API use but actually scrapes Brave web pages and fetches arbitrary third-party URLs, the skill is materially more powerful and less predictable than advertised. That increases the risk of policy bypass, unexpected data flows to external sites, and review evasion because core network behavior is hidden behind inaccurate documentation.

Ae1

High
Category
analysis-evasion
Content
./search.js "query" # Basic search (5 results)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
./search.js "query" # Basic search (5 results)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
./search.js "query" # Basic search (5 results)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
./search.js "query" # Basic search (5 results)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Known Vulnerable Dependency: ws==8.18.3 — 2 advisory(ies): CVE-2026-45736 (ws: Uninitialized memory disclosure); CVE-2026-48779 (ws: Memory exhaustion DoS from tiny fragments and data chunks)

High
Category
Supply Chain
Confidence
95% confidence
Finding
The lockfile pins transitive dependency ws to version 8.18.3, and the static finding cites known advisories for uninitialized memory disclosure and memory-exhaustion denial of service in that version. Even though this file is only a dependency manifest, it accurately reflects that the skill will install the affected package via jsdom, so the vulnerable component is present in the shipped dependency graph.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill declares no explicit tool scope or permissions even though its documented behavior requires network access. This creates an authorization gap where an agent may invoke outbound web requests without clear policy visibility, making review, sandboxing, and least-privilege enforcement harder.

Vague Triggers

Medium
Confidence
87% confidence
Finding
The description is broad enough to match many ordinary user requests involving facts, documentation, or general web content. Overly broad invocation language can cause the skill to be selected in more contexts than necessary, increasing exposure to unnecessary network access and making prompt-routing abuse easier.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The catch-all guidance 'Any task requiring web search without interactive browsing' is ambiguous and can trigger the skill for a very wide range of requests. In a network-capable skill, such open-ended routing guidance increases the chance of overuse, unnecessary external requests, and misuse in contexts where tighter controls would be appropriate.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
This code sends the user's query to Brave Search and, when --content is used, later fetches third-party result pages. While the CLI usage shows functionality, it does not clearly warn that user-provided queries and visited URLs will be transmitted to external services, which is a user-disclosure gap for network operations.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
The request hard-codes an "Accept-Language: en-US,en;q=0.9" header, which imposes a specific language/locale on fetched content. This is a natural-language policy concern because the skill does not offer user opt-in or explain why English is required.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"author": "Mario Zechner",
	"license": "MIT",
	"dependencies": {
		"@mozilla/readability": "^0.6.0",
		"jsdom": "^27.0.1",
		"turndown": "^7.2.2",
		"turndown-plugin-gfm": "^1.0.2"
Confidence
93% confidence
Finding
The dependency is specified with a caret range, which allows newer compatible versions to be installed over time rather than guaranteeing a single reviewed version. This creates supply-chain risk because future installs may pull in an unexpected release containing a malicious change or a newly introduced vulnerability.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"license": "MIT",
	"dependencies": {
		"@mozilla/readability": "^0.6.0",
		"jsdom": "^27.0.1",
		"turndown": "^7.2.2",
		"turndown-plugin-gfm": "^1.0.2"
	}
Confidence
93% confidence
Finding
The jsdom dependency uses a caret version range, so installs are not fully reproducible and may resolve to different upstream releases later. In a package that processes untrusted web content, dependency drift increases exposure to supply-chain compromise or regressions in a parsing library that could affect security-sensitive behavior.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dependencies": {
		"@mozilla/readability": "^0.6.0",
		"jsdom": "^27.0.1",
		"turndown": "^7.2.2",
		"turndown-plugin-gfm": "^1.0.2"
	}
}
Confidence
92% confidence
Finding
Using a non-pinned turndown version permits automatic movement to later patch/minor releases, which weakens reproducibility and reviewability of the shipped dependency set. If an upstream package is compromised or a bad release is published, new installs could silently incorporate it.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"@mozilla/readability": "^0.6.0",
		"jsdom": "^27.0.1",
		"turndown": "^7.2.2",
		"turndown-plugin-gfm": "^1.0.2"
	}
}
Confidence
92% confidence
Finding
The turndown-plugin-gfm dependency is not pinned exactly, allowing future installs to resolve to different code than what was originally tested. This is a classic low-severity supply-chain hygiene issue because it can introduce unreviewed changes indirectly from the package registry.

Natural-Language Policy Violations

Low
Confidence
98% confidence
Finding
The request header explicitly sets Accept-Language to en-US,en;q=0.9, which imposes a language/locale choice regardless of user preference. This is a natural-language policy issue because the skill does not offer a locale choice or explain a justified region-specific constraint.

Static analysis

No suspicious patterns detected.