Back to skill

Security audit

Sync Obsidian to Lexiang

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches an Obsidian-to-Lexiang sync tool, but it uses unsafe authentication fallback logic and can upload files outside the intended vault boundary.

Review carefully before installing. Use only with a trusted vault, run dry-run first, prefer an explicit Lexiang profile or credential file, avoid relying on agent-proxy fallback authentication, remove untrusted symlinks or traversal-style attachment references, and verify the target Lexiang space/folder before any non-dry-run sync.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/lexiang_api.py:241
Finding

Automatic Extraction and Reuse of Authentication Credentials from Agent Process Arguments

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/converter.py:81
Finding

Markdown Image Path Traversal Can Upload Files Outside the Obsidian Vault

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/sync.py:129
Finding

Vault File Symlinks Can Cause Arbitrary External Files to Be Uploaded

Content
View full analysis
str: sha256 = hashlib.sha256() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(8192), b""): sha256.update(chunk) return f"sha256:{sha256.hexdigest()}" ``` Independent files are subsequently read and uploaded: ```python name = os.path.basename(file_path) size = os.path.getsize(file_path) mime = mimetypes.guess_type(name)[0] or "application/octet-stream" with open(file_path, "rb") as f: file_data = f.read() put_req = urllib.request.Request( upload_url, data=file_data, method="PUT", headers={ "Content-Type": mime, "Content-Length": str(len(file_data)), }, ) ``` ### Technical Analysis Python file operations follow symbolic links by default. Although `os.walk()` does not follow symlinked directories unless requested, a symlink to a file appears in the `filenames` collection and is processed as an ordinary file. No check is made using `os.path.islink()`, `Path.is_symlink()`, or canonical-path containment. Consequently, a link located inside the vault can point to any readable file outside it. The target is hashed and then uploaded as an independent Lexiang file. This differs from the Markdown traversal issue becau ...[truncated 1349 chars]
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (27)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared purpose is Obsidian-to-Lexiang synchronization, but the behavior described by analysis includes direct Lexiang entry management, moving/reorganizing content, listing remote children, and cleanup operations unrelated to simple one-way sync. That mismatch is dangerous because users may authorize the skill expecting import/sync behavior while it can modify or reorganize existing remote knowledge-base content beyond that expectation.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
91% confidence
Finding

The skill explicitly references credential file locations and environment-based credential discovery, and it can persist credential-file paths into vault config for later reuse. In a skill with shell, file, and network capabilities, this creates a meaningful risk of credential exposure, misuse, or accidental propagation of sensitive local paths if logs, reports, configs, or prompts are accessed by other components.

Content

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

text

命名 profile 位于 `~/.config/lexiang-upload/profiles/<name>.json`;
`default` 对应 `~/.config/lexiang-upload/credentials.json`。也可直接传
`--lexiang-credential-file <PATH>`,两者不能同时使用。命令行选择会写入
vault 的 config(只保存 profile 名或路径,不保存 token),后续定时任务可复用。
若 config 未设置,则考虑 `LEXIANG_UPLOAD_CREDENTIALS` /

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

md
MCP_URL = "https://mcp.lexiang-app.com/mcp"
DEFAULT_PERSONAL_CREDENTIALS = Path(
    "~/.config/lexiang-upload/credentials.json"
).expanduser()
PERSONAL_PROFILE_DIR = Path(
    "~/.config/lexiang-upload/profiles"

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 · scripts/lexiang_api.py (reported line 37)May include surrounding context.

python
MCP_URL = "https://mcp.lexiang-app.com/mcp"
DEFAULT_PERSONAL_CREDENTIALS = Path(
    "~/.config/lexiang-upload/credentials.json"
).expanduser()
PERSONAL_PROFILE_DIR = Path(
    "~/.config/lexiang-upload/profiles"

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 · scripts/sync.py (reported line 680)May include surrounding context.

python
MCP_URL = "https://mcp.lexiang-app.com/mcp"
DEFAULT_PERSONAL_CREDENTIALS = Path(
    "~/.config/lexiang-upload/credentials.json"
).expanduser()
PERSONAL_PROFILE_DIR = Path(
    "~/.config/lexiang-upload/profiles"

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

This code explicitly searches process listings for WorkBuddy/QClaw/CodeBuddy MCP configuration, extracts Authorization and session headers, and then reuses them to make authenticated requests. That is a direct credential-harvesting behavior not required for simple Obsidian-to-Lexiang sync, and it enables unauthorized reuse of another local agent session.

Content

No source excerpt is available for this finding.

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 · scripts/test_sync.py (reported line 1205)May include surrounding context.

python
def test_loads_nested_uploader_compatible_fields(self):
        with tempfile.TemporaryDirectory() as directory:
            path = os.path.join(directory, "credential.json")
            with open(path, "w", encoding="utf-8") as handle:
                json.dump({
                    "auth": {

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill describes capabilities that involve filesystem access, shell execution, environment-variable reads, and networked synchronization, but it does not declare any explicit tool scope or allowed-tools boundary. In an agent environment, this weakens least-privilege enforcement and can let the skill run with broader access than users or platform policy would expect.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

This markdown file presents all operational guidance in Chinese and does not indicate that users may select another language or locale. Under the policy rule, forcing a specific language without user opt-in is a natural-language policy violation unless the locale restriction is clearly justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The file-level docstring and inline natural-language documentation are entirely in Chinese, which indicates a fixed language choice without any opt-in, fallback, or statement that the skill is region-specific. Under the policy, language constraints should either offer user choice or be clearly documented as justified for a specific locale.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
79% confidence
Finding

The module documentation claims page creation/overwrite is forbidden here, but the implementation exposes generic call_tool primitives that can invoke arbitrary MCP tools by name. In a hostile or buggy caller context, that means the code can be repurposed to perform operations beyond the documented safety boundary, undermining the stated restriction.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code silently falls back to harvesting proxy authentication material from local process listings without notice or consent. Even if intended as convenience, undisclosed extraction of bearer credentials and session IDs is a serious trust and security violation because it enables stealthy impersonation of another authenticated local process.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
92% confidence
Finding

The subprocess call itself is not the main issue, but here it is used to enumerate local processes and parse command-line arguments for MCP proxy details. Because those arguments may contain live Authorization headers and session identifiers, this becomes credential harvesting from unrelated local agent processes, which exceeds the stated sync purpose and creates a real credential-access pathway.

Content

Scanner excerpt · scripts/lexiang_api.py (reported line 241)May include surrounding context.

python
# 从进程列表解析(macOS / Linux 通用)
    try:
        out = subprocess.check_output(["ps", "aux"], text=True, stderr=subprocess.DEVNULL)
        for line in out.splitlines():
            # 匹配 WorkBuddy / QClaw / CodeBuddy 进程,且包含 mcp-config
            lower = line.lower()

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The upload flow reads arbitrary local files and transmits their contents to a presigned remote URL and then commits the upload via MCP. These are safety-relevant network and data-transmission operations, but the code provides no confirmation prompt or user-visible disclosure at the point of transmission.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This Python file contains natural-language documentation and status text entirely in Chinese, including the module docstring and runtime message content. Under the policy criteria, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is explicitly justified, which is not present here.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The module description and all user-facing report content are written only in Chinese, indicating the skill generates reports in a fixed language. The file does not offer any language choice or document a justified locale restriction, which conflicts with the language/locale policy for natural-language behavior.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file-level docstring and all user-facing usage/help text in this script are written in Chinese, which effectively constrains the skill's interaction language for operators without offering any language choice. This is a natural-language locale policy concern because the skill does not document an opt-in or alternative locale.

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

python
name=name,
            credential_selector=credential_selector,
        )
        process = subprocess.run(command, capture_output=True, text=True)
        if process.returncode != 0:
            raise LexiangError(process.stderr.strip() or "公共 Markdown 上传器执行失败")
        try:

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

python
logf = open(log_path, "a", encoding="utf-8")
        except OSError:
            logf = subprocess.DEVNULL
        proc = subprocess.Popen(
            child_argv, stdout=logf, stderr=logf,
            stdin=subprocess.DEVNULL, start_new_session=True,
        )

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/test_sync.py (reported line 1074)May include surrounding context.

python
# 伪造一个死进程的锁(同机 + 不存在的 pid)
        os.makedirs(get_plugin_dir(self.tmpdir), exist_ok=True)
        with open(self.lock_path, "w", encoding="utf-8") as f:
            json.dump({"pid": 999999, "host": __import__("socket").gethostname(),
                       "acquired_at": "x", "acquired_ts": time.time()}, f)
        self.assertFalse(_pid_alive(999999))
        lock = acquire_lock(self.tmpdir)  # 应接管

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/test_sync.py (reported line 1190)May include surrounding context.

python
"LEXIANG_UPLOAD_PROFILE": "environment",
            }
        )
        self.assertEqual(selected.path, __import__("pathlib").Path("/tmp/environment.json"))
        self.assertEqual(selected.profile, "")
        self.assertIsNone(resolve_personal_credential_selector(environ={}))

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/test_sync.py (reported line 1254)May include surrounding context.

python
"mcp_token": self.TOKEN,
            "company_from": "company",
        })
        error = __import__("urllib.error", fromlist=["HTTPError"]).HTTPError(
            connector.mcp_url, 401, "unauthorized", {}, io.BytesIO(b"unauthorized")
        )
        with mock.patch(

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

python
def test_init_persists_selector_without_token(self):
        with tempfile.TemporaryDirectory() as vault:
            os.makedirs(os.path.join(vault, ".obsidian"))
            result = subprocess.run([
                sys.executable,
                os.path.join(os.path.dirname(__file__), "sync.py"),
                "--init",

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

This markdown file instructs users to obtain personal credentials and configure credential/profile files and environment variables, but it does not include a warning about handling these credentials carefully or the privacy implications of syncing content to a third-party knowledge base. Because SQP-2 applies to markdown files when descriptions omit warnings about behaviors affecting privacy or user data, this is a qualifying omission.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The primary natural-language instructions and documentation in the module docstring are presented only in Chinese. This can violate language/locale policy when the skill forces a specific language without user opt-in or a documented region-specific justification.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.