Back to skill

Security audit

bluesky-skill

Security checks for vulnerabilities and agentic risk

Overview

This Bluesky tool is mostly transparent, but it gives an agent broad control over an account and private DMs while caching a reusable session token in a weakly protected local file.

Install only if you want an agent to have broad Bluesky account authority, including reading and sending DMs, posting, deleting posts, changing profile details, and changing social relationships. Prefer a dedicated app password, avoid enabling DM access unless needed, run the tool in an isolated environment with pinned dependencies, and protect or delete ~/.bsky_session.json when done or after revoking access.

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)

T08 · Insecure Dependencies

Warning
Location
SKILL.md:17
Finding
Unpinned Third-Party Dependencies Create a Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 17-20 **Vulnerability Type**: Unpinned and unverifiable third-party dependencies **Risk Level**: Medium ### Vulnerable Code ```markdown Install dependencies: ```bash pip install atproto python-dotenv ``` ``` ### Technical Analysis The installation instructions fetch the latest available versions of `atproto` and `python-dotenv` without exact version constraints, package hashes, a lockfile, or an explicitly trusted package index. Consequently, installations are not reproducible, and the code ultimately executed may differ from the version originally reviewed. These dependencies run in the same Python process as the Skill and therefore inherit access to the Bluesky handle, app password, exported session, direct messages, selected media, and all account-management operations. A compromised package release, dependency takeover, or substituted package source could access this sensitive data during installation or import. No evidence indicates that the currently named packages are malicious. The vulnerability is the absence of controls preventing a future or substituted release from being installed. ### Attack Path 1. An attacker compromises a dependency release or causes the installer to resolve a malicious package through an untrusted package source. 2. A user follows the documented `pip install atproto python-dotenv` command. 3. The unreviewed package executes installation-time or import-time code. 4. When the Skill runs, the package can read environment variables and the local session cache. 5. The attacker can exfiltrate credentials or use the authenticated client context to access and modify the Bluesky account. ### Impact Assessment Successful exploitation could expose the Bluesky app password, reusable session tokens, private direct messages, posts, uploaded media, and account metadata. Because the Skill supports posting, deletion, follows, blocks, profile changes, notification ...[truncated 220 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct dependency to a reviewed exact version, for example: ```text atproto==<reviewed-version> python-dotenv==<reviewed-version> ``` 2. Generate and commit a lockfile containing resolved transitive dependencies and cryptographic hashes. 3. Install with hash verification, such as `pip install --require-hashes -r requirements.txt`. 4. Use an explicitly configured, trusted Python package index and disable unintended extra indexes. 5. Run dependency vulnerability and provenance checks in CI. 6. Review dependency updates before modifying pinned versions. 7. Prefer an isolated virtual environment with only the packages required by this Skill. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/bsky.py:35
Finding
Reusable Authentication Session Is Stored Without Enforced Access Controls<![CDATA[ ## Vulnerability Details **File Location**: `scripts/bsky.py`, lines 16 and 35-40 **Vulnerability Type**: Insecure plaintext storage of sensitive authentication material **Risk Level**: Medium ### Vulnerable Code ```python SESSION_FILE = Path.home() / ".bsky_session.json" ``` ```python def _save_session(event, session): """Save session to disk on change.""" try: SESSION_FILE.write_text(json.dumps({"session_string": session.export()})) except Exception: pass ``` ### Technical Analysis The application exports a reusable authenticated session and writes it as plaintext JSON under the user's home directory. `Path.write_text()` uses the process's current umask when creating the file, but the code does not explicitly enforce owner-only permissions such as `0600`. If the file already exists, its existing permissions remain in effect. The implementation also does not verify that the destination is a regular file owned by the current user or reject symbolic links. On a system where an attacker can prepare the path, this could redirect the session write to another accessible location. Silently suppressing every write exception further prevents users from detecting failures in session-storage protections. Session caching is consistent with the declared functionality and is documented in `SKILL.md`; however, storing a bearer-like reusable session without explicit access controls exceeds the minimum safe handling requirements for authentication secrets. ### Attack Path A local disclosure path is: 1. The victim runs any Skill command and authenticates to Bluesky. 2. The session-change callback exports the authenticated session to `~/.bsky_session.json`. 3. The file is created or retained with permissions that permit another local principal or compromised process to read it. 4. The attacker copies the `session_string`. 5. The attacker supplies that session to a compatible AT Protocol client. 6. The attacker acts within the perm ...[truncated 1089 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer an operating-system credential store or keyring instead of a plaintext session file. 2. If a file must be used, create it atomically with owner-only permissions (`0600`) rather than relying on the process umask. 3. Verify that the destination is a regular file owned by the current user, and reject symbolic links. 4. Reapply and verify restrictive permissions whenever an existing cache is opened or replaced. 5. Write to a securely created temporary file in the same directory, flush it, and atomically replace the destination. 6. Avoid suppressing all storage exceptions. Return a sanitized warning that session caching failed without printing the session value. 7. Document session revocation and deletion procedures, and minimize session lifetime where supported. 8. Never include the exported session in logs, command output, diagnostics, or exception messages. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (12)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
Auth errors: `{"error": "AUTH_ERROR", "type": "<TYPE>", "message": "..."}` with exit code 1.

1. **SESSION_CORRUPT** → `rm ~/.bsky_session.json` and retry
2. **MISSING_ENV** → Ensure `.env` has `BLUESKY_HANDLE` and `BLUESKY_APP_PASSWORD`
3. **INVALID_CREDENTIALS** → Handle: `user.bsky.social`, App password: `xxxx-xxxx-xxxx-xxxx` (19 chars)
4. **NETWORK** → Retry up to 3 times with 10s delay
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).

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding
The skill uses 'env' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Credential Access

High
Category
Privilege Escalation
Content
password = os.environ.get("BLUESKY_APP_PASSWORD")

    if not handle or not password:
        error_json("AUTH_ERROR", "Set BLUESKY_HANDLE and BLUESKY_APP_PASSWORD in .env", "MISSING_ENV")

    client = Client()
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Session Persistence

Medium
Category
Rogue Agent
Content
Manage a Bluesky (bsky) account — posting, replies, likes, reposts, follows,
  blocks, mutes, search, timeline, threads, notifications, DMs, and profile
  updates via the AT Protocol.
allowed-tools: Bash Read Edit Write Glob Grep
metadata:
  openclaw:
    requires:
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The documented commands include destructive or state-changing actions such as delete, block, mute, and marking notifications as read, but there is no warning that these actions are irreversible or can materially alter account state and social relationships. In an autonomous or semi-autonomous workflow, this increases the risk of accidental account changes, data loss, or user-impacting actions without adequate confirmation.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill explicitly supports reading and sending direct messages but provides no privacy warning, consent requirement, or guidance to limit access to only what is necessary. In an agent setting, this can normalize access to private communications and lead to unnecessary exposure, processing, or disclosure of sensitive personal or business information.

Session Persistence

Medium
Category
Rogue Agent
Content
- **Handles**: Always pass handles **without** the `@` prefix — use `user.bsky.social`, not `@user.bsky.social`.
- **URIs**: Every post has an AT Protocol URI (`at://did:plc:abc/app.bsky.feed.post/xyz`). Extract from the `uri` field in JSON. Used as arguments for like, reply, repost, thread, get, delete.
- **Rich text**: @mentions, #hashtags, URLs in post text are auto-converted to links. Write naturally.
- **Character limit**: 300 graphemes per post.
- **Unlike/unrepost**: Pass the **post URI**, not the like/repost record URI. Auto-resolved internally.
- **Reply threading**: `--reply-to <uri>` auto-resolves the thread root.
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The code persists an exported session string to ~/.bsky_session.json without setting restrictive file permissions or warning the user. If another local user, backup system, or adjacent process can read that file, they may be able to hijack the authenticated Bluesky session and act as the user.

Session Persistence

Medium
Category
Rogue Agent
Content
client = Client()

    def _save_session(event, session):
        """Save session to disk on change."""
        try:
            SESSION_FILE.write_text(json.dumps({"session_string": session.export()}))
        except Exception:
Confidence
91% confidence
Finding
The tool intentionally persists login session material to disk for reuse, which increases the attack surface beyond in-memory authentication. A stolen or improperly protected session file can enable account access without needing the original password, especially in shared or compromised environments.

Cloud Storage Exfiltration

Medium
Category
Data Exfiltration
Content
img_path = Path(img_file)
            if not img_path.exists():
                error_json("FILE_NOT_FOUND", f"Image not found: {img_file}")
            upload = client.upload_blob(img_path.read_bytes())
            alt = alts[i] if i < len(alts) else ""
            images.append(models.AppBskyEmbedImages.Image(alt=alt, image=upload.blob))
        img_embed = models.AppBskyEmbedImages.Main(images=images)
Confidence
55% confidence
Finding
Data is uploaded to cloud storage (S3 / GCS / Azure Blob). This may be a legitimate backup or exfiltration to an external bucket. Manual review is recommended.

Cloud Storage Exfiltration

Medium
Category
Data Exfiltration
Content
img_path = Path(img_file)
            if not img_path.exists():
                error_json("FILE_NOT_FOUND", f"Image not found: {img_file}")
            upload = client.upload_blob(img_path.read_bytes())
            alt = alts[i] if i < len(alts) else ""
            images.append(models.AppBskyEmbedImages.Image(alt=alt, image=upload.blob))
        img_embed = models.AppBskyEmbedImages.Main(images=images)
Confidence
55% confidence
Finding
Data is uploaded to cloud storage (S3 / GCS / Azure Blob). This may be a legitimate backup or exfiltration to an external bucket. Manual review is recommended.

Missing User Warnings

Medium
Confidence
80% confidence
Finding
The dm-send command sends user-provided message text to the Bluesky chat service, which is a network operation involving private communication data. While sending is the command's purpose, the file provides no user-facing warning or disclosure about the privacy-sensitive nature of DM transmission or handling.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
SKILL.md:30