Back to skill

Security audit

Frameo Photo Frame Control

Security checks for vulnerabilities and agentic risk

Overview

This Frameo control skill appears purpose-aligned, but it needs Review because it handles account tokens and wireless device control without enough safety guardrails.

Install only if you control the Frameo account, frame, and network. Treat Frameo tokens and ~/.frameo_token as secrets, use restrictive permissions or a credential store, avoid sharing logs, disable wireless ADB when finished, and review any rm or wildcard ADB commands carefully before running them.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/frameo_client.py:87
Finding
OAuth Access and Refresh Tokens Stored Without Explicit Owner-Only Permissions## Vulnerability Details **File Location**: `scripts/frameo_client.py:87-93`, `scripts/frameo_client.py:99-111`; related insecure setup guidance at `SKILL.md:27-31` **Vulnerability Type**: Sensitive credential storage with insufficient permission enforcement **Risk Level**: Medium ### Vulnerable Code ```python token_data = r.json() with open(TOKEN_FILE, "w") as f: json.dump(token_data, f) print("Login successful! Token cached.") return token_data["access_token"] ``` ```python def refresh_token(refresh_tok): data = { "client_id": "frameo-app", "grant_type": "refresh_token", "refresh_token": refresh_tok, } r = requests.post(AUTH_URL, data=data) if r.status_code != 200: return None token_data = r.json() with open(TOKEN_FILE, "w") as f: json.dump(token_data, f) return token_data["access_token"] ``` The associated setup documentation also creates the credential file without explicitly setting restrictive permissions: ```bash echo '{"access_token": "YOUR_TOKEN"}' > ~/.frameo_token ``` ### Technical Analysis The login and token-refresh flows write the complete OAuth response to `~/.frameo_token`. This response can include both a short-lived access token and a longer-lived refresh token. The file is opened using the process's current umask, without explicitly enforcing mode `0600`. On systems with a permissive or misconfigured umask, the resulting file may be readable by other local users. An access token permits authenticated Frameo API requests until expiration. A refresh token is more sensitive because it can be exchanged for new access tokens and may extend unauthorized access significantly. The network destinations used by the authentication flow are fixed HTTPS endpoints under the official `frameo.net` domain. No transmission to an unknown or attacker-controlled host was found. The vulnerability concerns local token s ...[truncated 1260 chars]
Remediation
## Remediation Suggestions 1. Create the token file atomically with owner-only permissions: ```python import os flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC fd = os.open(TOKEN_FILE, flags, 0o600) with os.fdopen(fd, "w") as f: json.dump(token_data, f) os.chmod(TOKEN_FILE, 0o600) ``` 2. Prefer an operating-system credential store, such as macOS Keychain, Windows Credential Manager, or the Linux Secret Service, instead of a plaintext JSON file. 3. Before reading an existing token file, verify that it is a regular file, owned by the current user, and not accessible by group or other users. 4. Avoid following symbolic links when creating or replacing the token file, and use atomic replacement to prevent race conditions. 5. Replace the documentation's shell redirection with an owner-only creation procedure, for example: ```bash install -m 600 /dev/null ~/.frameo_token printf '%s\n' '{"access_token": "YOUR_TOKEN"}' > ~/.frameo_token chmod 600 ~/.frameo_token ``` 6. Document token revocation and deletion procedures for lost or compromised systems.

T08 · Insecure Dependencies

Note
Location
SKILL.md:25
Finding
Unpinned and Unnecessary Runtime Dependency Installation## Vulnerability Details **File Location**: `SKILL.md:25-30` **Vulnerability Type**: Unpinned third-party dependencies and excessive dependency scope **Risk Level**: Low ### Vulnerable Code ```markdown ### Cloud API Setup 1. Install: `pip3 install requests pillow` 2. Get Bearer token from Frameo app traffic (Proxyman/Charles) 3. Save token: `echo '{"access_token": "YOUR_TOKEN"}' > ~/.frameo_token` 4. Run: `python3 scripts/frameo_client.py --frames` ``` ### Technical Analysis The setup instructions install `requests` and `pillow` directly from the package index without reviewed version constraints or cryptographic hashes. This makes installations non-reproducible and allows future package releases to be selected without review. The included `scripts/frameo_client.py` imports `requests` but does not import or otherwise use `pillow`. Installing Pillow therefore expands the dependency and vulnerability surface beyond what is necessary for the declared client implementation. No evidence was found that either package name is intentionally typosquatted, that the project configures an unsafe package index, or that the currently named packages are malicious. The risk arises from unconstrained future dependency resolution and installation of an unused package. ### Attack Path 1. A user follows the documented `pip3 install requests pillow` command. 2. `pip` resolves the newest packages available from its configured index rather than reviewed versions. 3. A compromised package release, compromised configured index, or maliciously altered dependency in the resolution chain is selected. 4. Package installation or a later import executes attacker-controlled code under the privileges of the user running the command. Exploitation depends on compromise of a package release, dependency, or package source; the audited project does not itself provide such a malicious package. ### Impact Assessment A compromised dependency can ...[truncated 430 chars]
Remediation
## Remediation Suggestions 1. Remove `pillow` unless image processing is implemented and demonstrably required. 2. Define dependencies in a reviewed requirements or lock file with exact versions. 3. Use cryptographic hashes, such as a hash-locked `requirements.txt` installed with `pip install --require-hashes`. 4. Regularly update pinned versions through a controlled dependency-review process and vulnerability scanning. 5. Install dependencies in an isolated virtual environment rather than the user's global Python environment. 6. Document the expected Python version and trusted package index to improve reproducibility. 7. Avoid running package installation as root or with elevated privileges.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (14)

Tainted flow: 'body' from os.environ.get (line 123, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
def register_client(token):
    body = {"deviceId": CLIENT_DEVICE_ID, "peerId": CLIENT_PEER_ID}
    r = requests.post(f"{BASE_API}/v1/managed-pairings/client",
                      headers=get_headers(token), json=body)
    return r.status_code in [200, 204]
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
### Delete Photo
```bash
adb shell rm /sdcard/Frameo/photo.jpg
```

### List Photos
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Missing User Warnings

High
Confidence
98% confidence
Finding
The automation example performs bulk deletion using a wildcard, which can irreversibly remove all matching photos from the Frameo directory. Because it is presented as a reusable script, users are especially likely to run it as-is, amplifying the chance of large-scale accidental data loss.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
adb connect $FRAME_IP:5555

# Clear old photos
adb shell rm /sdcard/Frameo/*.jpg

# Push today's photos
adb push $PHOTO_DIR/*.jpg /sdcard/Frameo/
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Credential Access

High
Category
Privilege Escalation
Content
return token
        # Fall back to access_token if available
        if "access_token" in token_data:
            print("Using cached access token.")
            return token_data["access_token"]
    if not FRAMEO_PASSWORD:
        print("ERROR: Set FRAMEO_PASSWORD in the script, then run --login first.")
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill instructs users to extract a live bearer token from app traffic and store it in a plaintext file under the home directory, but provides no warning that this is sensitive credential material. If that token is exposed through shell history, backups, local compromise, or overly broad file permissions, an attacker could access the associated Frameo account within the token lifetime and potentially any API actions permitted by that token.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The ADB setup directions enable wireless debugging on TCP port 5555 and remote device control, but omit any warning that exposing ADB over the network can allow unauthorized control of the frame if the network is untrusted or access controls are weak. Because this skill specifically promotes full-control ADB operations, the context increases the danger: a misconfigured setup could permit photo tampering, device manipulation, or broader abuse of the Android-based frame.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documented delete command permanently removes a photo from the device without any warning, confirmation step, or mention of backup/recovery. In a reference skill, users may copy-paste commands directly, so omission of data-loss warnings makes accidental destructive use more likely.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The document provides a complete OAuth2 password-grant login flow and explicitly advises readers to 'capture fresh token from Proxyman,' which normalizes interception and reuse of authentication tokens without any safety guidance. Even though this is documentation rather than executable code, it exposes sensitive operational details and encourages unsafe credential and token handling that could lead to account compromise or unauthorized API access if copied into agent behavior or shared broadly.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script stores token data, including refresh tokens, in a predictable file under the user's home directory without setting restrictive permissions or warning the user. If another local user, process, backup system, or malware can read that file, long-lived authentication material could be stolen and reused to access the account.

Intent-Code Divergence

Medium
Confidence
89% confidence
Finding
The inline message encourages capturing traffic on a local device, which moves beyond the implemented client behavior into network interception guidance. In a shared skill or automation context, this can normalize or prompt intrusive actions against devices on the local network and increases suspicion because it suggests reverse-engineering and surveillance steps not necessary for normal operation.

Missing User Warnings

Low
Confidence
81% confidence
Finding
The reboot command interrupts device operation immediately and can disrupt users or ongoing workflows, yet the documentation provides no warning about service interruption. While not inherently malicious, it can still cause unintended downtime if executed casually.

Missing User Warnings

Low
Confidence
82% confidence
Finding
FRAMEO_EMAIL and FRAMEO_PASSWORD are pulled from environment variables and then transmitted for authentication, which is sensitive credential handling. The file contains setup comments, but the CLI/help text does not clearly warn users that credentials are being sourced from environment variables and used for network authentication.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The headers force client_preferred_language and Accept-Language to en-US, which is a natural-language locale constraint. There is no opt-in, configuration flag, or explanation that this skill is intentionally region-specific, so it conflicts with the policy against forcing a specific language without user choice.

Static analysis

No suspicious patterns detected.