Back to skill

Security audit

Browser Proof

Security checks for vulnerabilities and agentic risk

Overview

This skill coherently creates browser QA evidence files; it has validation and report-sanitization weaknesses to watch, but no hidden persistence, exfiltration, or destructive behavior.

Reasonable to install for browser QA evidence workflows. Use it with manifests and browser-observed text you trust, keep generated bundles in a project workspace, avoid recording secrets or cookies, and review generated Markdown before publishing it to GitHub, Slack, Linear, or a release channel.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/check_browser_proof_bundle.py:30
Finding

Repository Boundary Bypass Through Artifact Path Traversal

Content
View full analysis

Vulnerability Details

File Location: scripts/check_browser_proof_bundle.py, lines 30–31 and 84–90
Vulnerability Type: Path traversal and filesystem existence disclosure
Risk Level: Medium

Vulnerable Code:

python
def is_absolute_or_private(path_text: str) -> bool:
    return path_text.startswith("/") or path_text.startswith("~") or ":\\" in path_text or path_text.startswith("file://")
python
for field in ARTIFACT_FIELDS:
    artifact_path = artifacts.get(field, "").strip()
    if not artifact_path:
        continue
    if is_absolute_or_private(artifact_path):
        add_finding(findings, "error", "absolute-artifact-path", f"{field} uses an absolute or private path.", step_id)
        continue
    candidate = repo_root / artifact_path
    if not candidate.exists():
        add_finding(findings, "warning", "missing-artifact", f"{field} path does not exist under repo root: {artifact_path}", step_id)

Technical Analysis

The validator rejects several forms of absolute path but does not reject parent-directory components such as ... It also does not resolve the constructed path and verify that the result remains beneath the resolved repository root.

Consequently, an artifact value such as ../../etc/passwd is treated as a relative path and joined to repo_root. Filesystem path resolution can then escape the intended repository boundary. The code only calls Path.exists() and does not read or return file contents, so this is not arbitrary file disclosure. It nevertheless creates a local filesystem existence oracle and can incorrectly accept an artifact located outside the evidence repository as valid.

Symbolic links inside the repository can cause a similar boundary escape unless containment is checked after canonical resolution.

Attack Path

  1. An attacker supplies or modifies a browser-proof manifest.
  2. The attacker sets an artifact field such as `screenshot ...[truncated 1077 chars]
Remediation
View remediation

Remediation Suggestions

Resolve both paths and enforce containment before performing any filesystem operation:

python
repo_root = Path(args.repo_root).expanduser().resolve()
candidate = (repo_root / artifact_path).resolve()

try:
    candidate.relative_to(repo_root)
except ValueError:
    add_finding(
        findings,
        "error",
        "artifact-path-traversal",
        f"{field} escapes the repository root.",
        step_id,
    )
    continue

if not candidate.exists():
    add_finding(
        findings,
        "warning",
        "missing-artifact",
        f"{field} path does not exist under repo root: {artifact_path}",
        step_id,
    )

Additionally:

  • Explicitly reject .. path components as an early validation measure.
  • Perform containment checks on resolved paths to address symbolic-link escapes.
  • Require artifacts to be regular files where appropriate.
  • Add tests for traversal paths, symbolic links, absolute paths, Windows path forms, and valid nested repository paths.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/render_browser_proof_report.py:44
Finding

Unescaped Manifest Content Allows Markdown and HTML Injection

Content
View full analysis

Vulnerability Details

File Location: scripts/render_browser_proof_report.py, lines 44–79
Vulnerability Type: Markdown and raw HTML content injection
Risk Level: Medium

Vulnerable Code:

python
lines = [
    "# Browser Proof Report",
    "",
    f"- Session: `{session.get('session_id', '')}`",
    f"- App: {session.get('app', '')}",
    f"- Goal: {session.get('goal', '')}",
    f"- Base URL: {session.get('base_url', '') or 'n/a'}",
    f"- Run context: {session.get('run_context', '') or 'n/a'}",
    f"- Environment: {session.get('environment', '') or 'n/a'}",
    f"- Surfaces: {', '.join(session.get('surfaces', [])) or 'n/a'}",
    f"- Overall status: **{summary.get('status', 'unknown')}**",
    f"- Passed / Failed / Blocked: {summary.get('passed_steps', 0)} / {summary.get('failed_steps', 0)} / {summary.get('blocked_steps', 0)}",
    "",
    "## Steps",
    "",
]

for index, step in enumerate(steps, start=1):
    lines.append(f"### {index}. {step.get('step_id', f'step-{index}')}")
    lines.append(f"- Surface: {step.get('surface', '') or 'n/a'}")
    lines.append(f"- Status: **{step.get('status', 'unknown')}**")
    lines.append(f"- Action: {step.get('action', '')}")
    lines.append(f"- Expected: {step.get('expected', '')}")
    lines.append(f"- Actual: {step.get('actual', '')}")
    if step.get("note"):
        lines.append(f"- Note: {step['note']}")
    issue_keys = step.get("issue_keys", [])
    if issue_keys:
        lines.append(f"- Issue keys: {', '.join(issue_keys)}")
    artifact_parts = format_artifacts(step.get("artifacts", {}))
    if artifact_parts:
        lines.append(f"- Artifacts: {'; '.join(artifact_parts)}")
    lines.append("")

Technical Analysis

Manifest-controlled values are interpolated directly into a Markdown document without escaping Markdown metacharacters, embedded line breaks, raw HTML, or link and image syntax. Several f ...[truncated 1986 chars]

Remediation
View remediation

Remediation Suggestions

  • Treat every manifest value as untrusted.
  • Escape Markdown metacharacters, including backslashes, backticks, brackets, parentheses, asterisks, underscores, and heading markers.
  • Normalize or encode embedded carriage returns and line feeds so values cannot create new Markdown blocks.
  • Reject or encode raw HTML.
  • Apply a strict URL policy if links or images are intentionally supported.
  • Prefer fenced plain-text blocks for long browser-observed fields such as expected and actual behavior, while safely handling embedded fence delimiters.
  • Where the report is converted to HTML, use a Markdown implementation configured to disable raw HTML and sanitize generated HTML with an allowlist.
  • Add tests covering newlines, backticks, headings, image syntax, links, raw HTML, and mixed Markdown payloads.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (2)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill instructs the agent to run local Python scripts that create and modify files, but it declares no explicit tool scope such as permissions or allowed-tools. That mismatch weakens the platform's ability to constrain file read/write behavior and can lead to broader-than-expected filesystem access if the skill is invoked in a permissive environment.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

This Python code writes a generated markdown report containing session metadata, step details, notes, issue keys, and artifact references to disk. While file output is part of the script's purpose, the code does not include any warning, comment, or user-facing notice that the resulting report may contain sensitive browsing or system context data.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.