Back to skill

Security audit

DeviantArt Post

Security checks for vulnerabilities and agentic risk

Overview

The skill appears intended to post to DeviantArt, but it needs review because it stores reusable OAuth tokens without enforcing private file permissions and its documented preview/gallery controls do not match the code.

Review before installing. Use a dedicated DeviantArt developer app with only the scopes you need, avoid user.manage unless posting journals or statuses, and treat ~/.openclaw/deviantart-token.json as a sensitive login secret. The publisher should fix private token-file permissions and repair the artwork script's --dry-run and --gallery-name handling before this is used for routine posting.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/deviantart_common.py:46
Finding
OAuth Tokens Are Stored Without Restrictive File Permissions## Vulnerability Details **File Location**: `scripts/deviantart_common.py`, lines 46–48 **Vulnerability Type**: Insecure storage of OAuth access and refresh tokens **Risk Level**: Medium ### Vulnerable Code ```python def save_json(path: Path, data: Dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") ``` This function is used to persist OAuth tokens at: - `scripts/deviantart_auth.py:135` - `scripts/deviantart_common.py:137` ```python save_json(TOKEN_PATH, token) ``` ```python save_json(TOKEN_PATH, refreshed) ``` ### Technical Analysis The shared JSON-writing function does not explicitly restrict the permissions of either the token file or its parent directory. Newly created files therefore inherit permissions determined by the process environment and system umask. For example, a Unix-like environment with a permissive umask may create the token file with permissions that allow other local users to read it. The stored JSON includes OAuth access and refresh tokens. A refresh token is particularly sensitive because it may allow an attacker to obtain new access tokens until it expires or is revoked. The flagged network transmission itself is necessary for the declared functionality: OAuth codes, client credentials, refresh tokens, access tokens, post content, and selected artwork are sent only to hard-coded HTTPS DeviantArt endpoints. No unrelated exfiltration was identified. The vulnerability concerns local token storage rather than unauthorized network transmission. ### Attack Path 1. A victim runs `scripts/deviantart_auth.py` and successfully authorizes the DeviantArt application. 2. The returned access and refresh tokens are written to `~/.openclaw/deviantart-token.json`, or to the path selected through `DEVIANTART_TOKEN_PATH`. 3. The file is created without explicit owner-only permissions. 4. If inherited filesystem permissions permit ...[truncated 816 chars]
Remediation
## Remediation Suggestions 1. Create the credential directory with owner-only permissions on supported systems: ```python path.parent.mkdir(parents=True, exist_ok=True, mode=0o700) os.chmod(path.parent, 0o700) ``` 2. Write sensitive JSON through a securely created temporary file with mode `0600`, flush it to disk, and atomically replace the destination. 3. Explicitly enforce mode `0600` on both newly created and pre-existing token files: ```python os.chmod(path, 0o600) ``` 4. Separate general JSON persistence from secret persistence so non-sensitive files do not needlessly share credential-handling logic. 5. Prefer an operating-system credential manager or keychain for refresh tokens where available. 6. Apply equivalent protections to the application credentials file when it contains a `client_secret`. 7. Avoid including tokens or complete token endpoint response bodies in logs and exception messages.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (17)

Tainted flow: 'req' from os.environ.get (line 83, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req_headers.update(headers)
    req = urllib.request.Request(url, data=data, headers=req_headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            body = read_response_text(resp)
    except urllib.error.HTTPError as e:
        body = _decode_bytes(e.read(), e.headers.get("Content-Encoding") or "")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 83, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req_headers.update(headers)
    req = urllib.request.Request(url, data=data, headers=req_headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            body = read_response_text(resp)
    except urllib.error.HTTPError as e:
        body = _decode_bytes(e.read(), e.headers.get("Content-Encoding") or "")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The description presents a broader multi-capability DeviantArt skill covering authentication, artwork posting, journals, and status updates. The actual code chunk only handles one narrow function: uploading and publishing artwork via Sta.sh endpoints. That portion is consistent with the description, but key declared capabilities are absent from the provided code. Additionally, the script references parser fields and helper functions that are not defined in the shown chunk, indicating the implementation is incomplete or inconsistent as supplied. Because the declared purpose materially exceeds the observed behavior in this code chunk, this should be flagged as a mismatch.

Credential Access

High
Category
Privilege Escalation
Content
## Workflow

1. Ensure a local DeviantArt app exists and the user has a `client_id` and redirect URI.
2. Create an app credentials file at `~/.openclaw/deviantart-app-credentials.json`, or override the path with `DEVIANTART_APP_CREDENTIALS`.
3. If no token exists or refresh fails, run `scripts/deviantart_auth.py`.
4. Before any external post, summarize what will be published and get explicit confirmation.
5. Run the relevant script for artwork, journals, or statuses.
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## Workflow

1. Ensure a local DeviantArt app exists and the user has a `client_id` and redirect URI.
2. Create an app credentials file at `~/.openclaw/deviantart-app-credentials.json`, or override the path with `DEVIANTART_APP_CREDENTIALS`.
3. If no token exists or refresh fails, run `scripts/deviantart_auth.py`.
4. Before any external post, summarize what will be published and get explicit confirmation.
5. Run the relevant script for artwork, journals, or statuses.
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## Workflow

1. Ensure a local DeviantArt app exists and the user has a `client_id` and redirect URI.
2. Create an app credentials file at `~/.openclaw/deviantart-app-credentials.json`, or override the path with `DEVIANTART_APP_CREDENTIALS`.
3. If no token exists or refresh fails, run `scripts/deviantart_auth.py`.
4. Before any external post, summarize what will be published and get explicit confirmation.
5. Run the relevant script for artwork, journals, or statuses.
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
- `stash/submit` may return an error body even with HTTP 200. Always inspect the JSON body.
- New DeviantArt apps use PKCE. Keep the auth flow local and desktop-friendly.
- Access tokens expire quickly; refresh automatically before posting.
- Omit empty optional publish fields; DeviantArt validates them aggressively.
- Use `--dry-run` when the user wants a preview before uploading.
- Gallery folder names can be resolved through `--gallery-name`; if multiple folders have the same name, require a UUID instead.
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## Token lifetimes

- Access token: about 1 hour
- Refresh token: about 3 months

## Posting workflow
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill documentation describes capabilities that require environment access, reading and writing local files, and network access, but it does not declare any explicit tool scope or permissions boundary. This increases the chance that an agent executes the skill with broader-than-necessary privileges, making accidental credential exposure or unintended external posting more likely.

Session Persistence

Medium
Category
Rogue Agent
Content
---
name: deviantart-post
description: Post artwork, journals, and status updates to a user's DeviantArt account through the official DeviantArt API using OAuth 2.1 Authorization Code with PKCE, Sta.sh upload, and Sta.sh publish. Use when the user wants to authenticate a local DeviantArt app, upload or publish a local file to DeviantArt, create a DeviantArt journal, or post a DeviantArt status update.
---

# DeviantArt Post
Confidence
69% confidence
Finding
The skill explicitly relies on persistent local token storage to perform authenticated posting actions, which creates a reusable session artifact on disk. If those token files are accessible to other local processes, users, or overprivileged agents, an attacker could reuse the session to post content or access account functions without re-authentication.

Session Persistence

Medium
Category
Rogue Agent
Content
## Public-skill expectations

This skill assumes the user will:
- create their own DeviantArt developer app
- provide their own `client_id`
- choose a localhost redirect URI
- store credentials locally in `~/.openclaw/`
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
87% confidence
Finding
This code refreshes OAuth credentials over the network and persists the refreshed token to disk, and later sends access tokens and arbitrary form fields to the DeviantArt API. The file contains no confirmation prompt, logging, print statement, or explanatory comment/docstring warning users that credentials and submission data will be transmitted and stored.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The multipart encoder reads bytes from local file paths and api_post_multipart sends that data to the DeviantArt API, which can affect user privacy and data handling. There is no visible confirmation, print/log notice, or explanatory comment/docstring in this file disclosing that local files are uploaded to an external service.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The script references hidden behavior via args.gallery_name and resolve_gallery_names() even though neither is declared in the parser or visible interface. This creates a mismatch between the documented CLI contract and runtime behavior, which is dangerous in an agent skill because undeclared inputs or helper calls can enable unintended actions, confuse operators, or conceal data-dependent network operations.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The code path depends on undocumented options/behavior not present in the parser, contradicting the declared interface. In an agent context, this is a security-relevant integrity issue because users and orchestrators may approve one set of actions while the implementation attempts another, undermining transparency and safe review.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code performs a multipart network upload of the user-supplied file to the DeviantArt API, which transmits local file contents off-system. Although success and dry-run messages exist, there is no confirmation prompt, pre-upload notice, or inline warning comment/docstring disclosing that the file will be sent to a remote service.

Missing User Warnings

Low
Confidence
80% confidence
Finding
The form POST publishes artwork metadata including title, comments, tags, and moderation-related fields to DeviantArt. The script lacks a pre-action warning or confirmation explaining that this metadata will be transmitted and used to publish content remotely.

Static analysis

No suspicious patterns detected.