T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/m365-todo.mjs:86
- Finding
- OAuth Token Cache Is Stored Without Restrictive File Permissions## Vulnerability Details **File Location**: `scripts/m365-todo.mjs`, lines 86–95 **Vulnerability Type**: Insecure storage of reusable OAuth token material **Risk Level**: Medium ### Vulnerable Code ```js ensureDir(cachePath); if (fs.existsSync(cachePath)) { try { const raw = fs.readFileSync(cachePath, 'utf8'); pca.getTokenCache().deserialize(raw); } catch { // ignore corrupted cache, device login will refresh } } return { pca, cachePath }; } async function saveCache(pca, cachePath) { const serialized = await pca.getTokenCache().serialize(); fs.writeFileSync(cachePath, serialized, 'utf8'); } ``` ### Technical Analysis The Skill serializes the Microsoft Authentication Library token cache and writes it to a local file without explicitly setting restrictive permissions. The serialized cache can contain reusable authentication material, including access-token and refresh-token data associated with the delegated Device Code login. `fs.mkdirSync()` and `fs.writeFileSync()` are invoked without explicit modes. Consequently, the resulting permissions depend on the process umask. In a common environment, the cache directory may be created as `0755` and the cache file as `0644`, potentially allowing other local users to read the file. The implementation also does not inspect or correct permissions when an existing cache is loaded. The optional `M365_TOKEN_CACHE_PATH` environment variable further permits the cache to be placed in a custom location, but the code does not verify that the destination is private, is a regular file, or is not a symbolic link. ### Attack Path 1. A victim runs the Skill and completes Microsoft Device Code authentication. 2. MSAL serializes reusable OAuth token material. 3. The Skill writes that material to the configured or default cache path without enforcing a `0600` file mode. 4. On a multi-user system or within an environment where another compromised process can access the path, an attacker reads or redirects a ...[truncated 1284 chars]
- Remediation
- ## Remediation Suggestions 1. Create the cache directory with owner-only permissions: ```js fs.mkdirSync(path.dirname(cachePath), { recursive: true, mode: 0o700, }); fs.chmodSync(path.dirname(cachePath), 0o700); ``` 2. Write the cache with mode `0600`. Prefer an atomic replacement strategy using a private temporary file in the same directory: ```js const tempPath = `${cachePath}.${process.pid}.tmp`; fs.writeFileSync(tempPath, serialized, { encoding: 'utf8', mode: 0o600, flag: 'wx', }); fs.renameSync(tempPath, cachePath); fs.chmodSync(cachePath, 0o600); ``` 3. Before reading or replacing an existing cache, use `lstatSync()` to reject symbolic links and non-regular files. 4. Verify ownership and permissions of existing cache files. Refuse to use files owned by a different user or files accessible by group/other users. 5. Document that `M365_TOKEN_CACHE_PATH` must point to a private, user-owned location and should not reside in shared directories, source repositories, synchronized folders, or world-readable mounts. 6. Where practical, store token material in an operating-system credential store or encrypted secret-storage service rather than a plaintext filesystem cache. 7. Provide token-revocation and cache-removal instructions for users who suspect local disclosure.
