Back to skill

Security audit

Mastodon Scout

Security checks for vulnerabilities and agentic risk

Overview

The skill is a mostly coherent read-only Mastodon helper, but it can send the user's bearer token to any configured instance URL without validating the destination.

Review this before installing. Use only a read-scope token, set MASTODON_INSTANCE to the exact trusted HTTPS instance that issued that token, and do not let prompts or untrusted examples choose --instance. Revoke and rotate the token if it may have been used with an unknown or non-HTTPS instance.

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

Error
Location
scripts/mastodon_scout.py:78
Finding

OAuth Bearer Token Can Be Disclosed to an Arbitrary or Plaintext Endpoint

Content
View full analysis

Vulnerability Details

File Location: scripts/mastodon_scout.py:78-84, scripts/mastodon_scout.py:105-116, and credential-bearing call sites through scripts/mastodon_scout.py:146
Related Documentation: SKILL.md:27 and SKILL.md:45-46
Vulnerability Type: Unvalidated credential-bearing network destination
Risk Level: High

Vulnerable Code

The API helper attaches the OAuth token to every supplied URL:

python
def api_get(url, token):
    req = urllib.request.Request(url, headers={
        'Authorization': f'Bearer {token}',
        'Accept': 'application/json',
    })
    try:
        with urllib.request.urlopen(req) as resp:
            return resp.status, resp.read().decode('utf-8')
    except urllib.error.HTTPError as e:
        return e.code, e.read().decode('utf-8')

The destination is controlled by a command-line option or environment variable without validating its scheme or origin:

python
parser.add_argument('--instance', default=os.environ.get('MASTODON_INSTANCE', 'https://mastodon.social'))
parser.add_argument('--limit', type=int, default=int(os.environ.get('LIMIT', '20')))
parser.add_argument('--json', action='store_true', dest='raw_json')
args = parser.parse_args()

token = os.environ.get('MASTODON_TOKEN', '')
if not token:
    print('Error: MASTODON_TOKEN is not set', file=sys.stderr)
    sys.exit(1)

base = args.instance.rstrip('/')

if args.command == 'home':
    status, body = api_get(f'{base}/api/v1/timelines/home?limit={args.limit}', token)

Other commands similarly send the token to URLs derived from base:

python
elif args.command == 'user-tweets':
    status, body = api_get(f'{base}/api/v1/accounts/verify_credentials', token)
    check(status, body)
    acct_id = json.loads(body)['id']
    status, body = api_get(f'{base}/api/v1/accounts/{acct_id}/statuses?limit={args.limit}', token)

elif args.command == 'mentions
...[truncated 3772 chars]
Remediation
View remediation

Remediation Suggestions

  1. Require HTTPS

    • Parse the instance with urllib.parse.urlsplit.
    • Reject every scheme other than https.
    • Reject missing hostnames, URL fragments, and embedded usernames or passwords.
    • Permit HTTP only through an explicit development-only override that never uses production credentials.
  2. Bind credentials to an exact origin

    • Store each token together with the exact normalized scheme, hostname, and port of the issuing instance.
    • Refuse to send a token when the requested origin differs from the token's configured origin.
    • Prefer instance-specific variables or configuration records instead of one global token combined with an arbitrary URL.
  3. Constrain redirects

    • Disable automatic redirects for authenticated API requests or implement a custom redirect handler.
    • If redirects are needed, follow them only when the destination has the same validated HTTPS origin.
    • Never forward the Authorization header across origins.
  4. Validate the instance before constructing API URLs

    • Accept only an origin, not arbitrary paths or query strings.
    • Normalize internationalized hostnames and ports before comparison.
    • Consider an explicit trusted-instance allowlist for automated agent environments.
  5. Require informed confirmation for origin changes

    • Display the exact normalized destination hostname before first use.
    • Require explicit user approval before associating a token with a new instance.
    • Do not infer permission to transmit credentials merely because an untrusted prompt supplied --instance.
  6. Apply defense in depth

    • Continue recommending minimal read-only OAuth scopes.
    • Document that Mastodon tokens are instance-specific and must not be reused across unrelated hosts.
    • Avoid logging authorization headers and redact tokens from all errors.
    • Provide token revocation and rotation instructions ...[truncated 24 chars]
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (5)

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 · SKILL.md (reported line 122)May include surrounding context.

md
- **Redirect URI**: `urn:ietf:wg:oauth:2.0:oob`
  - **Scopes**: **CRITICAL — only select `read`** (uncheck write, follow, push)

**Step 3: Get Access Token**
- Click Submit, then open the created application
- Copy the **"Your access token"** value

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 · SKILL.md (reported line 124)May include surrounding context.

Step 3: Get Access Token

  • Click Submit, then open the created application
  • Copy the "Your access token" value

Step 4: Set Environment Variable

bash

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill requires environment-variable access and outbound network access to operate, but it does not explicitly declare a tool scope such as permissions or allowed-tools. That mismatch can cause the agent runtime or reviewer to underestimate the skill's capabilities, reducing transparency and increasing the chance of unintended token exposure or external requests being made without clear policy review.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The trigger section lists phrases like "show my mastodon timeline" and "get my mastodon posts" without defining stricter activation boundaries or exclusion conditions. Although Mastodon-specific, these are still free-form requests and the file does not clearly state when the skill should not activate, which can make invocation scope ambiguous.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The module docstring describes a Mastodon client, but the exposed command is named 'user-tweets', which refers to Twitter/X terminology rather than Mastodon posts. This creates an intent-level documentation/interface mismatch that can mislead callers about what resource is being fetched, even though the underlying code queries Mastodon account statuses.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.