T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/_lib.mjs:15
- Finding
- Profile Name Path Traversal Enables Filesystem Access Outside the Secrets Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_lib.mjs:15-20` **Vulnerability Type**: Path traversal through an unvalidated profile identifier **Risk Level**: Medium ### Vulnerable Code ```js export function profilePaths(profile) { const dir = ensureSecretsDir(); return { cfgPath: path.join(dir, `${profile}.json`), cachePath: path.join(dir, `${profile}-token-cache.json`), }; } ``` The profile value is obtained directly from a command-line argument, including in `scripts/auth-devicecode.mjs:17` and `scripts/import-raw-token.mjs:14`: ```js const profile = mustGetArg('profile'); ``` The resulting paths are subsequently used for sensitive filesystem writes. For example, `scripts/import-raw-token.mjs:17-33` contains: ```js ensureSecretsDir(); const { cfgPath, cachePath } = profilePaths(profile); const raw = JSON.parse(fs.readFileSync(file, 'utf8')); if (!raw.access_token) throw new Error('Raw token file missing access_token'); // Store as a simple JSON cache format that _graph.mjs can consume when present. fs.writeFileSync(cachePath, JSON.stringify(raw, null, 2) + '\n', 'utf8'); writeJson(cfgPath, { clientId: raw.client_id || null, tenant: raw.tenant || 'consumers', email: raw.email || null, scopes: raw.scope ? String(raw.scope).split(' ') : [], authFlow: 'device_code_raw_import', createdAt: new Date().toISOString(), notes: 'Imported raw token JSON. Prefer MSAL cache when possible.', }); ``` ### Technical Analysis The `profile` argument is concatenated into two filenames without validation and passed to `path.join()`. Although `path.join()` normalizes a path, it does not enforce containment within the intended base directory. A profile containing traversal components such as `../../target` can therefore produce normalized paths outside: ```text ~/.openclaw/secrets/m365-calendar/ ``` The generated paths are used by several security-sensitive operations: - `scripts/auth-devicecode.mjs` writes profile configura ...[truncated 1982 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Restrict profile identifiers to a conservative allowlist: ```js export function validateProfile(profile) { if (typeof profile !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(profile)) { throw new Error( 'Invalid profile name: use only letters, digits, underscores, and hyphens' ); } return profile; } ``` 2. Resolve the generated paths and enforce containment within the secrets directory: ```js export function profilePaths(profile) { validateProfile(profile); const dir = path.resolve(ensureSecretsDir()); const cfgPath = path.resolve(dir, `${profile}.json`); const cachePath = path.resolve(dir, `${profile}-token-cache.json`); const prefix = `${dir}${path.sep}`; if (!cfgPath.startsWith(prefix) || !cachePath.startsWith(prefix)) { throw new Error('Profile path escapes the secrets directory'); } return { cfgPath, cachePath }; } ``` 3. Apply validation centrally in `profilePaths()` so every caller receives the same protection. 4. Reject path separators, dot components, control characters, and empty profile names. 5. Add automated tests covering malicious inputs such as: ```text ../target ../../target . .. profile/name profile\name ``` 6. Consider refusing to overwrite existing non-profile files and use safe, atomic file creation where practical. ]]>
