Back to skill

Security audit

Social Favorites to Obsidian

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent purpose, but it handles browser cookies and Obsidian passwords with under-scoped supply-chain and secret-protection controls.

Review carefully before installing. Use only a dedicated/local CookieCloud instance, avoid pasting Obsidian or encryption passwords into chat, rotate any credentials exposed in logs, restrict cookiecloud.env permissions, bind CookieCloud to localhost or a protected network, and prefer pinned/reviewed dependency versions before running sync or cron automation.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
Findings (6)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/setup.py:20
Finding

Mutable Third-Party Code Is Downloaded and Executed with Authentication Secrets

Content
View full analysis
Path: if shutil.which("git"): checkout = workdir / "my-collection-skills" code = run(["git", "clone", "--depth", "1", repo_url, str(checkout)]) if code == 0: return checkout print("[warn] git clone failed, trying main.zip") archive = workdir / "my-collection-skills-main.zip" urllib.request.urlretrieve(HCTEC_REPO_ZIP, archive) with zipfile.ZipFile(archive) as zf: zf.extractall(workdir) return workdir / "my-collection-skills-main" ``` ```python def load_env_file(path: str | Path) -> int: env_path = expand_path(path) if not env_path.exists(): return 0 loaded = 0 for raw in env_path.read_text(encoding="utf-8").splitlines(): line = raw.strip() if not line or line.startswith("#"): continue if line.startswith("export "): line = line[len("export ") :].strip() if "=" not in line: continue key, value = line.split("=", 1) key = key.strip() value = value.strip().strip('"').strip("'") if key: os.environ[key] = value loaded += 1 return loaded ``` ```python def run_json(cmd: list[str], timeout: int = 60 ...[truncated 2683 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/setup.py:36
Finding

Obsidian and CookieCloud Secrets Are Exposed Through Command Arguments and Console Output

Content
View full analysis
int: print("+ " + " ".join(cmd)) return subprocess.call(cmd) ``` ```python print("CookieCloud browser extension requires the following values:") print(f" Server URL: {cookiecloud_server_url}") print(f" UUID: {cc_uuid}") print(f" Password: {cc_password}") ``` ```python cmd = [ob, "sync-setup", "--vault", remote_vault, "--path", str(vault_path)] if sync_password: cmd.extend(["--password", sync_password]) if device_name: cmd.extend(["--device-name", device_name]) else: cmd.extend(["--device-name", "social-favorites-to-obsidian"]) code = run(cmd) ``` ```python def run(cmd: list[str], *, cwd: Path = SKILL_ROOT, check: bool = True) -> int: print("+ " + " ".join(str(x) for x in cmd)) code = subprocess.call([str(x) for x in cmd], cwd=str(cwd)) if check and code != 0: raise SystemExit(code) return code ``` ```python if obsidian_password: run( [ob, "login", "--email", obsidian_email, "--password", obsidian_password], check=False, ) ``` ```python if sync_password: setup_cmd.extend(["--obsidian-sync-password", sync_password]) run(setup_cmd) ``` ```python external_todos.append( "Install the CookieCloud extension in Chrome and enter: " f"server {browser_cookie_url}, " f"UUID {env_values.get('COOKIECLOUD_UUID', '')}, " f"Password {env_values.get('COOKIECLOUD_PASSWORD', '')}." ) ``` ### Technical Analysis The shared command wrappers print every command by joining all arguments. Obsidian account passwords and end-to-end encryption passwords are included directly in those argument arrays. They are therefore exp ...[truncated 1531 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/setup.py:166
Finding

Cookie and Authentication Files Are Created Without Enforced Owner-Only Permissions

Content
View full analysis
None: path.parent.mkdir(parents=True, exist_ok=True) lines = path.read_text(encoding="utf-8").splitlines() if path.exists() else [] seen: set[str] = set() out: list[str] = [] for line in lines: if "=" not in line or line.strip().startswith("#"): out.append(line) continue key = line.split("=", 1)[0].strip() if key in updates: out.append(f"{key}={updates[key]}") seen.add(key) else: out.append(line) for key, value in updates.items(): if key not in seen: out.append(f"{key}={value}") path.write_text("\n".join(out).rstrip() + "\n", encoding="utf-8") ``` ### Technical Analysis The `cookiecloud.env` file can contain CookieCloud credentials and raw Xiaohongshu or Zhihu cookies. The code creates and rewrites this file using `Path.write_text()` without explicitly setting owner-only permissions. The resulting permissions depend on the current process umask. In shared systems or unusual deployment environments, the file may be readable by the group or other local users. Updating an existing permissive file does not repair its mode. The file is necessarily plaintext because it is later loaded into environment variables, but plaintext storage requires strict filesystem controls that are not enforced here. ### Attack Path 1. The installer runs under a permissive um ...[truncated 767 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/export_obsidian.py:129
Finding

Unrestricted Image Downloads Permit Server-Side Request Forgery

Content
View full analysis
list[str]: html_text = str(item.get("html") or "") urls = [] for tag in re.findall(r"]*>", html_text, flags=re.I): for attr in ("data-original", "data-actualsrc", "src"): match = re.search( rf'{attr}\s*=\s*["\']([^"\']+)["\']', tag, flags=re.I, ) if match: urls.append(match.group(1)) return unique(urls) ``` ```python def download_image(session: requests.Session, url: str, referer: str) -> bytes: headers = {"User-Agent": UA} if referer: headers["Referer"] = referer response = session.get(url, headers=headers, timeout=45) response.raise_for_status() return response.content ``` ```python session = requests.Session() session.trust_env = False for index, url in enumerate(image_urls(platform, item), start=1): filename = f"{index:03d}-{extension_from_url(url)}.webp" target = asset_dir / filename wiki_path = ( f"{asset_dirname}/{platform_dir}/" f"{sanitize_filename(ident, ident)}/{filename}" ) try: if not target.exists(): save_webp(download_image(session, url, source_url), target) refs.append(f"![[{wiki_path}]]") except Exception as exc: errors.append({"url": url, "error": str(exc)[:500]}) ``` ### Technical Analysis Image URLs originate from remotely retrieved post HTML or JSON and are passed directly to `requests.Session.get()`. There is no validation of URL scheme, destination hostname, resolved IP address, redirect target, port, content type, or response size. Disabling proxy inheritance with `trust_env = False` does not prevent SSRF. An atta ...[truncated 1580 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
assets/docker-compose.cookiecloud.yml:1
Finding

CookieCloud Credential Service Uses a Mutable Proxy Image and Binds to All Interfaces

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/setup.py:220
Finding

Downloaded ZIP Archive Is Extracted Without Path Traversal Validation

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (45)

Tainted flow: 'cmd' from input (line 279, user input) → subprocess.call (code execution)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

External input (network, user) flows to a code execution sink. This enables remote code execution or command injection.

Content

Scanner excerpt · scripts/setup.py (reported line 39)May include surrounding context.

python
def run(cmd: list[str]) -> int:
    print("+ " + " ".join(cmd))
    return subprocess.call(cmd)


def command_path(name: str) -> str | None:

Ssd 3

High
Category
Not specified by scanner
Confidence
93% confidence
Finding

The workflow instructs handling CookieCloud credentials and generated secrets as part of the agent-guided setup, which normalizes secret collection in conversational flow. Even if the values are generated locally, having the agent request, display, or relay them creates a significant chance of leakage via transcripts or logging systems.

Content

No source excerpt is available for this finding.

Ssd 3

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill directs users to place CookieCloud secrets into a plaintext configuration file and even duplicates the same secret values under multiple variable names. Plaintext secret storage materially raises exposure risk from local compromise, backups, sync tools, shoulder surfing, and accidental inclusion in support bundles or repositories.

Content

No source excerpt is available for this finding.

Ssd 3

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The workflow explicitly tells the agent to ask the user for an account password and optional end-to-end encryption password. In the context of an agent skill, this is especially dangerous because such secrets may traverse untrusted interfaces, be retained in conversation logs, or be mishandled by downstream tooling.

Content

No source excerpt is available for this finding.

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/common.py (reported line 114)May include surrounding context.

python
def run_json(cmd: list[str], timeout: int = 600) -> dict[str, Any]:
    proc = subprocess.run(cmd, capture_output=True, text=True, env=os.environ.copy(), timeout=timeout)
    if proc.returncode != 0:
        err = (proc.stderr or proc.stdout or "").strip()
        raise RuntimeError(err or f"command failed: {' '.join(cmd)}")

Rp1

Medium
Category
MCP Rug Pull
Confidence
92% confidence
Finding

The README instructs users to execute an unpinned package directly via npx, which fetches and runs whatever version is currently published under that package name. If the package is updated maliciously, compromised, or a typo/package-ownership issue occurs, users could execute arbitrary code during installation. In a skill whose purpose is installation and credential setup, this is especially risky because the executed code may gain access to local files, browser-adjacent data, and subsequent secrets entered by the user.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The README states that CookieCloud UUID and password are automatically generated and written to ~/.config/social-favorites-to-obsidian/cookiecloud.env, but it does not warn that this file contains secrets or specify required filesystem protections. Because CookieCloud credentials can grant access to synchronized browser cookies, disclosure could enable session theft for logged-in services such as Xiaohongshu and Zhihu.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The README asks the user to provide highly sensitive Obsidian Sync credentials, including account password and potentially the end-to-end encryption password, but does not warn about secure entry, storage, logging, or process-history risks. In a skill that automates setup and sync, mishandling these secrets could expose the user's cloud account and all synced vault contents if they are stored insecurely or echoed in logs.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill invokes shell commands, accesses files, environment variables, and network-dependent services, but it does not declare any tool scope or permission boundaries. That creates an authorization gap where an agent may execute powerful actions without explicit least-privilege constraints or clear user consent expectations.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The activation description is broad enough to match many user requests involving installation, configuration, export, diagnosis, or scheduling around social content and Obsidian. Over-broad triggering increases the chance that the skill activates in unintended contexts and prompts for credentials or performs shell/network actions when a narrower skill should have been used.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs collection of highly sensitive secrets, including CookieCloud UUID/password and Obsidian account credentials, without a prominent user-facing warning about privacy, storage, transmission, and safer alternatives. In this context, the skill also performs networked synchronization and local persistence, so mishandling these credentials could expose third-party accounts, synced notes, and browsing/session data.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The short description and default prompt are written entirely in Chinese, indicating a fixed language/locale experience. There is no visible option for users to choose another language and no documented justification that this skill is intentionally region-specific, which creates a natural-language policy concern.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The default prompt auto-invokes the skill with a broad, action-oriented request to configure synchronization and daily scheduled syncing, without clearly constraining when invocation is appropriate or requiring explicit user confirmation. In a skill that handles social-platform favorites, login state, exports, and scheduled tasks, this increases the chance of unintended activation and overbroad actions affecting user data or automation settings.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

Several inline comments are written only in Chinese, embedding operational guidance in a single language without any indication of user opt-in or multilingual support. This may violate language/locale policy where skills are expected not to force a specific language absent explicit justification or choice.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This file contains user-facing natural-language instructions only in Chinese, and there is no indication that the skill is region-specific or that users may choose another language. Under the policy, forcing a specific language without user opt-in can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The instructions tell the agent to solicit highly sensitive Obsidian Sync credentials, including account password and possibly encryption password, without a clear privacy warning or a safer default. This increases the risk of credential exposure through chat history, logs, screenshots, or unintended persistence by the agent or platform.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill explicitly instructs the agent to ask the user for their Obsidian account password, which is a sensitive credential. Even though it mentions falling back to interactive ob login if the user is unwilling, normalizing direct password collection by the agent increases the risk of secret exposure through chat logs, telemetry, prompt history, or downstream tooling.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill instructs collection of the end-to-end encryption password, which is highly sensitive because it protects synced vault contents. Requesting it through the agent creates unnecessary exposure risk, and compromise of this secret could allow decryption of synchronized notes if other sync access is obtained.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The function reads arbitrary key-value pairs from an env file and injects them into os.environ, which can affect downstream behavior and introduce sensitive credentials into subprocess execution context. There is no confirmation prompt, logging, or explanatory comment warning users that local secrets or configuration from the file will be loaded into the runtime environment.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This code executes external commands via subprocess.run and explicitly passes os.environ.copy(), which may include sensitive variables loaded earlier or present in the host environment. The file contains no prompt, logging, or warning that external commands will run with inherited environment data.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/common.py (reported line 114)May include surrounding context.

python
def run_json(cmd: list[str], timeout: int = 600) -> dict[str, Any]:
    proc = subprocess.run(cmd, capture_output=True, text=True, env=os.environ.copy(), timeout=timeout)
    if proc.returncode != 0:
        err = (proc.stderr or proc.stdout or "").strip()
        raise RuntimeError(err or f"command failed: {' '.join(cmd)}")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/export_obsidian.py (reported line 291)May include surrounding context.

python
print("[skip] ob command not found")
        return
    try:
        subprocess.run([ob, "sync"], cwd=str(vault), check=False)
    except FileNotFoundError:
        print("[skip] ob command not found")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code file builds a command string that changes directories and runs two Python scripts, then embeds it in an agent message instructing execution. There is no confirmation prompt, warning comment/docstring, or user-facing disclosure in this file explaining that scheduled subprocess-style commands will be executed automatically.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.exposed_secret_literal

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
bin/social-favorites-to-obsidian.js:30

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/setup.py:50