T09 · Insecure Skill Coding Practices
- Location
scripts/purple_highlight_screenshot.py:112- Finding
Unrestricted URL Navigation Enables Server-Side Request Forgery and Local Resource Access
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This appears to be a real DOCX research workflow, but it should be reviewed because it can open arbitrary citation URLs in a browser and send page or research text to model tooling without strong privacy or destination controls.
Install only if you are comfortable with a research pipeline that uses external search, model calls, browser automation, and DOCX file mutation. Before using screenshot verification, review the footnote URLs, avoid private or authenticated pages, and run it in a network-restricted environment if possible. Treat LLM-selected highlights as candidate evidence that still needs human review.
scripts/purple_highlight_screenshot.py:112Unrestricted URL Navigation Enables Server-Side Request Forgery and Local Resource Access
scripts/purple_highlight_screenshot.py:29Untrusted Webpage Content Is Inserted Directly into an LLM Instruction Prompt
The skill’s screenshot-verification stage implies network/browser access and external tool execution, but the top-level metadata does not declare those permissions. In environments that expect accurate declarations, this creates a consent and containment gap: a user may install a seemingly documentation-focused skill that can actually browse, fetch URLs, and invoke external model tooling.
The skill’s screenshot-verification stage implies network/browser access and external tool execution, but the top-level metadata does not declare those permissions. In environments that expect accurate declarations, this creates a consent and containment gap: a user may install a seemingly documentation-focused skill that can actually browse, fetch URLs, and invoke external model tooling.
The skill’s screenshot-verification stage implies network/browser access and external tool execution, but the top-level metadata does not declare those permissions. In environments that expect accurate declarations, this creates a consent and containment gap: a user may install a seemingly documentation-focused skill that can actually browse, fetch URLs, and invoke external model tooling.
The skill’s screenshot-verification stage implies network/browser access and external tool execution, but the top-level metadata does not declare those permissions. In environments that expect accurate declarations, this creates a consent and containment gap: a user may install a seemingly documentation-focused skill that can actually browse, fetch URLs, and invoke external model tooling.
The skill’s screenshot-verification stage implies network/browser access and external tool execution, but the top-level metadata does not declare those permissions. In environments that expect accurate declarations, this creates a consent and containment gap: a user may install a seemingly documentation-focused skill that can actually browse, fetch URLs, and invoke external model tooling.
The skill’s screenshot-verification stage implies network/browser access and external tool execution, but the top-level metadata does not declare those permissions. In environments that expect accurate declarations, this creates a consent and containment gap: a user may install a seemingly documentation-focused skill that can actually browse, fetch URLs, and invoke external model tooling.
The skill’s screenshot-verification stage implies network/browser access and external tool execution, but the top-level metadata does not declare those permissions. In environments that expect accurate declarations, this creates a consent and containment gap: a user may install a seemingly documentation-focused skill that can actually browse, fetch URLs, and invoke external model tooling.
The skill’s screenshot-verification stage implies network/browser access and external tool execution, but the top-level metadata does not declare those permissions. In environments that expect accurate declarations, this creates a consent and containment gap: a user may install a seemingly documentation-focused skill that can actually browse, fetch URLs, and invoke external model tooling.
The skill’s screenshot-verification stage implies network/browser access and external tool execution, but the top-level metadata does not declare those permissions. In environments that expect accurate declarations, this creates a consent and containment gap: a user may install a seemingly documentation-focused skill that can actually browse, fetch URLs, and invoke external model tooling.
The skill specifies extensive capabilities including shell execution, filesystem reads/writes, network access, browser automation, and external tool invocation, but it does not declare an explicit tool/permission scope. That increases the risk of overbroad execution in hosts that rely on metadata for containment, and makes it harder for reviewers or users to understand the real attack surface before installation.
The skill documentation presents the workflow, inputs, and operating instructions in Chinese and specifies Chinese claim extraction and wording rules later in the file, but does not indicate that users may choose another language. This creates a locale/language constraint without explicit opt-in, which matches the policy-violation category for forced language behavior.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
if ref_docx:
args += ['--reference-doc', ref_docx]
subprocess.run(args, check=True)
print(docx_path)
The grade field description is written entirely in Chinese (A=高亮有效...) inside an otherwise English schema. This introduces a locale-specific requirement or expectation without offering language choice or documenting why a Chinese-only description is necessary.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def _call_llm(prompt: str) -> list | None:
"""Execute openclaw infer model run and return parsed output, or None on failure."""
try:
proc = subprocess.run(
['openclaw', 'infer', 'model', 'run', '--prompt', prompt, '--json'],
capture_output=True, text=True, timeout=LLM_TIMEOUT,
)
The code sends the full claim text and research quote to an external LLM process without any user-facing disclosure, consent gate, or data-classification check. If those inputs contain unpublished research, proprietary text, or sensitive material, this can cause unintentional data exposure across a trust boundary, and the skill context makes this more concerning because it processes sourced research content as part of a document pipeline.
The function mutates research-derived keywords into claim-derived ones purely to satisfy a quota, even though the documented output says the source field indicates provenance. This creates integrity tampering in verification metadata: downstream citation checks may wrongly treat unsupported claim terms as claim-grounded evidence, weakening source verification and enabling false attribution.
The fallback path also rewrites non-claim keywords as claim-sourced, so when the LLM is unavailable the tool can silently fabricate claim provenance. In a chapter-generation and mandatory source-verification pipeline, this is especially risky because degraded-mode behavior can systematically mislead later verification steps and produce unsupported citations that appear validated.
This script hard-codes Chinese-language trigger patterns and later relies on a Chinese-specific footnote section marker and Chinese output labels, indicating the skill is designed around a fixed locale. Because the file provides no user opt-in, configuration, or justification for restricting behavior to Chinese content, it presents a natural-language locale policy concern.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# ── Call claim_keyword_extractor.py ───────────────────────────────────
try:
proc = subprocess.run(
[PYTHON, str(EXTRACTOR), claim, research_quote],
capture_output=True, text=True, timeout=90,
)
The module docstring states the script opens a URL, injects highlights, and takes a screenshot, but omits that it may also perform LLM-based analysis of extracted page text. This mismatch is security-relevant because operators may run the tool under false assumptions and unknowingly disclose data to an external processor.
The helper extracts full visible page text and forwards it to an external LLM subprocess when smart mode is enabled, even though the tool is presented as a highlighting/screenshot utility. This unnecessarily expands the skill's data exposure surface and could leak sensitive page contents from internal, authenticated, or proprietary sources.
Sending extracted page text to an external LLM subprocess without an explicit warning or consent mechanism is a genuine privacy and confidentiality issue. In this skill context, the browser may load arbitrary user-specified URLs, including pages containing internal research, paid content, or authenticated data, making undisclosed transmission more dangerous.
The subprocess call itself is not command-injection prone because it uses an argument list rather than a shell, but it launches an external LLM tool with page-derived content. In smart mode this creates a real data-exfiltration and trust-boundary issue, since arbitrary webpage text is sent to another program outside the documented screenshot-only behavior.
)
try:
proc = subprocess.run(
['openclaw', 'infer', 'model', 'run', '--prompt', prompt, '--json'],
capture_output=True, text=True, timeout=timeout_sec
)
The screenshot verifier expands its scope by sending source excerpts and titles into an LLM to decide what to search for, which is not necessary for basic screenshot capture. That means untrusted or sensitive research content is disclosed to another component and the verifier's behavior becomes susceptible to prompt-injection or manipulation by adversarial source text.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
)
try:
proc = subprocess.run(
['openclaw', 'infer', 'model', 'run', '--prompt', prompt, '--json'],
capture_output=True, text=True, timeout=30
)
No suspicious patterns detected.