T09 · Insecure Skill Coding Practices
- Location
scripts/pangolinfo.py:35- Finding
Permanent Bearer Token Stored Using a Non-Atomic, Symlink-Following File Operation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/pangolinfo.py, lines 35 and 63-66
Vulnerability Type: Insecure secret storage and unsafe file creation
Risk Level: MediumVulnerable Code
python TOKEN_CACHE_PATH = Path.home() / ".pangolin_token" def save_cached_token(token): """Save token to cache file.""" TOKEN_CACHE_PATH.write_text(token) TOKEN_CACHE_PATH.chmod(0o600)The associated documentation states that the cached tokens are permanent unless the account is deactivated:
markdown - Tokens are **permanent** -- they do not expire on their own - A token becomes invalid only if the account is deactivated - The script caches tokens at `~/.pangolin_token`Technical Analysis
The token is first written through
Path.write_text()and only afterward restricted to mode0600. When the cache file is initially created, its permissions are determined by the process umask. This creates a time-of-check/time-of-protection window during which the token may be readable by other local users. If the process terminates between the write andchmod(), the file may remain with overly permissive permissions.The fixed cache path is also used without checking whether it is a symbolic link. Both
write_text()andchmod()follow symbolic links. An attacker who can pre-create or replace~/.pangolin_tokenwith a symbolic link could therefore redirect the token write to another user-writable target and cause the target's permissions to be changed to0600.Because the cached value is a bearer token, possession is sufficient for authentication. The long-lived token design increases the useful exploitation window.
Attack Path
- An attacker with local access and the ability to manipulate the token-cache path monitors or pre-creates
~/.pangolin_token. - The attacker either waits for creation under a permissive umask or replaces the path with a symbolic link to a suitable target.
- The victim invokes the Skill without ...[truncated 1244 chars]
- An attacker with local access and the ability to manipulate the token-cache path monitors or pre-creates
- Remediation
View remediation
Remediation Suggestions
- Create secret files with restrictive permissions from the outset rather than writing first and applying
chmod()afterward. - Reject symbolic links by using
O_NOFOLLOWwhere supported. - Write the token to a securely created temporary file in the same directory, flush and synchronize it, and atomically replace the cache file.
- Verify that existing cache files are regular files, owned by the current user, and have mode
0600before reading them. - Prefer an operating-system credential store instead of a plaintext file where practical.
- Support token rotation and revocation because the documentation identifies tokens as permanent.
An example hardened approach is:
python import os import stat import tempfile def save_cached_token(token): cache_dir = TOKEN_CACHE_PATH.parent fd, temp_path = tempfile.mkstemp( prefix=".pangolin_token.", dir=str(cache_dir), text=True, ) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w") as cache: cache.write(token) cache.flush() os.fsync(cache.fileno()) if TOKEN_CACHE_PATH.is_symlink(): raise RuntimeError("Refusing to replace a symbolic-link token cache") os.replace(temp_path, TOKEN_CACHE_PATH) except Exception: try: os.close(fd) except OSError: pass try: os.unlink(temp_path) except OSError: pass raiseThe cache-loading path should independently use
lstat()or an equivalent no-follow operation to ensure the cache is a regular file owned by the current user with no group or other access permissions.- Create secret files with restrictive permissions from the outset rather than writing first and applying
