T05 · Unauthorized Access and Privilege Escalation
- 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
Security audit
Security checks for vulnerabilities and agentic risk
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.
scripts/lexiang_api.py:241Automatic Extraction and Reuse of Authentication Credentials from Agent Process Arguments
scripts/converter.py:81Markdown Image Path Traversal Can Upload Files Outside the Obsidian Vault
scripts/sync.py:129Vault File Symlinks Can Cause Arbitrary External Files to Be Uploaded
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.
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.
命名 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` /
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
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"
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
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"
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
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"
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.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
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": {
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.
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.
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.
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.
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.
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.
# 从进程列表解析(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()
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.
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.
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.
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.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
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 calls execute external commands. Without careful input validation, this enables command injection.
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() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
# 伪造一个死进程的锁(同机 + 不存在的 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() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
"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() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
"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 calls execute external commands. Without careful input validation, this enables command injection.
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",
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.
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.
No suspicious patterns detected.