Credential Access
- Category
- Privilege Escalation
- Confidence
- 60% confidence
- Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- Content
text __pycache__/ *.py[cod] .venv/ .env
Security audit
Security checks for vulnerabilities and agentic risk
The skill is a coherent Obsidian vault helper, but its optional shell hooks can run arbitrary local commands during ordinary vault operations, so it belongs in Review before installation.
Install only if you are comfortable granting the skill read/write authority over the configured vault. Leave OBSIDIAN_PRE_COMMAND and OBSIDIAN_POST_COMMAND unset unless you have reviewed the exact hook script yourself; if enabled, those hooks run with your user permissions even for reads and previews. Point OBSIDIAN_VAULT_RULES only at a dedicated non-sensitive Markdown rules file.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
__pycache__/
*.py[cod]
.venv/
.env
The skill is presented as a file-first vault editor, but it explicitly supports execution of trusted shell hooks via OBSIDIAN_PRE_COMMAND and OBSIDIAN_POST_COMMAND and relies on external configuration from OBSIDIAN_VAULT_RULES. This expands the attack surface from note manipulation to arbitrary command execution, and the mismatch can mislead operators into granting or using the skill under a lower-risk assumption than its real behavior warrants.
The skill exposes pre/post command hooks that can execute arbitrary shell commands via environment variables before and after ordinary vault operations. That behavior materially expands the tool from file editing into command execution, which is especially dangerous because the advertised purpose is safe, file-first Obsidian vault manipulation.
This duplicate finding points to the same dangerous primitive: Popen(command, shell=True) with untrusted input. In the context of an agent skill advertised for safe vault editing, embedding a shell-execution path is an unjustified and high-risk expansion of capability.
raise ValueError()
except ValueError as exc:
raise VaultError('OBSIDIAN_HOOK_TIMEOUT must be a positive finite number') from exc
process = subprocess.Popen(command, shell=True, cwd=vault.root, start_new_session=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
_, stderr = process.communicate(timeout=timeout)
This duplicate finding points to the same dangerous primitive: Popen(command, shell=True) with untrusted input. In the context of an agent skill advertised for safe vault editing, embedding a shell-execution path is an unjustified and high-risk expansion of capability.
raise ValueError()
except ValueError as exc:
raise VaultError('OBSIDIAN_HOOK_TIMEOUT must be a positive finite number') from exc
process = subprocess.Popen(command, shell=True, cwd=vault.root, start_new_session=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
_, stderr = process.communicate(timeout=timeout)
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.
self.base = Path(self.temp.name)
self.root = self.base / 'vault'
self.root.mkdir()
self.env = {k: v for k, v in os.environ.items() if not k.startswith('OBSIDIAN_')}
self.env.update(OBSIDIAN_VAULT_PATH=str(self.root), XDG_CACHE_HOME=str(self.base / 'cache'))
def note(self, name, content):
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.
self.base = Path(self.temp.name)
self.root = self.base / 'vault'
self.root.mkdir()
self.env = {k: v for k, v in os.environ.items() if not k.startswith('OBSIDIAN_')}
self.env.update(OBSIDIAN_VAULT_PATH=str(self.root), XDG_CACHE_HOME=str(self.base / 'cache'))
def note(self, name, content):
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.
mkdir -p ~/.openclaw/skills
git clone https://github.com/lanjelin/openclaw-obsidian.git \
~/.openclaw/skills/openclaw-obsidian
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.
Ask your agent, for example:
- “Find my notes about the garden project.”
- “Create a note in Inbox with these meeting notes.”
- “Show backlinks to Projects/Website.md.”
- “Preview renaming this note and updating its links.”
- “List empty project folders without removing them.”
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
--workdir /home/you/.local/state/obsidian-bisync
Run `chmod 700 /home/you/.local/bin/obsidian-bisync`, then add to your OpenClaw `.env`:
```dotenv
OBSIDIAN_PRE_COMMAND="/home/you/.local/bin/obsidian-bisync"
The skill exposes broad effective capabilities (environment access, file read/write, shell execution, and possibly network via helper behavior) without declaring an explicit tool scope or permissions boundary. That makes the operational trust model unclear and increases the chance an agent will invoke high-risk actions that users or the platform did not expect.
Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.
### Help generate a rules file
When requested, use `examples/vault-rules.md` as a starting point. Gather the user's protected/no-read folders, default note destination, naming, templates, and directory-specific conventions; do not invent private folder names or permissions. With no configured rules, use conservative defaults for any authorized discovery. Propose the resulting Markdown and save it at a user-approved absolute path outside the vault using a file-writing tool. Never overwrite existing rules implicitly. Explain how to set `OBSIDIAN_VAULT_RULES`; do not change environment or executable hooks without authorization.
## Discover and read
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
- When creating a requested note, create its missing allowed destination folder with the helper's `mkdir` command (`--parents` for nested folders). This includes the default `Inbox/`; do not leave it as a future destination merely because it does not exist. Directory creation obeys the same no-read/no-write rules and never initializes a missing vault root.
- Discover directories with `list --type dirs|all --under DIR`; use `--empty` only with `--type dirs`. Empty-folder discovery is not cleanup authorization, even when folders appear unused. Hidden/protected/symlink children still make a directory nonempty.
- Scope requested discovery with `search QUERY --under DIR` or `links [PATH] --under DIR`. Scope restricts source-note reads, not target resolution: outside-scope links still resolve using the full allowed inventory. Backlinks and move/delete/anchor refactors remain broad; scope never overrides no-read restrictions.
- Remove empty folders only when the user requests removal or explicitly authorizes cleanup covering those paths. Use the helper's `rmdir` preview followed by `--apply`; never remove contents, recurse, or automatically delete empty parents after note operations. Retain all directory restrictions.
- Prefer unambiguous vault-relative wiki links, for example `[[Projects/Example]]`. Preserve an existing link style when editing a note; do not rewrite external URLs or code examples.
- Keep attachments in `Attachments/` and preserve existing relative references. Only import explicitly user-specified local files with `import-attachment SOURCE DEST`: SOURCE is an absolute regular local file without symlink path components; DEST is an allowed non-`.md` path with an existing parent. Never overwrite, execute contents, or download automatically. In-vault sources retain their read restrictions. Do not replace, rename, or delete attachments unless explicitly requested and supported; never substitute shell mutations.
- Use literal `append PATH --content-file FILE --e
...[truncated 25 chars]
The rules file path is taken from an absolute environment variable and later rules_data() returns its full content to the caller. That permits exfiltration of arbitrary readable local files outside the vault if an attacker can point OBSIDIAN_VAULT_RULES at a sensitive path, which exceeds the stated vault-scoped purpose of the skill.
Hook execution occurs implicitly during normal command handling with no explicit prompt-time disclosure at the execution site. In practice this means a seemingly benign vault read or write may also run arbitrary local commands, undermining user expectations and increasing the chance of stealthy abuse.
The hook() function reads a command string from environment variables and executes it with subprocess.Popen(..., shell=True). Any actor who can influence the environment for this tool can trigger arbitrary shell execution in the context of the agent, and shell=True further enables shell metacharacter injection and command chaining.
raise ValueError()
except ValueError as exc:
raise VaultError('OBSIDIAN_HOOK_TIMEOUT must be a positive finite number') from exc
process = subprocess.Popen(command, shell=True, cwd=vault.root, start_new_session=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
_, stderr = process.communicate(timeout=timeout)
This is a real tainted-data-to-code-execution flow: command comes directly from os.environ.get and is passed to subprocess.Popen for execution. In an agent skill context, environment variables are configuration inputs rather than trusted code, so this creates an arbitrary command execution primitive unrelated to core vault file editing.
raise ValueError()
except ValueError as exc:
raise VaultError('OBSIDIAN_HOOK_TIMEOUT must be a positive finite number') from exc
process = subprocess.Popen(command, shell=True, cwd=vault.root, start_new_session=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
_, stderr = process.communicate(timeout=timeout)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
return str(path)
def cli(self, *args, code=0):
result = subprocess.run([sys.executable, str(HELPER), *args], env=self.env,
capture_output=True, text=True, timeout=15)
self.assertEqual(result.returncode, code, result.stdout + result.stderr)
return json.loads(result.stdout)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
return hashlib.sha256(path.read_bytes()).hexdigest()
def cli(self, *args, code=0):
result = subprocess.run([sys.executable, str(HELPER), *args], env=self.env,
capture_output=True, text=True, timeout=10)
self.assertEqual(result.returncode, code, result.stdout + result.stderr)
return json.loads(result.stdout)
The import-attachment command reads an external file and writes it into the vault with atomic_write, which affects user data. This function contains no confirmation prompt, print/log statement, or warning comment/docstring describing that it copies external content into the vault.
The helper methods file() and input() perform file writes, which fall under the code-file warning criteria for potentially data-affecting operations. Although this is test code, these functions have no docstring or comment explaining that they create or overwrite files in a temporary test workspace.
The test helper invokes an external Python script via subprocess.run, which is a safety-relevant operation under the code-file warning criteria. While the module docstring describes CLI coverage generally, there is no comment, docstring, print, or other explicit disclosure near this helper that it executes a subprocess.
No suspicious patterns detected.