Back to skill

Security audit

Obsidian FNS

Security checks for vulnerabilities and agentic risk

Overview

This skill is purpose-built for remote Obsidian note access, but it handles credentials and vault data in ways users should review carefully before installing.

Install only if you trust the Fast Note Sync server and understand that the skill can read and modify remote vault notes. Use an HTTPS endpoint you control, avoid saving passwords in the config file, protect ~/.config/fast-note-sync/config.json, and treat terminal logs from login as sensitive because they may contain the bearer token.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fns.py:91
Finding

Sensitive credentials and vault data can be transmitted over an insecure or attacker-controlled endpoint

Content
View full analysis
str: base = base_url.rstrip('/') if not path.startswith('/'): path = '/' + path url = base + path if query: clean = {k: v for k, v in query.items() if v is not None and v != ''} if clean: url += '?' + urllib.parse.urlencode(clean) return url def request_json(method: str, url: str, *, token: Optional[str] = None, payload: Optional[Dict[str, Any]] = None, timeout: int = DEFAULT_TIMEOUT, accept_non_json: bool = False) -> Any: data = None headers = {} if payload is not None: data = json.dumps(payload, ensure_ascii=False).encode('utf-8') headers['Content-Type'] = 'application/json' if token: headers['Authorization'] = token if token.lower().startswith('bearer ') else f'Bearer {token}' req = urllib.request.Request(url=url, data=data, method=method.upper(), headers=headers) ``` The login operation passes credentials and a password to the same unrestricted destination: ```python def cmd_login(args: argparse.Namespace) -> None: cfg = effective_config(args) require(cfg, 'baseUrl', 'credentials', 'password') data = request_json( 'POST', build_url(cfg['baseUrl'], '/user/login'), payload={'credentials': cfg['credentials'], 'password': cfg['password']}, timeout=cfg['timeoutSeconds'], ) ``` ### Technical Analysis The remote network behavior is necessary for the declared Fast Note Sync functionality. However, the implementation accepts an arbitrary `baseUrl` from command-line arguments, environment variables, or configuration files without validating its scheme or destination. Consequently, the client permits plain HTTP ...[truncated 1855 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fns.py:71
Finding

Authentication secrets are persisted in a plaintext configuration file without enforced restrictive permissions

Content
View full analysis
None: data = get_local_config() data.update({k: v for k, v in updates.items() if v is not None and v != ''}) LOCAL_CONFIG.parent.mkdir(parents=True, exist_ok=True) LOCAL_CONFIG.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') ``` Login automatically persists the returned bearer token: ```python save_local_config({'token': token, 'baseUrl': cfg['baseUrl'], 'credentials': cfg['credentials'], 'vault': cfg.get('vault')}) ``` The configuration command can also persist both a password and token: ```python def cmd_set_config(args: argparse.Namespace) -> None: updates = { 'baseUrl': args.base_url, 'credentials': args.credentials, 'password': args.password, 'vault': args.vault, 'token': args.token, 'timeoutSeconds': args.timeout_seconds, } save_local_config(updates) ``` ### Technical Analysis The Skill stores authentication material in `~/.config/fast-note-sync/config.json` as unencrypted JSON. It does not explicitly create the file with mode `0600`, validate its owner, repair permissions on an existing file, or use an operating-system credential store. The effective permissions therefore depend on the process umask and any permissions already assigned to the file. A permissive umask or pre-created configuration file may make the secrets readable by other local users or processes. Persisting the account password is particularly unnecessary after token-based authentication is available. Although local token reuse is part of the declared functionality, plaintext password storage and the lack of enforced access controls are not required to implement that functionality securely. ### ...[truncated 1079 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fns.py:178
Finding

Login exposes the complete bearer token through standard output

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (11)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill advertises and operationally relies on shell, network, file read/write, and environment access, but it does not declare any explicit tool scope such as permissions or allowed-tools. That omission weakens least-privilege boundaries and makes it harder for a caller or enforcement layer to restrict the skill to only the minimum capabilities required, increasing the chance of unintended command execution or broader data access in a remote-vault workflow.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

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.

Content

Scanner excerpt · references/usage.md (reported line 40)May include surrounding context.

python3 {baseDir}/scripts/fns_actions.py read-note --path "OpenClaw/API-Test.md"

text

### Write note

```bash
python3 {baseDir}/scripts/fns_actions.py write-note \

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The usage guide documents low-level operations beyond the stated read/search/write/append scope, including broader file-management commands. This creates a capability mismatch that can mislead operators, bypass least-privilege expectations, and enable destructive actions through a skill that appears narrower than it really is.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The 'Verified capabilities' section explicitly advertises destructive and metadata operations such as prepend, replace, rename, and move that are not reflected in the skill's higher-level description. In a remote vault context, hidden breadth of capability increases the risk of unauthorized modification, data loss, or operator misuse because users may trust the narrower manifest instead of the deeper documentation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The code persists sensitive values from configuration updates directly to ~/.config/fast-note-sync/config.json, including password and token fields, without enforcing restrictive file permissions or warning the user. On multi-user systems, in misconfigured home directories, backups, or endpoint collection tooling, this can expose reusable credentials and API tokens beyond the intended scope.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The login command sends credentials and password in an HTTP POST request to the configured base URL. While network transmission is central to the tool's purpose, this file provides no visible disclosure, prompt, or comment warning users that sensitive authentication data will be sent to a remote service.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

After login, the returned authentication token is automatically written to local disk for reuse. Persisted bearer tokens are often sufficient for account access without re-entering a password, so compromise of the local config file can directly enable unauthorized access to remote Obsidian vault contents.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The set-config command allows password and token values to be saved to disk in plaintext configuration, again without warning, confirmation, or permission hardening. This broadens the exposure path because secrets may be manually persisted even outside the login flow, increasing risk of leakage through local compromise, backups, logs, or forensic collection.

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

python
def run_fns(args):
    cmd = ['python3', str(FNS)] + args
    p = subprocess.run(cmd, capture_output=True, text=True)
    if p.returncode != 0:
        sys.stderr.write(p.stderr or p.stdout)
        raise SystemExit(p.returncode)

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

This markdown file includes commands for write-note and append-note, which change user data in the vault, but it does not include any caution about overwriting or altering existing notes. For markdown files, SQP-2 applies when descriptions omit warnings about behaviors that could affect user data or system integrity.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The low-level CLI section shows direct modification commands such as put and append, and the document later lists additional mutating capabilities like replace, rename, and move, but no warning is provided about their impact on stored notes. In a markdown skill reference, omitting such warnings can leave users unaware that these commands alter persisted data.

Content

No source excerpt is available for this finding.