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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/frameo_client.py (reported line 124)May include surrounding context.

python
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]

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · references/adb-commands.md (reported line 35)May include surrounding context.

Delete Photo

bash
adb shell rm /sdcard/Frameo/photo.jpg

List Photos

Missing User Warnings

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · references/adb-commands.md (reported line 126)May include surrounding context.

md
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/

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/frameo_client.py (reported line 113)May include surrounding context.

python
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.")

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.