Back to skill

Security audit

WebToSkill

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent, but it should be reviewed because it renders user-selected public pages with weakened browser and HTTPS protections.

Install only if you are comfortable running a public-web crawler. Use it only on public content you are permitted to retain and redistribute, and run it in an isolated environment or container with minimal filesystem access, current browser binaries, pinned dependencies, normal Chromium sandboxing, and normal HTTPS certificate validation.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/crawl_urls.py:82
Finding

Chromium Sandbox Is Disabled by Default for Untrusted Web Content

Content
View full analysis
list[str]: args = ["--disable-blink-features=AutomationControlled", "--disable-dev-shm-usage"] if os.environ.get("PLAYWRIGHT_NO_SANDBOX", "1").lower() not in {"0", "false", "no"}: args.extend(["--no-sandbox", "--disable-setuid-sandbox"]) return args ``` The single-page fetch helper implements the equivalent behavior: ```python launch_args = [ "--disable-blink-features=AutomationControlled", "--disable-dev-shm-usage", ] if os.environ.get("PLAYWRIGHT_NO_SANDBOX", "1").lower() not in {"0", "false", "no"}: launch_args.extend(["--no-sandbox", "--disable-setuid-sandbox"]) ``` ### Technical Analysis The scripts intentionally load JavaScript and other active resources from user-selected, potentially attacker-controlled websites. Chromium normally isolates renderer processes through operating-system sandbox mechanisms. Passing both `--no-sandbox` and `--disable-setuid-sandbox` removes this containment boundary. The environment-variable expression defaults to `"1"`, so unsafe execution occurs unless the operator explicitly overrides it. Disabling the browser sandbox is not intrinsically required for public-page crawling and exceeds the minimum privileges necessary in environments where Chromium sandboxing is available. A browser vulnerability triggered by hostile page content would ordinarily still need a sandbox escape to reach resources available to the crawler process. This configuration removes that additional security boundary. ### Attack Path 1. An attacker hosts a public page containing content designed to exploit a vulnerability in the installed Ch ...[truncated 1251 chars]
Remediation
View remediation
list[str]: args = [ "--disable-blink-features=AutomationControlled", "--disable-dev-shm-usage", ] if os.environ.get("PLAYWRIGHT_NO_SANDBOX", "0").lower() in {"1", "true", "yes"}: args.extend(["--no-sandbox", "--disable-setuid-sandbox"]) return args ``` 2. Treat unsandboxed mode as an exceptional, explicit opt-in and emit a prominent warning when it is enabled. 3. If the host cannot support Chromium's sandbox, run the entire crawler inside a hardened container or virtual machine with: - A non-root user. - A read-only root filesystem. - A dedicated writable task directory. - Dropped Linux capabilities. - A restrictive seccomp/AppArmor profile. - No host filesystem mounts containing credentials. - Network egress controls. 4. Keep Chromium updated and use a reviewed, pinned browser version. 5. Apply the correction consistently to `prepare_urls.py`, `crawl_urls.py`, and `fetch_rendered_html.py`, including the copy placed in generated skills. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/crawl_urls.py:417
Finding

HTTPS Certificate Validation Is Disabled for All Browser Requests

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fetch_rendered_html.py:81
Finding

SSRF Guard Is Vulnerable to DNS Rebinding and Resolution-Time Race Conditions

Content
View full analysis
now: return addresses self._dns_cache.pop(key, None) task = self._dns_inflight.get(key) if task is None: task = asyncio.create_task(self._resolve_uncached(host, port)) self._dns_inflight[key] = task addresses = await asyncio.shield(task) self._dns_cache[key] = (time.monotonic() + self.dns_cache_ttl, addresses) return addresses ``` Resolution is performed independently of Chromium's eventual connection: ```python infos = await asyncio.wait_for( loop.getaddrinfo(host, port, type=socket.SOCK_STREAM), timeout=self.dns_timeout, ) addresses = tuple(dict.fromkeys(info[4][0] for info in infos if info[4])) if not addresses: raise URLSafetyError(f"DNS returned no addresses for {host}") return addresses ``` After this validation, the browser request is allowed to continue by hostname: ```python async def validate_route(route: Any, request: Any) -> None: nonlocal blocked_navigation_error request_url = request.url if not request_url.startswith(("http://", "https://")): if request.is_navigation_request(): blocked_navigation_error = URLSafetyError( f"blocked navigation scheme: {urlparse(request_url).scheme}" ) await route.abort("blockedbyclient") return try: await url_guard.validate(request_url) except URLSafetyError as exc: if request.is_navigation_request(): blocked_navigation_error = ex ...[truncated 2719 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
README.md:53
Finding

Installation Instructions Use Unpinned Dependencies and Browser Binaries

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description is about ingestion and packaging of web content into a reusable skill: URL discovery, crawling, rendered HTML snapshots, source profiling, and bounded index creation. The supplied code chunk performs a different function: post-build retrieval over an already generated index. Its main operations are parsing a query, loading JSONL records from disk, tokenizing/scoring terms across title/description/keywords/URL, ranking results, and printing bounded search output. This is related to indexed web content, but it is not merely an implementation detail of the declared behavior; it is a distinct capability with a different primary purpose. Therefore the description does not accurately represent what this code chunk actually does.

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/fetch_rendered_html.py (reported line 200)May include surrounding context.

python
parsed.path.startswith(prefix) for prefix in rule.match_path_prefixes
            )
            if domain_matches and path_matches:
                return rule
    return None

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/prepare_urls.py (reported line 282)May include surrounding context.

python
parsed.path.startswith(prefix) for prefix in rule.match_path_prefixes
            )
            if domain_matches and path_matches:
                return rule
    return None

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · README.md (reported line 181)May include surrounding context.

md
## Site-Specific Behavior

| Site or content type | Strategy                                                                                                                                     |
|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| Generic public pages | Discover links from rendered navigation and fall back to the rendered page body                                                              |
| ChatWiki Docs        | Use the Docusaurus sitemap and retain the language selected by the entry URL                                                                 |

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · README.md (reported line 186)May include surrounding context.

md
| Generic public pages | Discover links from rendered navigation and fall back to the rendered page body                                                              |
| ChatWiki Docs        | Use the Docusaurus sitemap and retain the language selected by the entry URL                                                                 |
| KanCloud             | Read the full directory tree from `application/payload+json`; fail rather than silently continue when a known directory yields only one page |
| Yuque                | Use a longer navigation timeout during directory discovery                                                                                   |
| Feishu               | Keep the longest stable body snapshot if the final rendered body becomes empty or shorter                                                    |
| Other adapted sites  | Use built-in body selectors and fall back to the rendered page body when necessary                                                           |

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill instructs the agent to perform network access, file reads/writes, and execution of bundled scripts, but it declares no explicit tool or permission scope. Without a clear allowlist, a host agent may grant broader capabilities than intended, increasing the risk of unintended external access, filesystem exposure, or abuse if the skill or its dependencies are compromised.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This code hard-codes locale="zh-CN" for all browsing sessions, which imposes a specific language/locale behavior on every crawl. Under the policy, forcing a locale without user opt-in or clear region-specific justification is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script launches headless Chromium with --no-sandbox and --disable-setuid-sandbox by default unless an environment variable explicitly disables that behavior. Because this skill renders arbitrary public web pages with active client-side content, a browser compromise would have fewer containment boundaries and could more directly impact the host running the skill.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This code hard-codes the Playwright browser locale to "zh-CN", which can force a specific language/locale behavior regardless of user preference. The policy allows locale constraints only when the user is offered a choice or the restriction is clearly documented and justified, neither of which is present in this file.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The Playwright browser context is created with locale="zh-CN", which hard-codes a specific locale. This is a natural-language/locale policy concern because the script does not offer the user any choice or indicate that locale restriction is required for a region-specific purpose.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The browser context in the main discovery flow also sets locale="zh-CN", again enforcing a specific locale. Because there is no user-facing opt-in or documented rationale in this file, this appears to violate the language/locale policy requirement.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The markdown explains that the skill captures rendered pages, creates local HTML snapshots, and packages them for reuse, which can affect user data handling and system storage. While it mentions public websites and terms-of-service compliance later, it does not clearly warn users near the workflow description that captured content will be stored locally and included in the generated archive.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

The argparse description says the tool fetches a rendered snapshot for 'one URL,' which implies the supplied URL itself is what gets captured. In practice, page.goto may land on a different final URL after redirects, and the script snapshots page.url after that redirect chain, so the documentation does not accurately describe the actual target of capture.

Content

No source excerpt is available for this finding.