T09 · Insecure Skill Coding Practices
- Location
scripts/web_server.py:39- Finding
Authentication Fails Open When the Access Token Is Missing
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill is a coherent local media manager, but verified web gallery and cleanup weaknesses could expose or delete files beyond what users are told.
Review carefully before installing. Use only a strong IMAGE_VAULT_TOKEN, keep IMAGE_VAULT_HOST on 127.0.0.1, avoid importing untrusted category/tag metadata, do not use cleanup_inbound.py with arbitrary directories, and treat imported GPS/location metadata and original file paths as sensitive until the auth, path validation, XSS, and deletion-scope issues are fixed.
scripts/web_server.py:39Authentication Fails Open When the Access Token Is Missing
scripts/web_server.py:158Arbitrary File Disclosure Through Unsafe Path-Prefix Validation
scripts/web_server.py:214Stored Cross-Site Scripting Through Unescaped Image Category Metadata
scripts/cleanup_inbound.py:11Unrestricted User-Selected Directory Can Cause Destructive File Deletion
setup.sh:43Unpinned Dependencies Are Installed Into the Active Python Environment
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def _load_dotenv():
"""Load .env from skill root (one level up from scripts/)."""
env_path = os.path.join(os.path.dirname(__file__), "..", ".env")
if not os.path.isfile(env_path):
return
with open(env_path) as f:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def _load_dotenv():
"""Load .env from skill root (one level up from scripts/)."""
env_path = os.path.join(os.path.dirname(__file__), "..", ".env")
if not os.path.isfile(env_path):
return
with open(env_path) as f:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def _load_dotenv():
"""Load .env from skill root (one level up from scripts/)."""
env_path = os.path.join(os.path.dirname(__file__), "..", ".env")
if not os.path.isfile(env_path):
return
with open(env_path) as f:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def _load_dotenv():
"""Load .env from skill root (one level up from scripts/)."""
env_path = os.path.join(os.path.dirname(__file__), "..", ".env")
if not os.path.isfile(env_path):
return
with open(env_path) as f:
The permanent deletion paths remove files from disk and delete database records immediately, with no safety checks, authorization guard, or confirmation mechanism in this layer. If these functions are reachable from an agent action or exposed API, a mistaken or malicious invocation could irreversibly destroy user data.
Loading a parent .env file is credential-access relevant because .env files commonly contain API keys, tokens, and database secrets, and this code imports them wholesale into the running process. In a reusable skill context, this broad secret ingestion exceeds least privilege and increases the blast radius if the app, its logs, or dependent modules are compromised.
sys.path.insert(0, os.path.dirname(__file__))
def _load_dotenv():
env_path = os.path.join(os.path.dirname(__file__), "..", ".env")
if not os.path.isfile(env_path):
return
with open(env_path) as f:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# What this script does:
# 1. Checks Python 3.9+ and ffmpeg availability
# 2. Installs 3 Python packages: Flask, Pillow, PyYAML
# 3. Creates .env with a random access token (if not exists)
# 4. Creates ~/.image-vault directory for local storage
# 5. Initializes the SQLite database
#
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# What this script does:
# 1. Checks Python 3.9+ and ffmpeg availability
# 2. Installs 3 Python packages: Flask, Pillow, PyYAML
# 3. Creates .env with a random access token (if not exists)
# 4. Creates ~/.image-vault directory for local storage
# 5. Initializes the SQLite database
#
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# What this script does:
# 1. Checks Python 3.9+ and ffmpeg availability
# 2. Installs 3 Python packages: Flask, Pillow, PyYAML
# 3. Creates .env with a random access token (if not exists)
# 4. Creates ~/.image-vault directory for local storage
# 5. Initializes the SQLite database
#
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# What this script does:
# 1. Checks Python 3.9+ and ffmpeg availability
# 2. Installs 3 Python packages: Flask, Pillow, PyYAML
# 3. Creates .env with a random access token (if not exists)
# 4. Creates ~/.image-vault directory for local storage
# 5. Initializes the SQLite database
#
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# What this script does:
# 1. Checks Python 3.9+ and ffmpeg availability
# 2. Installs 3 Python packages: Flask, Pillow, PyYAML
# 3. Creates .env with a random access token (if not exists)
# 4. Creates ~/.image-vault directory for local storage
# 5. Initializes the SQLite database
#
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# What this script does:
# 1. Checks Python 3.9+ and ffmpeg availability
# 2. Installs 3 Python packages: Flask, Pillow, PyYAML
# 3. Creates .env with a random access token (if not exists)
# 4. Creates ~/.image-vault directory for local storage
# 5. Initializes the SQLite database
#
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# What this script does:
# 1. Checks Python 3.9+ and ffmpeg availability
# 2. Installs 3 Python packages: Flask, Pillow, PyYAML
# 3. Creates .env with a random access token (if not exists)
# 4. Creates ~/.image-vault directory for local storage
# 5. Initializes the SQLite database
#
This line prints the access token directly to stdout, which is a genuine secret disclosure. Anyone with access to terminal history, logs, remote session recordings, or shared output can recover the token and use it to access the gallery.
IMAGE_VAULT_TOKEN=$TOKEN
# IMAGE_VAULT_DIR=$DEFAULT_VAULT
EOF
echo "✓ Created .env with auto-generated token: $TOKEN"
echo " ⚠ Save this token — you'll need it to access the gallery"
else
echo "✓ .env already exists"
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.
| Resource | Why | Scope |
|----------|-----|-------|
| **File system** | Read/write images to `~/.image-vault` | Only `IMAGE_VAULT_DIR` directory |
| **SQLite database** | Store image metadata (no images in DB) | `~/.image-vault/image_vault.db` |
| **Local HTTP server** | Web gallery UI on `127.0.0.1` | Localhost only, token-protected |
| **Python packages** | Flask, Pillow, PyYAML (via `pip`) | Standard packages |
The theme names and album descriptions are hardcoded in Chinese, so generated album names and metadata will use a specific language regardless of user preference. This is a natural-language locale policy issue because the file does not provide opt-in, configuration, or documented justification for enforcing that locale.
This code creates albums and adds images to them, which is a write operation affecting user data organization. While the script supports a --dry-run mode, the non-dry-run path performs changes without an in-code confirmation prompt or user-facing disclosure at the point of action beyond the final 'Created' output after changes are already made.
Multiple comments and docstrings in this file are written only in Chinese, while other parts of the file are in English, indicating a mixed but partially fixed language presentation. The skill does not offer any language choice or document that it is intentionally region/language-specific, which can violate language/locale policy expectations.
The code reads GPS coordinates from image EXIF data and later the schema includes latitude, longitude, and location_name fields, which is privacy-sensitive behavior. While there are internal docstrings, there is no visible user disclosure, confirmation prompt, or user-facing logging in this file warning that location metadata may be collected and stored.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
"ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", "-show_streams", filepath
]
out = subprocess.check_output(cmd, timeout=15, text=True)
data = json.loads(out)
duration = float(data.get("format", {}).get("duration", 0))
width, height = None, None
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
"-i", video_path, "-frames:v", "1",
"-q:v", "2", output_path
]
subprocess.run(cmd, capture_output=True, timeout=15)
if os.path.isfile(output_path) and os.path.getsize(output_path) > 0:
return output_path
except Exception as e:
This code can perform an irreversible operation by moving the user's file, which removes it from the original location when copy is false. While the CLI flag name --move hints at this behavior, the function itself has no confirmation prompt or explicit user-facing warning at the point of execution, and this is a safety-relevant file operation.
The importer automatically extracts EXIF date and GPS coordinates from user media and persists them without any disclosure, consent gate, or minimization. In a media-vault context this is meaningfully sensitive because location and timestamp metadata can reveal home addresses, routines, travel history, or identities, and users may not realize importing a photo also stores hidden metadata.
The server automatically reads a parent-directory .env file and imports every key into the process environment without validating origin or scope. In a skill/plugin context, this can unintentionally ingest secrets belonging to the host environment or adjacent projects, expanding the skill's access to credentials and making later misuse or leakage more likely.
Lines L143-L146 expose an irreversible image deletion operation, but this file provides no confirmation prompt, warning message, comment, or docstring disclosing that the action deletes user data. Under the code-file criteria, destructive operations should have some visible user disclosure unless clearly documented as the skill's stated purpose, which is not evident here.
No suspicious patterns detected.