T09 · Insecure Skill Coding Practices
- Location
scripts/m365-todo.mjs:94- Finding
OAuth Token Cache Is Written Without Explicit Restrictive Permissions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/m365-todo.mjs, lines 94–97
Vulnerability Type: Insecure storage of OAuth credentials
Risk Level: MediumVulnerable Code
js async function saveCache(pca, cachePath) { const serialized = await pca.getTokenCache().serialize(); fs.writeFileSync(cachePath, serialized, 'utf8'); }Technical Analysis
The serialized MSAL token cache is sensitive authentication material that may contain access tokens, refresh tokens, and account metadata. The Skill requests the
offline_accessscope, so the cache may permit authentication to be renewed beyond the lifetime of an individual access token.The cache is written using
fs.writeFileSync()without an explicit restrictive file mode. For a newly created file, effective permissions depend on the host process's umask. If the configured cache file already exists with permissive permissions, this operation does not correct them. Consequently, the cache may be readable by other local users or processes on a shared or misconfigured host.The default location under the user's home directory reduces exposure on normally configured systems, but it does not guarantee secure permissions. The optional
M365_TOKEN_CACHE_PATHalso allows the cache to be placed in a less protected location.Attack Path
- A victim authenticates through the Skill's Microsoft Entra device-code flow.
- The Skill serializes the MSAL cache after authentication or silent token acquisition.
- The cache is written to the default or configured path without enforcing mode
0600. - A permissive umask, insecure pre-existing file, or unsafe custom path makes the cache readable by another local account or process.
- A local attacker copies the serialized token cache.
- The attacker imports or otherwise uses the cached credentials to obtain Microsoft Graph access as the victim.
- The attacker reads or modifies resources authorized by the delegated scopes until the credentials ...[truncated 796 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 token cache with mode
0600, and correct permissions on existing files:
js fs.writeFileSync(cachePath, serialized, { encoding: 'utf8', mode: 0o600, }); fs.chmodSync(cachePath, 0o600);-
Use an atomic write strategy: create a temporary file in the same protected directory with
O_CREAT | O_EXCL, mode0600, flush it, and rename it over the destination. -
Before reading or replacing the cache, use
lstat()and reject symbolic links and non-regular files. Where supported, use no-follow file-opening semantics to reduce symbolic-link attacks. -
Validate custom
M365_TOKEN_CACHE_PATHvalues and warn or fail if the parent directory or cache file is accessible to group or other users. -
Prefer an operating-system credential manager or encrypted token-cache persistence rather than a plaintext filesystem cache.
-
Document the sensitivity of the cache, its expected permissions, credential revocation procedures, and the security implications of enabling
offline_access.
