T09 · Insecure Skill Coding Practices
- Location
index.js:79- Finding
Unrestricted Storage Path and Insecure Vault File Handling
- Content
View full analysis
Vulnerability Details
File Location:
index.js:79-102, with configuration input atindex.js:161-162
Vulnerability Type: Path traversal, symbolic-link following, insecure file permissions, and non-atomic credential storage
Risk Level: HighVulnerable Code
js class VaultStorage { constructor(storagePath, encryption) { this.storagePath = join(homedir(), storagePath); this.encryption = encryption; } async ensureStorage() { const dir = dirname(this.storagePath); if (!existsSync(dir)) { await mkdir(dir, { recursive: true }); } if (!existsSync(this.storagePath)) { await writeFile(this.storagePath, JSON.stringify({}, null, 2)); } } async load() { await this.ensureStorage(); const content = await readFile(this.storagePath, 'utf-8'); return JSON.parse(content); } async save(data) { await this.ensureStorage(); await writeFile(this.storagePath, JSON.stringify(data, null, 2)); } }The path is obtained directly from plugin configuration:
js const config = api.getConfig() || {}; const storagePath = config.storageFile || '.vault/passwords.json';Technical Analysis
The
storageFileoption is documented as a path relative to the user's home directory, but the implementation does not enforce that restriction. Passing the value tojoin(homedir(), storagePath)does not prevent..components from resolving outside the home directory.The implementation also uses ordinary filesystem reads and writes without checking whether the storage file or any parent component is a symbolic link. Consequently, a pre-positioned symlink can redirect vault operations to another file accessible to the OpenClaw process.
Files and directories are created without explicit restrictive modes. Their effective permissions therefore depend on the process umask, even though the documentation recommends m ...[truncated 2147 chars]
- Remediation
View remediation
Remediation Suggestions
-
Constrain storage to a dedicated directory
- Resolve the configured value against a fixed vault root such as
~/.vault. - Reject absolute paths,
..traversal, null bytes, and any resolved path outside that root. - Compare normalized paths using
path.resolve()andpath.relative()before filesystem access.
- Resolve the configured value against a fixed vault root such as
-
Defend against symbolic links
- Inspect path components with
lstat. - Reject symbolic links for the vault directory and storage file.
- Where supported, open files with flags that prevent symlink following and use file descriptors for subsequent operations.
- Verify file ownership before reading or replacing an existing vault.
- Inspect path components with
-
Apply restrictive permissions automatically
- Create the vault directory with mode
0700. - Create the vault file and temporary files with mode
0600. - Validate and, where appropriate, correct permissions on existing storage.
- Do not rely solely on the process umask or manual instructions.
- Create the vault directory with mode
-
Use atomic and serialized updates
- Serialize modifications with an appropriate lock.
- Write the complete JSON document to an exclusively created temporary file in the same directory.
- Flush the temporary file, apply mode
0600, and atomically rename it over the original. - Flush the containing directory where supported to improve crash consistency.
- Maintain a securely permissioned backup or recovery strategy.
-
Validate storage contents and fail safely
- Enforce a schema for parsed vault records.
- Set reasonable file-size limits.
- Avoid modifying a target if ownership, type, path containment, permissions, or schema validation fails.
-
