Back to skill

Security audit

Facebook Group Monitor

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it claims, but it uses a persistent Facebook login profile and an unsandboxed browser with weak URL and file cleanup safeguards, so it should be reviewed carefully before use.

Install only if you are comfortable storing an authenticated Facebook browser session locally and saving screenshots of group content. Use a dedicated, private workspace, avoid shared screenshot folders, pass only real Facebook group URLs or IDs, and periodically delete .browser-data, .seen-posts.json, and screenshots when no longer needed. This should not be used on hosts where untrusted prompts or users can control the group URL or screenshot directory.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fb-group-monitor.py:363
Finding
Unrestricted Browser Navigation with Chromium Sandbox Disabled<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fb-group-monitor.py:55-59, 224-233, 363-380` **Vulnerability Type**: Browser-based server-side request forgery and unsafe browser configuration **Risk Level**: High ### Vulnerable Code ```python BROWSER_ARGS = [ "--disable-blink-features=AutomationControlled", "--no-sandbox", "--disable-dev-shm-usage", ] ``` ```python context = await p.chromium.launch_persistent_context( user_data_dir=str(BROWSER_DATA), headless=headless, viewport={"width": 1280, "height": 900}, user_agent=USER_AGENT, args=BROWSER_ARGS, ignore_default_args=["--enable-automation"], locale="vi-VN", timezone_id="Asia/Ho_Chi_Minh", ) ``` ```python group_url = args.group_url limit = args.limit take_screenshots = not args.no_shots shots_dir = Path(args.shots_dir).expanduser() shots_dir.mkdir(parents=True, exist_ok=True) if not group_url.startswith("http"): group_url = f"https://www.facebook.com/groups/{group_url}" if take_screenshots: cleanup_screenshots(shots_dir) async with async_playwright() as p: context, stealth_fn = await create_browser_context(p, headless=True) page = context.pages[0] if context.pages else await context.new_page() if stealth_fn: await stealth_fn(page) try: await page.goto(group_url, wait_until="domcontentloaded") ``` ### Technical Analysis The `group_url` validation only tests whether the supplied string begins with `http`. It does not enforce HTTPS, an approved Facebook hostname, or a Facebook group path. Consequently, any attacker who can influence the command argument can direct the browser to arbitrary public, loopback, link-local, or private-network destinations. The screenshot routine falls back to capturing the full viewport when no Facebook feed element exists. Therefore, content returned by a non-Facebook endpoint may be written to a screenshot even when post extraction fails. Navigation may also trigger state-c ...[truncated 1914 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse URLs with `urllib.parse.urlsplit` rather than testing string prefixes. 2. Require the `https` scheme. 3. Allow only exact approved hostnames, such as `www.facebook.com`, rather than suffix or substring matching. 4. Require the path to begin with `/groups/` and reject embedded credentials and unexpected ports. 5. Resolve the destination and reject loopback, private, link-local, multicast, reserved, and unspecified IP addresses. 6. Validate every redirect target before following it. 7. Remove `--no-sandbox` and run Chromium as a non-root user with its normal sandbox enabled. 8. If disabling the browser sandbox is operationally unavoidable, place the entire process in a separately hardened container with restricted filesystem mounts, network egress controls, dropped Linux capabilities, and no access to host services. 9. Consider using a fresh context for scraping and storing only the minimum authentication state required for Facebook. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fb-group-monitor.py:197
Finding
Arbitrary Deletion of JPEG Files Through User-Controlled Screenshot Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fb-group-monitor.py:197-211, 278-280, 359-360, 522-536` **Vulnerability Type**: Insufficient path validation and unsafe file cleanup **Risk Level**: Medium ### Vulnerable Code ```python def cleanup_screenshots(shots_dir: Path): """Xóa screenshots cũ hơn TTL hoặc vượt MAX_SCREENSHOTS.""" cutoff = datetime.now() - timedelta(hours=SCREENSHOT_TTL_HOURS) files = sorted(shots_dir.glob("*.jpg"), key=lambda f: f.stat().st_mtime) removed = 0 for f in files: mtime = datetime.fromtimestamp(f.stat().st_mtime) if mtime < cutoff: f.unlink() removed += 1 files = sorted(shots_dir.glob("*.jpg"), key=lambda f: f.stat().st_mtime) while len(files) > MAX_SCREENSHOTS: files[0].unlink() files = files[1:] removed += 1 return removed ``` ```python async def cmd_clean_shots(args): shots_dir = Path(args.shots_dir).expanduser() shots_dir.mkdir(parents=True, exist_ok=True) removed = cleanup_screenshots(shots_dir) ``` ```python shots_dir = Path(args.shots_dir).expanduser() shots_dir.mkdir(parents=True, exist_ok=True) if not group_url.startswith("http"): group_url = f"https://www.facebook.com/groups/{group_url}" if take_screenshots: cleanup_screenshots(shots_dir) ``` ```python clean_p.add_argument( "--shots-dir", default=str(DEFAULT_SCREENSHOTS_DIR), help="Thư mục chứa screenshots (default: script_dir/screenshots)" ) scrape_p.add_argument( "--shots-dir", default=str(DEFAULT_SCREENSHOTS_DIR), help="Thư mục lưu screenshots — NÊN trỏ vào workspace để image tool đọc được. " "VD: ~/.openclaw/workspace-daily-digest/temp-screenshots" ) ``` ### Technical Analysis Both `scrape` and `clean-shots` accept an arbitrary filesystem path through `--shots-dir`. The path is expanded and created but is not constrained to the Skill directory or an approved workspace directory. The cleanup ...[truncated 1376 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve the requested path with `Path.resolve()` and enforce containment beneath a dedicated, approved screenshot root. 2. Reject filesystem roots, home directories, shared media directories, and paths outside the configured workspace. 3. Create and use a Skill-specific subdirectory rather than cleaning a user-selected directory directly. 4. Delete only files matching the exact generated filename pattern, such as `feed_[0-9a-f]{8}_[0-9]+.jpg`. 5. Maintain a manifest of screenshots created by the Skill and clean only manifest-listed files. 6. Refuse cleanup when unexpected files are present, or require an explicit confirmation for a standalone destructive cleanup command. 7. Use safe ownership and symlink checks before deletion, including `lstat()` or equivalent protections where appropriate. 8. Add tests proving that unrelated JPEG files are preserved. ]]>

T08 · Insecure Dependencies

Note
Location
references/SETUP.md:17
Finding
Unpinned Python and Browser Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `references/SETUP.md:17-24` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Low ### Vulnerable Code ```bash # Install dependencies pip install playwright playwright-stealth ``` ```bash playwright install chromium ``` ### Technical Analysis The installation instructions request the latest available releases of `playwright` and `playwright-stealth` without version constraints or package hashes. The Chromium revision is likewise selected by whichever Playwright release happens to be installed. This does not use a suspicious custom package source, dependency name, or remote shell pipeline. However, it makes builds non-reproducible and causes future installations to trust package and browser artifacts that were not part of the reviewed project state. A compromised, malicious, or unexpectedly incompatible future release could execute code during installation or runtime. ### Attack Path 1. A user follows the documented setup instructions at a later date. 2. Package resolution selects the latest releases available from the configured Python package index. 3. A compromised or malicious release, if present, is downloaded and installed without hash verification. 4. Installation hooks or imported package code execute with the privileges of the user performing setup or running the Skill. 5. The corresponding Chromium binary is also downloaded according to the dynamically selected Playwright version. ### Impact Assessment A compromised dependency could execute code with the privileges of the account installing or running the Skill. This could expose the persistent Facebook session, screenshots, deduplication data, and other files accessible to that account. No currently malicious package or unsafe package repository was identified in the reviewed files. The finding concerns supply-chain integrity and reproducibility rather than evidence of an embedded malicious dependency. ...[truncated 3 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin reviewed versions of `playwright`, `playwright-stealth`, and Pillow in a requirements or lock file. 2. Record and verify package hashes, for example by using `pip install --require-hashes`. 3. Install only from an explicitly configured, trusted package index. 4. Pin the Playwright release so that the corresponding Chromium revision is deterministic. 5. Retain the lock file in version control and update dependencies through a documented review process. 6. Run dependency vulnerability and provenance checks before accepting updates. 7. Perform installation in an isolated virtual environment under a non-privileged account. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (6)

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill performs file read/write operations for persistent browser session data, dedup state, and screenshots, but it does not declare an explicit tool scope or permissions boundary. That makes the effective capabilities less visible to reviewers and orchestrators, increasing the chance of overbroad file access or unintended data exposure from stored Facebook session artifacts and scraped content.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger phrases are broad enough to match generic requests like 'monitor group' or 'Facebook marketplace,' which can cause the skill to activate when the user did not specifically intend browser automation, scraping, persistent login usage, or screenshot capture. In this context, unintended invocation is more dangerous because the skill accesses authenticated Facebook content and stores scraped images and state on disk.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill prominently describes persistent login storage and stitched screenshots of Facebook group feeds, but it does not present a clear user-facing warning about the privacy and security implications of storing authenticated session data and capturing potentially sensitive third-party content. This omission increases the risk of users enabling the skill without understanding that private group posts, images, and session artifacts may be retained locally and exposed to other tools or operators.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The setup guide explicitly instructs users to create and retain a persistent authenticated Facebook session in a local browser profile for weeks to months, but it does not warn about the sensitivity of that stored session or how compromise of the local profile could enable account takeover. In the context of a browser automation skill that uses stealth mode and persistent login, this increases the risk of unauthorized access if the host, workspace, backups, or shared filesystem are exposed.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill launches a persistent Playwright browser context backed by `.browser-data`, which retains Facebook authentication state on disk, and it separately stores scraped state in `.seen-posts.json` plus optional screenshots on the filesystem. In an agent/tooling environment, these retained artifacts can expose session cookies, account access, and scraped group content to other local users, later runs, or workspace-accessible tools without any explicit user warning, consent flow, or protection boundary.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The skill hard-codes `locale="vi-VN"` and `timezone_id="Asia/Ho_Chi_Minh"`, which imposes a specific language/locale behavior on all users. This matches the policy-violation category because there is no opt-in, configuration option, or documented justification that the skill is intended only for a Vietnam-specific context.

Static analysis

No suspicious patterns detected.