T09 · Insecure Skill Coding Practices
- Location
scripts/m365-todo.mjs:86- Finding
OAuth Token Cache Is Stored Without Restrictive File Permissions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/m365-todo.mjs, lines 86–95
Vulnerability Type: Insecure storage of reusable OAuth token material
Risk Level: MediumVulnerable 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()andfs.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 as0755and the cache file as0644, 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_PATHenvironment 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
- A victim runs the Skill and completes Microsoft Device Code authentication.
- MSAL serializes reusable OAuth token material.
- The Skill writes that material to the configured or default cache path without enforcing a
0600file mode. - 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
View remediation
Remediation Suggestions
- Create the cache directory with owner-only permissions:
js fs.mkdirSync(path.dirname(cachePath), { recursive: true, mode: 0o700, }); fs.chmodSync(path.dirname(cachePath), 0o700);- 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);-
Before reading or replacing an existing cache, use
lstatSync()to reject symbolic links and non-regular files. -
Verify ownership and permissions of existing cache files. Refuse to use files owned by a different user or files accessible by group/other users.
-
Document that
M365_TOKEN_CACHE_PATHmust point to a private, user-owned location and should not reside in shared directories, source repositories, synchronized folders, or world-readable mounts. -
Where practical, store token material in an operating-system credential store or encrypted secret-storage service rather than a plaintext filesystem cache.
-
Provide token-revocation and cache-removal instructions for users who suspect local disclosure.
