Back to skill

Security audit

bangumi-tracker

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a legitimate Bangumi tracker, but it needs review because it persists reusable OAuth tokens insecurely on non-Windows systems and has a weak OAuth callback flow.

Review before installing, especially on macOS or Linux. Use only on a machine where ~/.bangumi is private, do not share or commit those files, and revoke the Bangumi OAuth app if tokens may have been exposed. The account-changing commands are user-invoked and purpose-aligned, and I found no evidence of malware or unrelated exfiltration.

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

T09 · Insecure Skill Coding Practices

Warning
Location
bangumi_tracker.py:215
Finding

OAuth Tokens Stored in Plaintext Without Enforced Access Permissions

Content
View full analysis

Vulnerability Details

File Location: bangumi_tracker.py, lines 215-230; related directory creation at lines 128-131
Vulnerability Type: Plaintext sensitive-data storage with insufficient permission enforcement
Risk Level: Medium

Vulnerable Code

python
def ensure_config_dir():
    """Ensure config directory exists"""
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
python
def save_token(token: Dict[str, Any]):
    """Save access token to Windows Credential Manager"""
    ensure_config_dir()

    if is_windows():
        # Save sensitive data to Credential Manager
        if token.get("access_token"):
            save_credential(CRED_TARGET_ACCESS_TOKEN, "access_token", token["access_token"])
            print(f"✅ Access token saved to Windows Credential Manager")

        if token.get("refresh_token"):
            save_credential(CRED_TARGET_REFRESH_TOKEN, "refresh_token", token["refresh_token"])
            print(f"✅ Refresh token saved to Windows Credential Manager")

        # Save expires_at to file (non-sensitive)
        if "expires_at" in token:
            with open(TOKEN_FILE, 'w', encoding='utf-8') as f:
                json.dump({"expires_at": token["expires_at"]}, f, indent=2)
    else:
        # Fallback to file for non-Windows
        with open(TOKEN_FILE, 'w', encoding='utf-8') as f:
            json.dump(token, f, indent=2)
        print(f"✅ Token saved to {TOKEN_FILE}")

Technical Analysis

On non-Windows platforms, the complete OAuth token object—including the access token and refresh token—is serialized as plaintext JSON in ~/.bangumi/token.json.

The code creates ~/.bangumi and the token file without explicitly enforcing owner-only permissions. Their effective permissions therefore depend on the process umask and any pre-existing directory or file permissions. Under a permissive umask, or when the paths already have unsafe permiss ...[truncated 1809 chars]

Remediation
View remediation

Remediation Suggestions

  1. Use a supported operating-system credential store on every platform, such as macOS Keychain, Secret Service on Linux, or an established cross-platform keyring library.
  2. If a file fallback is unavoidable:
    • Create ~/.bangumi with mode 0700.
    • Create a new token file with mode 0600 using exclusive, low-level file creation.
    • Correct unsafe permissions on existing paths before use.
    • Verify that the directory and file are owned by the current user.
    • Reject symbolic links and non-regular files.
    • Write to a protected temporary file, flush it, and atomically replace the destination.
  3. Minimize persisted token fields and never print tokens in logs or error messages.
  4. Document the storage risk and provide a command that revokes or removes all locally stored credentials.
  5. Consider encrypting file-based tokens with a key held by an operating-system credential store, rather than storing the encryption key beside the ciphertext.

T09 · Insecure Skill Coding Practices

Warning
Location
bangumi_tracker.py:365
Finding

Missing OAuth State Validation Enables Login CSRF and Authorization Response Injection

Content
View full analysis

Vulnerability Details

File Location: bangumi_tracker.py, lines 365-409
Vulnerability Type: OAuth authorization request and callback not correlated with a cryptographic state value
Risk Level: Medium

Vulnerable Code

python
# Build authorization URL
auth_params = {
    "client_id": config["client_id"],
    "response_type": "code",
    "redirect_uri": config.get("redirect_uri", DEFAULT_REDIRECT_URI)
}
auth_url = f"{OAUTH_URL}/authorize?{urllib.parse.urlencode(auth_params)}"
python
class CallbackHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        nonlocal authorization_code, server_running

        if "/callback" in self.path:
            # Parse query parameters
            query = urllib.parse.urlparse(self.path).query
            params = urllib.parse.parse_qs(query)

            if "code" in params:
                authorization_code = params["code"][0]
                self.send_response(200)
                self.end_headers()
                html = '''<html>
<body style="font-family: sans-serif; text-align: center; padding: 50px;">
    <h1>Authorization Successful!</h1>
    <p>You can close this window and return to the terminal.</p>
</body>
</html>'''
                self.wfile.write(html.encode('utf-8'))
                server_running = False
            elif "error" in params:
                self.send_response(400)
                self.end_headers()
                self.wfile.write(f"Error: {params['error'][0]}".encode())
                server_running = False
            else:
                self.send_response(400)
                self.end_headers()
                self.wfile.write(b"Missing authorization code")
        else:
            self.send_response(404)
            self.end_headers()

Technical Analysis

The OAuth authorization request does not include a cryptographically unpredictable state paramet ...[truncated 2380 chars]

Remediation
View remediation

Remediation Suggestions

  1. Generate a unique state value for every authorization attempt using a cryptographically secure generator such as secrets.token_urlsafe(32).
  2. Add that value to the authorization request as the OAuth state parameter.
  3. Retain the expected value only for the lifetime of the active authorization flow.
  4. Require the callback to include exactly one matching state value.
  5. Reject callbacks with an absent, malformed, duplicated, or incorrect state value before accepting the authorization code.
  6. Compare state values with secrets.compare_digest() where practical.
  7. Match the parsed callback path exactly against /callback rather than searching for the substring.
  8. Process only the first valid callback, impose a short timeout, and close the callback listener immediately after success or failure.
  9. Consider adding PKCE if supported by the Bangumi OAuth service, as defense in depth against authorization-code interception.
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 (16)

Credential Access

High
Category
Privilege Escalation
Confidence
74% confidence
Finding

On non-Windows systems, load_token reads OAuth tokens from ~/.bangumi/token.json, meaning bearer credentials are stored and retrieved from a regular filesystem path without any permission hardening or OS-backed secret storage. If local file permissions are weak, another local user or process could read the token and impersonate the user against the Bangumi API.

Content

Scanner excerpt · bangumi_tracker.py (reported line 173)May include surrounding context.

python
def load_token() -> Optional[Dict[str, Any]]:
    """Load access token from Windows Credential Manager"""
    if not is_windows():
        # Fallback to file for non-Windows
        if TOKEN_FILE.exists():

Credential Access

High
Category
Privilege Escalation
Confidence
80% confidence
Finding

save_token persists full token data to disk on non-Windows platforms, including access and refresh tokens, without enforcing restrictive permissions. Those tokens grant authenticated API access and can be reused by other local processes or users if the files are exposed.

Content

Scanner excerpt · bangumi_tracker.py (reported line 209)May include surrounding context.

python
def save_token(token: Dict[str, Any]):
    """Save access token to Windows Credential Manager"""
    ensure_config_dir()

    if is_windows():

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 · bangumi_tracker.py (reported line 216)May include surrounding context.

python
# Save sensitive data to Credential Manager
        if token.get("access_token"):
            save_credential(CRED_TARGET_ACCESS_TOKEN, "access_token", token["access_token"])
            print(f"✅ Access token saved to Windows Credential Manager")

        if token.get("refresh_token"):
            save_credential(CRED_TARGET_REFRESH_TOKEN, "refresh_token", token["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 · bangumi_tracker.py (reported line 239)May include surrounding context.

python
# Save sensitive data to Credential Manager
        if token.get("access_token"):
            save_credential(CRED_TARGET_ACCESS_TOKEN, "access_token", token["access_token"])
            print(f"✅ Access token saved to Windows Credential Manager")

        if token.get("refresh_token"):
            save_credential(CRED_TARGET_REFRESH_TOKEN, "refresh_token", token["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 · bangumi_tracker.py (reported line 297)May include surrounding context.

python
def get_valid_token() -> Optional[str]:
    """Get valid access token, refresh if expired"""
    token_data = load_token()
    if not token_data:
        return None

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 · bangumi_tracker.py (reported line 311)May include surrounding context.

python
def get_valid_token() -> Optional[str]:
    """Get valid access token, refresh if expired"""
    token_data = load_token()
    if not token_data:
        return None

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 · references/API.md (reported line 27)May include surrounding context.

md
| Operation | Method | URL |
|-----------|--------|-----|
| Get access token | POST | `https://bgm.tv/oauth/access_token` |
| Refresh token | POST | `https://bgm.tv/oauth/access_token` |
| Check token status | POST | `https://bgm.tv/oauth/token_status` |

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 · references/COMMANDS.md (reported line 94)May include surrounding context.

md
### Windows
- Client ID: `~/.bangumi/config.json`
- Client Secret: Windows Credential Manager (`BangumiTracker:client_secret`)
- Access Token: Windows Credential Manager (`BangumiTracker:access_token`)
- Refresh Token: Windows Credential Manager (`BangumiTracker:refresh_token`)
- Expires At: `~/.bangumi/token.json`

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill describes capabilities that involve network access, reading configuration, and writing credential/token data, but it does not declare any explicit tool scope or permissions boundary. That mismatch weakens reviewability and least-privilege controls, making it easier for an agent runtime to grant broader access than users or platform policy expect.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
76% confidence
Finding

The skill sets up persistent OAuth credentials and stores tokens locally, which creates session persistence on the host. While this is normal for an OAuth-based tracker, compromised local storage, overbroad file access, or poor token handling could let another process or user reuse the session to access or modify the owner's Bangumi account.

Content

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

md
## First Time Setup

### Step 1: Create OAuth App

1. Visit https://bgm.tv/dev/app/create
2. Fill in:

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 · bangumi_tracker.py (reported line 56)May include surrounding context.

python
CredFree = advapi32.CredFree

# Constants
BASE_URL = "https://api.bgm.tv/v0"
OAUTH_URL = "https://bgm.tv/oauth"
DEFAULT_REDIRECT_URI = "http://localhost:17321/callback"
DEFAULT_PORT = 17321

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The manifest describes managing Bangumi collections and progress for media types like anime, book, game, and music, plus accessing personal Bangumi data. However, the code also adds, removes, and lists collected characters and persons, which are distinct Bangumi entities and not part of the stated media-progress scope. This expands the skill’s behavior beyond what the description tells a developer or user to expect.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This markdown file describes that OAuth app credentials and access/refresh tokens are stored in ~/.bangumi/config.json and ~/.bangumi/token.json, but it does not warn users that these files contain sensitive secrets or advise them to protect file permissions. Because the file documents behavior affecting credential privacy, the absence of a disclosure or caution qualifies as a missing user warning.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

This code maps user group names to Chinese strings in terminal output, which imposes a specific language on all users without any opt-in or locale selection. The file otherwise uses mostly English CLI messaging, so these hardcoded labels create an inconsistent and forced locale experience.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The episode type display strings include Chinese-only labels such as 本篇 and 预告, with no mechanism for users to choose their preferred language. This is a natural-language locale policy issue because the skill forces a specific language in its output.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The command reference presents several user-facing descriptions exclusively in Chinese (想看, 在看, 已看, 搁置, 抛弃, 书籍, 动画, etc.) within otherwise English documentation. This creates a language-policy concern because the file forces a specific language for part of the interface without offering user opt-in or explaining that the skill is intentionally region-specific.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.