Back to skill

Security audit

Md Viewer

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real Markdown LAN viewer, but it exposes local Markdown files over the network by default and uses risky reusable URL passwords and persistent history.

Review before installing. Use this only on trusted networks, prefer --localhost unless LAN sharing is truly needed, use --no-history for private files, do not share token URLs broadly, and avoid opening sensitive Markdown files until the authentication and path-validation issues are fixed.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/server.py:715
Finding

Sensitive path restrictions are bypassable through symbolic links

Content
View full analysis
tuple[bool, str]: """Check if path is allowed to be read. Returns (allowed, reason)""" path_lower = file_path.lower() # Only allow .md files if not file_path.endswith('.md'): return False, "Only .md files are allowed" # Check blocked absolute paths for blocked in BLOCKED_PATHS: if path_lower.startswith(blocked): return False, f"Access to system directory denied: {blocked}" # Check blocked patterns for pattern in BLOCKED_PATTERNS: if pattern.lower() in path_lower: return False, f"Access to sensitive file denied" return True, "" ``` ```python def _serve_markdown(self, file_path, query): # Security: Check if path is allowed allowed, reason = is_path_allowed(file_path) if not allowed: self._serve_error(f"Access denied: {escape(reason)}", query) return path = Path(file_path).resolve() # Verify it's actually a .md file if path.suffix.lower() != '.md': self._serve_error("Only .md files are allowed", query) return if not path.exists(): self._serve_error(f"File not found: {escape(file_path)}", query) return if not path.is_file(): self._serve_error("Not a file", query) return try: with open(path, 'r', encoding='utf-8') as f: md_content = f.read() ``` ### Technical Analysis The security deny-list is applied to the user-supplied path before `Path.resolve()` canonicalizes the path and follows symbolic links. The resulting canonical path is not submitted to `is_path_allowed()` again. Consequently, a path whose visible name appears harmless ca ...[truncated 1686 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/server.py:580
Finding

Reusable authentication password is exposed in URLs and plaintext HTTP traffic

Content
View full analysis
str: """Get auth parameter for URLs (for link sharing)""" password = _config.get('password') return f"&token={quote(password)}" if password else "" ``` ```python # Determine host: localhost flag or auto-detect if args.localhost: bind_host = "127.0.0.1" elif args.host: bind_host = args.host else: bind_host = lan_ip # Bind to LAN IP by default # Auto-generate password if not provided password = args.password or generate_password() ``` ```python if bind_host == "127.0.0.1": print(f"Local: http://localhost:{args.port}", flush=True) print("Network: Disabled (localhost only)", flush=True) elif bind_host == "0.0.0.0": print(f"Local: http://localhost:{args.port}", flush=True) print(f"Network: http://{lan_ip}:{args.port}", flush=True) ``` ```python lan_ip = get_lan_ip() encoded_path = quote(str(file_path)) link = f"http://{lan_ip}:{DEFAULT_PORT}/view?path={encoded_path}&token={quote(args.password)}" ``` ### Technical Analysis The same reusable server pa ...[truncated 2291 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/server.py:94
Finding

Missing Bleach dependency activates an unsafe regex-based XSS sanitizer

Content
View full analysis
str: """Sanitize HTML to prevent XSS using bleach (preferred) or fallback regex""" if BLEACH_AVAILABLE: # Use bleach for robust sanitization ALLOWED_TAGS = [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'hr', 'strong', 'em', 'b', 'i', 'u', 'code', 'pre', 'ul', 'ol', 'li', 'a', 'img', 'blockquote', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'span', 'div', ] ALLOWED_ATTRS = { 'a': ['href', 'title', 'name'], 'img': ['src', 'alt', 'title', 'width', 'height'], 'code': ['class'], 'pre': ['class'], 'span': ['class'], 'div': ['class'], 'h1': ['id'], 'h2': ['id'], 'h3': ['id'], 'h4': ['id'], 'h5': ['id'], 'h6': ['id'], } ALLOWED_PROTOCOLS = ['http', 'https', 'mailto'] return bleach.clean( html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS, protocols=ALLOWED_PROTOCOLS, strip=True ) # Fallback: basic regex sanitization (less secure) # Remove script tags html = re.sub(r']*>.*?', '', html, flags=re.DOTALL | re.IGNORECASE) html = re.sub(r']*/?>', '', html, flags=re.IGNORECASE) # Remove event h ...[truncated 2818 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
README.md:25
Finding

Runtime dependencies are installed without version or integrity pinning

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (22)

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

❌ ~/.ssh/, id_rsa, id_ed25519 ❌ ~/.gnupg/ ❌ ~/.aws/, ~/.gcp/ ❌ .netrc, .pgpass, .env ❌ *.pem, *.key, *.p12, *.pfx

text

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The documented purpose understates materially risky behaviors: LAN exposure, authentication, persistent cookies, tokenized URL access, disk-backed history, and JSON history retrieval. This mismatch prevents informed consent and can cause users to trigger a skill that exposes files or stores access metadata without realizing it.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill description does not prominently warn that it binds to a LAN IP and serves local file contents to other devices on the same network by default. This omission is especially risky in shared or untrusted Wi‑Fi environments, where users may not realize they are publishing local content beyond their own machine.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

Sharing authentication tokens in URLs and persisting authentication in 30-day cookies introduces credential leakage risks through browser history, logs, screenshots, referrers, and copied links. In a LAN file-sharing skill, this can allow anyone who obtains the URL or cookie to access exposed markdown content without re-authentication.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
80% confidence
Finding

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

Content

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

md
BLOCKED_PATTERNS = [
    'id_rsa', 'id_dsa', 'id_ecdsa', 'id_ed25519',
    '.ssh/', '.gnupg/', '.aws/', '.docker/',
    '.env', '.netrc', '.pgpass', '.pypirc',
    'credentials', 'secret', 'private',
    '.key', '.pem', '.p12', '.pfx',
    '.htaccess', '.htpasswd',

Credential Access

High
Category
Privilege Escalation
Confidence
80% confidence
Finding

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

Content

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

md
BLOCKED_PATTERNS = [
    'id_rsa', 'id_dsa', 'id_ecdsa', 'id_ed25519',
    '.ssh/', '.gnupg/', '.aws/', '.docker/',
    '.env', '.netrc', '.pgpass', '.pypirc',
    'credentials', 'secret', 'private',
    '.key', '.pem', '.p12', '.pfx',
    '.htaccess', '.htpasswd',

Credential Access

High
Category
Privilege Escalation
Confidence
80% confidence
Finding

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

Content

Scanner excerpt · scripts/server.py (reported line 56)May include surrounding context.

python
BLOCKED_PATTERNS = [
    'id_rsa', 'id_dsa', 'id_ecdsa', 'id_ed25519',
    '.ssh/', '.gnupg/', '.aws/', '.docker/',
    '.env', '.netrc', '.pgpass', '.pypirc',
    'credentials', 'secret', 'private',
    '.key', '.pem', '.p12', '.pfx',
    '.htaccess', '.htpasswd',

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

python
BLOCKED_PATTERNS = [
    'id_rsa', 'id_dsa', 'id_ecdsa', 'id_ed25519',
    '.ssh/', '.gnupg/', '.aws/', '.docker/',
    '.env', '.netrc', '.pgpass', '.pypirc',
    'credentials', 'secret', 'private',
    '.key', '.pem', '.p12', '.pfx',
    '.htaccess', '.htpasswd',

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The README promotes automatic LAN binding, password-in-URL authentication, persistent cookies, and history retention, but does not prominently warn users that links may leak through browser history, logs, referrers, screenshots, or shared devices. In a LAN file-viewing skill, these choices materially increase the risk of unauthorized access to local document contents and file paths.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The documented trigger phrases are broad enough to activate during ordinary conversation, increasing the chance that the skill will expose local files over a LAN without the user intentionally invoking it. In this skill’s context, accidental activation is more dangerous because the core action is serving user-selected Markdown files via a network-accessible web server.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill declares no explicit tool scope or permissions even though it clearly starts a network service and accesses files, which weakens containment and reviewability. In an agent environment, undeclared file/network capabilities can lead to unexpected exposure of local content and make it harder for users or policy systems to assess risk before invocation.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Broad trigger phrases like 'show me' or 'view file' are likely to match normal conversation and can invoke the skill unexpectedly. In this skill's context, accidental invocation is more dangerous because the default behavior is to expose file contents via a LAN-accessible web server rather than merely displaying them locally.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The key-principle guidance reinforces ambiguous examples without clearly limiting them to safe, explicit user requests. That increases the chance the agent substitutes network sharing for benign requests to simply open or summarize a file, leading to unintentional content exposure.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The script places the password directly into the query string of the generated URL, which can leak through browser history, screenshots, logs, proxies, referrer headers, and copy/paste sharing. In this skill's context, the tool is explicitly designed to share links across devices on a LAN, which increases the chance that the secret is exposed to other users or recorded by intermediate systems.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

Despite calling itself a secure version, the server includes the password in URL query parameters for authentication and link sharing. Query-string secrets are exposed through browser history, copied links, logs, proxies, referrer leakage, and shoulder-surfing, which is especially risky for a LAN-facing file viewer that grants access to local files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The application records viewed file paths and timestamps without a clear user-facing warning near the behavior, increasing the likelihood of unintentional disclosure of private local metadata. This is a security-relevant privacy issue because users may assume the tool only renders a file temporarily, not that it retains a navigable audit trail.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The history UI and API expose previously opened file paths to authenticated users, creating a broader data-disclosure surface than a simple one-file viewer. Because the service is intended for LAN access, this turns local filesystem metadata into remotely browsable information that may reveal sensitive filenames and locations even if file contents themselves are blocked.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The JSON history API adds a machine-readable enumeration endpoint for viewed files, making data harvesting easier than the HTML history page alone. This capability is unnecessary for a Markdown viewer and increases the chance that local file metadata can be scraped or integrated into other tooling without the user's awareness.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The server persistently stores absolute paths and timestamps for viewed files, which exposes sensitive local metadata beyond the stated purpose of rendering a requested Markdown file. On a LAN-accessible service, anyone with access to the app can enumerate what documents were opened and where they are stored on disk, leaking private project names, usernames, directory layouts, and workflow history.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

The server binds to a LAN-reachable address by default and serves local files, but the messaging does not strongly emphasize that the service is exposed to other devices on the network. In the skill context, this increases risk because a user may unintentionally publish access to local Markdown files and associated metadata to anyone who can reach the host and knows or obtains the password.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The README prescribes activation phrases in Vietnamese and English as the expected way to trigger the skill, but it does not state whether users can choose their preferred language or whether these locale constraints are intentional. This can be a language-policy concern when a skill implicitly privileges fixed languages without opt-in or justification.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The manifest frames this skill as generating a LAN-accessible viewing link for a local Markdown file, but get_lan_ip() determines the host IP by opening a UDP socket to 8.8.8.8. While no data is meaningfully exchanged, this is still outbound network interaction not mentioned in the helper's own description or obviously necessary in the narrowest sense of formatting a link.

Content

No source excerpt is available for this finding.