Back to skill

Security audit

Clawdbot Skill Dropbox

Security checks for vulnerabilities and agentic risk

Overview

This Dropbox skill does what it claims, but it asks for broad Dropbox access and keeps long-lived Dropbox credentials in a plaintext local file without enforcing safe permissions.

Install only if you are comfortable granting this skill Dropbox file read/write access and keeping Dropbox OAuth credentials on disk. Prefer a Dropbox App Folder app unless whole-account access is required, restrict ~/.config/atlas to 700 and dropbox.env to 600, keep the file out of backups and repositories, and revoke the Dropbox app if the token file may have been exposed.

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/dropbox.py:20
Finding

Dropbox OAuth credentials are stored without enforcing restrictive file permissions

Content
View full analysis

Vulnerability Details

File Location: scripts/dropbox.py:20-27 and scripts/dropbox.py:36-44
Vulnerability Type: Insecure storage of plaintext OAuth credentials
Risk Level: Medium

The Skill stores the Dropbox application secret, long-lived refresh token, and access token in ~/.config/atlas/dropbox.env. It neither verifies the file's ownership and permissions when reading it nor enforces restrictive permissions when creating or updating it.

python
CONFIG_PATH = Path.home() / ".config/atlas/dropbox.env"

# Global config cache
_config = None


def load_config():
    """Load all credentials from the .env file."""
    global _config
    if _config is not None:
        return _config
    
    config = {}
    with open(CONFIG_PATH) as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#") and "=" in line:
                key, value = line.split("=", 1)
                config[key.strip()] = value.strip()
    
    _config = config
    return config
python
def save_config(config):
    """Save config back to .env file."""
    global _config
    lines = []
    for key, value in config.items():
        lines.append(f"{key}={value}")
    
    with open(CONFIG_PATH, 'w') as f:
        f.write("\n".join(lines) + "\n")
    
    _config = config

Technical Analysis

Opening the credential file with open(CONFIG_PATH, 'w') does not explicitly assign mode 0600. When a new file is created, its permissions depend on the process umask. When an existing file is rewritten, any previously permissive mode generally remains in place.

The corresponding setup instructions place all of the following sensitive values in this file:

  • DROPBOX_APP_SECRET
  • DROPBOX_REFRESH_TOKEN
  • DROPBOX_ACCESS_TOKEN

The refresh token is particularly sensitive because it is long-lived and can be exchanged for new access to ...[truncated 1729 chars]

Remediation
View remediation

Remediation Suggestions

  1. Create ~/.config/atlas with mode 0700.
  2. Create the credential file using an API that explicitly requests mode 0600, such as os.open with O_CREAT, O_WRONLY, and O_TRUNC.
  3. Call os.chmod(CONFIG_PATH, 0o600) after creation and before storing credentials.
  4. Before reading, use lstat to verify that the path is a regular file, is owned by the current user, and has no group or other permission bits.
  5. Reject symbolic links to reduce the risk of writing credentials through an attacker-controlled path.
  6. Update the file atomically through a securely created temporary file in the same protected directory, then replace the original.
  7. Where supported, store the refresh token and app secret in an operating-system credential manager instead of a plaintext environment file.
  8. Document the minimum manual hardening commands:
bash
chmod 700 ~/.config/atlas
chmod 600 ~/.config/atlas/dropbox.env
  1. Recommend Dropbox “App folder” access by default and reserve “Full Dropbox” access for workflows that explicitly require it.

T08 · Insecure Dependencies

Note
Location
package.json:23
Finding

Unused and unpinned requests dependency introduces unnecessary supply-chain exposure

Content
View full analysis

Vulnerability Details

File Location: package.json:23-25, README.md:30-33, and README.md:96-99
Vulnerability Type: Unnecessary unpinned third-party dependency
Risk Level: Low

Package metadata declares an unconstrained dependency on requests:

json
"dependencies": {
  "python": ["requests"]
}

The installation documentation separately instructs users to install the same unpinned package:

bash
pip install requests

The documentation also advertises it as a dependency:

markdown
## Features

- **Auto token refresh** — handles expired tokens automatically
- **Simple output** — easy to parse in scripts
- **No compilation** — pure Python
- **Minimal dependencies** — just `requests`

Technical Analysis

scripts/dropbox.py does not import or use requests; it performs network operations through Python's standard-library urllib modules. Consequently, installing requests is not necessary for any audited feature.

The dependency has no version constraint, integrity hash, or lock file. This allows the resolved package version to change over time without a corresponding change to the audited Skill. Although no malicious package is currently identified in the project, unnecessary package installation expands the supply-chain attack surface and can introduce future compromised releases, installation-time behavior, or incompatibilities.

This finding does not constitute dependency confusion or typosquatting: the declared package name is the legitimate requests package. The risk arises from installing unused, unconstrained third-party code.

Attack Path

  1. A user installs the Skill through a dependency-aware package mechanism or follows the documented pip install requests command.
  2. The installer resolves whichever requests release is available under the environment's configured package index and constraints at that time.
  3. If the selected package source ...[truncated 933 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the python: ["requests"] dependency declaration from package.json.
  2. Remove pip install requests from README.md.
  3. Correct documentation claiming that requests is required; state that the implementation uses only Python's standard library.
  4. If requests becomes necessary in a future implementation, pin an audited version and use a reproducible dependency lock file.
  5. Where supported, require package hashes and use a trusted package index.
  6. Add automated checks that compare imported modules with declared dependencies to detect unused or undeclared packages.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (24)

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The skill instructs users to store a Dropbox app secret and a long-lived refresh token in a local .env file. Long-lived credentials on disk create a high-value target: if the file is read by another process, user, backup system, or leaked via logs, an attacker can continuously mint new access tokens and access Dropbox data.

Content

Scanner excerpt · SKILL.md (reported line 18)May include surrounding context.

| DROPBOX_APP_KEY | ✅ Yes | Your Dropbox app key | | DROPBOX_APP_SECRET | ✅ Yes | Your Dropbox app secret | | DROPBOX_REFRESH_TOKEN | ✅ Yes | OAuth refresh token (long-lived) | | DROPBOX_ACCESS_TOKEN | Optional | Short-lived access token (auto-refreshed) |

Store in ~/.config/atlas/dropbox.env:

bash

Credential Access

High
Category
Privilege Escalation
Confidence
91% confidence
Finding

Automatic token refresh that rewrites dropbox.env means sensitive credentials are repeatedly handled and persisted by the skill. This increases exposure to accidental disclosure through file reads, race conditions, backups, or insecure permissions, especially in shared or multi-agent environments.

Content

Scanner excerpt · SKILL.md (reported line 111)May include surrounding context.

md
The script automatically handles token refresh:

1. On 401 Unauthorized, it uses the refresh token to get a new access token
2. Updates `dropbox.env` with the new access token
3. Retries the original request

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

Persisting newly refreshed access tokens to a local env file extends the attack surface from transient in-memory credentials to durable local secrets. In context, the skill performs both network actions and file writes, so compromise of the local token store could directly enable Dropbox account access.

Content

Scanner excerpt · SKILL.md (reported line 112)May include surrounding context.

md
The script automatically handles token refresh:

1. On 401 Unauthorized, it uses the refresh token to get a new access token
2. Updates `dropbox.env` with the new access token
3. Retries the original request

## Token Lifecycle

Credential Access

High
Category
Privilege Escalation
Confidence
84% confidence
Finding

The token lifecycle section confirms storage of reusable authentication material, including a refresh token that effectively remains valid until revoked. Because refresh tokens can be used to continuously obtain new access tokens, compromise of this stored credential could lead to long-term unauthorized access to Dropbox contents.

Content

Scanner excerpt · SKILL.md (reported line 119)May include surrounding context.

md
| Token | Lifetime | Storage |
|-------|----------|---------|
| Access Token | ~4 hours | Updated automatically |
| Refresh Token | Never expires* | Keep secure, don't share |

*Refresh tokens only expire if explicitly revoked or app access is removed.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/dropbox.py (reported line 27)May include surrounding context.

python
def load_config():
    """Load all credentials from the .env file."""
    global _config
    if _config is not None:
        return _config

Credential Access

High
Category
Privilege Escalation
Confidence
81% confidence
Finding

Saving OAuth credentials back to a plaintext .env file without setting restrictive permissions can expose tokens and app secrets to other local users or processes on shared systems. In an agent environment, this increases credential exposure because the skill automatically persists refreshed secrets during normal operation.

Content

Scanner excerpt · scripts/dropbox.py (reported line 45)May include surrounding context.

python
def save_config(config):
    """Save config back to .env file."""
    global _config
    lines = []
    for key, value in config.items():

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/dropbox.py (reported line 58)May include surrounding context.

python
def refresh_access_token():
    """Refresh the access token using the refresh token."""
    config = load_config()
    
    refresh_token = config.get("DROPBOX_REFRESH_TOKEN")

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/dropbox.py (reported line 103)May include surrounding context.

python
def refresh_access_token():
    """Refresh the access token using the refresh token."""
    config = load_config()
    
    refresh_token = config.get("DROPBOX_REFRESH_TOKEN")

Credential Access

High
Category
Privilege Escalation
Confidence
84% confidence
Finding

The refreshed access token is persisted in plaintext to disk, which can unnecessarily broaden token exposure if the host is multi-user, backed up insecurely, or scanned by other software. Because refresh happens automatically after a 401, this sensitive write may occur without explicit operator awareness.

Content

Scanner excerpt · scripts/dropbox.py (reported line 91)May include surrounding context.

python
if not new_access_token:
                raise ValueError("No access_token in refresh response")
            
            # Update config with new access token
            config["DROPBOX_ACCESS_TOKEN"] = new_access_token
            save_config(config)

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/dropbox.py (reported line 95)May include surrounding context.

python
config["DROPBOX_ACCESS_TOKEN"] = new_access_token
            save_config(config)
            
            print("🔄 Access token refreshed", file=sys.stderr)
            return new_access_token
    except urllib.error.HTTPError as e:
        error_body = e.read().decode() if e.fp else ""

Session Persistence

Medium
Category
Rogue Agent
Confidence
73% confidence
Finding

The README directs users to create a Dropbox app with 'Full Dropbox' scope, which grants access to the entire account rather than limiting access to an app folder or narrower permissions. In a lightweight automation skill, this broad persistence and authorization scope increases the blast radius if the app credentials or refresh token are stolen.

Content

Scanner excerpt · README.md (reported line 36)May include surrounding context.

md
## Setup

### 1. Create Dropbox App

1. Go to https://www.dropbox.com/developers/apps
2. Create app → Scoped access → Full Dropbox

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · README.md (reported line 56)May include surrounding context.

md
"

# Exchange code for tokens
curl -X POST "https://api.dropboxapi.com/oauth2/token" \
  -d "code=AUTH_CODE" \
  -d "grant_type=authorization_code" \
  -d "client_id=APP_KEY" \

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The README instructs users to store a Dropbox app secret plus long-lived access and refresh tokens in a local plaintext env file, but provides no warning about the sensitivity of those credentials or any guidance on file permissions, secret rotation, or exclusion from source control. If that file is exposed through backups, logs, shell history, shared home directories, or accidental commits, an attacker could gain persistent access to the user's Dropbox data.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill advertises capabilities that involve network access and writing local files, but it does not declare an explicit tool scope or permissions boundary. That omission can cause users or host systems to underestimate what the skill can do, especially since it stores and updates OAuth tokens on disk automatically.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The setup instructions encourage broad Dropbox privileges, including 'Full Dropbox', upload, download, and write permissions, without an explicit warning that the skill can modify or exfiltrate user files. In an agent context, broad cloud-storage permissions materially increase the blast radius of mistakes or abuse.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 77)May include surrounding context.

4. Exchange Code for Tokens

bash
curl -X POST "https://api.dropboxapi.com/oauth2/token" \
  -d "code=AUTHORIZATION_CODE" \
  -d "grant_type=authorization_code" \
  -d "client_id=APP_KEY" \

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · README.md (reported line 56)May include surrounding context.

md
raise ValueError("Missing DROPBOX_REFRESH_TOKEN, DROPBOX_APP_KEY, or DROPBOX_APP_SECRET in config")
    
    # Build the token refresh request
    url = "https://api.dropboxapi.com/oauth2/token"
    data = urllib.parse.urlencode({
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 77)May include surrounding context.

md
raise ValueError("Missing DROPBOX_REFRESH_TOKEN, DROPBOX_APP_KEY, or DROPBOX_APP_SECRET in config")
    
    # Build the token refresh request
    url = "https://api.dropboxapi.com/oauth2/token"
    data = urllib.parse.urlencode({
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/dropbox.py (reported line 69)May include surrounding context.

python
raise ValueError("Missing DROPBOX_REFRESH_TOKEN, DROPBOX_APP_KEY, or DROPBOX_APP_SECRET in config")
    
    # Build the token refresh request
    url = "https://api.dropboxapi.com/oauth2/token"
    data = urllib.parse.urlencode({
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/dropbox.py (reported line 111)May include surrounding context.

python
raise ValueError("Missing DROPBOX_REFRESH_TOKEN, DROPBOX_APP_KEY, or DROPBOX_APP_SECRET in config")
    
    # Build the token refresh request
    url = "https://api.dropboxapi.com/oauth2/token"
    data = urllib.parse.urlencode({
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/dropbox.py (reported line 240)May include surrounding context.

python
raise ValueError("Missing DROPBOX_REFRESH_TOKEN, DROPBOX_APP_KEY, or DROPBOX_APP_SECRET in config")
    
    # Build the token refresh request
    url = "https://api.dropboxapi.com/oauth2/token"
    data = urllib.parse.urlencode({
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

When the access token is refreshed, the script silently updates the credential file at ~/.config/atlas/dropbox.env and only reports that the token was refreshed after the write. This is a sensitive credential write operation, and the code does not clearly warn users beforehand that credentials will be modified on disk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The download path is written directly to the local filesystem, and when local_path is user-supplied there is no validation, confirmation, or restriction on where data may be saved. In an agent context, this can cause unintended file creation or overwrite in sensitive locations if upstream prompts or tool invocations are manipulated.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The manifest description focuses on file operations in Dropbox with OAuth refresh, but the code adds an account command that fetches and prints the user's account name and email. Account-profile access is a different capability from file management and is not described in the manifest.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.