T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/todolist_drive_folder_agent.mjs:99- Finding
Managed OAuth requests unrestricted access to the user's entire Google Drive
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This Drive todo helper is mostly purpose-aligned, but it uses broad Google Drive authority and weak file/credential scoping that users should review before installing.
Review before installing. Use only with a dedicated Google account or isolated Drive folder containing no unrelated files, keep suggestions JSON trusted, prefer dry-run first, and revoke/delete stored OAuth tokens if you stop using it. Do not point it at a broad personal or business Drive until OAuth scope, file opt-in enforcement, and credential handling are tightened.
scripts/todolist_drive_folder_agent.mjs:99Managed OAuth requests unrestricted access to the user's entire Google Drive
scripts/todolist_drive_folder_agent.mjs:424Apply mode accepts arbitrary Drive file IDs without validating folder membership or authorization
scripts/todolist_drive_folder_agent.mjs:337Folder runner ignores the documented per-file AI opt-in controls
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.
---
name: todolist-md-clawdbot
description: Operate on todolist-md Markdown todo files. Read + summarize tasks, propose edits, and write outcomes back into Markdown using only <!-- bot: ... --> markers (line-stable write-back).
---
# todolist-md-clawdbot
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.
- Rule: **never insert a new line** once the header exists. Only update the existing header line.
- If the header does not exist, you may insert it at the very top **only if the user explicitly opted into Option B**.
- Canonical format:
- `<!-- bot: last_review --> 2026-02-04T15:39Z root=<rootFolderId> model=<model>`
6) Never complete tasks without explicit user confirmation.
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.
* Design goals:
* - Node-only I/O pipeline (unified)
* - Minimum tokens: do not call LLM unless a file changed; extract only open tasks
* - Safe write-back: write ONLY into a dedicated bot section using <!-- bot: ... --> markers
* - Avoid duplicates: overwrite-update via Drive API files.update(fileId)
* - Avoid overwriting while you edit: revision gate via headRevisionId
*
The script reads extra secret material from /root/clawd/.secrets/gog.env and passes account and keyring password values into a subprocess invocation for an unrelated gog tool. In an agent-skill context, this expands the trust boundary far beyond a simple Drive todo updater, enabling access to local secrets and privileged command execution paths that could be abused if the script or environment is compromised.
The code accesses a local secrets file under /root/clawd/.secrets to retrieve gog account credentials and keyring password, which is unrelated to the core todo-processing task. In an agent environment, harvesting or depending on extra credentials materially increases the blast radius because compromise of this skill can expose credentials for additional systems.
const envFile = '/root/clawd/.secrets/gog.env';
let account = process.env.GOG_ACCOUNT || '';
let pw = process.env.GOG_KEYRING_PASSWORD || '';
if ((!account || !pw) && fs.existsSync(envFile)) {
const text = fs.readFileSync(envFile, 'utf8');
The script explicitly parses and loads GOG_KEYRING_PASSWORD from a local secret source, expanding access to sensitive credentials not required for its primary function. This creates unnecessary credential exposure and makes the skill more dangerous in a privileged automation context.
let v = m[2];
v = v.replace(/^"|"$/g, '');
if (k === 'GOG_ACCOUNT' && !account) account = v;
if (k === 'GOG_KEYRING_PASSWORD' && !pw) pw = v;
if (k === 'GOG_BIN' && !process.env.GOG_BIN) process.env.GOG_BIN = v;
}
}
Passing GOG_ACCOUNT and GOG_KEYRING_PASSWORD into a child process environment exposes sensitive credentials to subprocess execution and potentially to process inspection, logs, crash reports, or inherited environments. In a multi-tenant or agent-hosted system, this is a serious secret-handling weakness with unnecessary lateral-risk potential.
const cmd = [
'sudo','-u','ubuntu','-H','env',
`GOG_ACCOUNT=${account}`,
`GOG_KEYRING_PASSWORD=${pw}`,
gogBin,
'drive','ls','--parent', folderId,'--json'
];
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.
`- [ ] Review open tasks in ${fileName} (generated ${now})`,
];
if (openTasks.trim()) {
lines.push(` > <!-- bot: note --> Open tasks sampled: ${openTasks.split(/\r?\n/).length}`);
} else {
lines.push(` > <!-- bot: note --> No open tasks found`);
}
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def gog_env() -> Dict[str, str]:
# Use same pattern as TOOLS.md: run as ubuntu and pass GOG_ACCOUNT/GOG_KEYRING_PASSWORD.
# Here we just forward current environment; caller should export vars.
env = os.environ.copy()
if not env.get("GOG_BIN"):
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.
def gog_env() -> Dict[str, str]:
# Use same pattern as TOOLS.md: run as ubuntu and pass GOG_ACCOUNT/GOG_KEYRING_PASSWORD.
# Here we just forward current environment; caller should export vars.
env = os.environ.copy()
if not env.get("GOG_BIN"):
env["GOG_BIN"] = "/home/linuxbrew/.linuxbrew/bin/gog"
return env
The script injects GOG_ACCOUNT and GOG_KEYRING_PASSWORD directly into the command line via env VAR=value arguments to sudo/env. On many systems command-line arguments are observable to other local users through process listing or audit logs, which can disclose the keyring password and compromise the authenticated Drive account.
"-H",
"env",
f"GOG_ACCOUNT={env.get('GOG_ACCOUNT','')}",
f"GOG_KEYRING_PASSWORD={env.get('GOG_KEYRING_PASSWORD','')}",
gog,
"drive",
"ls",
This repeats the same credential exposure pattern for the download path: the keyring password is embedded in process arguments when invoking sudo/env/gog. In the context of a Drive-review automation skill, that is more dangerous because the script operates on potentially sensitive documents and the exposed credential may grant broader Drive access than a single file.
"-H",
"env",
f"GOG_ACCOUNT={env.get('GOG_ACCOUNT','')}",
f"GOG_KEYRING_PASSWORD={env.get('GOG_KEYRING_PASSWORD','')}",
gog,
"drive",
"download",
The skill explicitly instructs the agent to persist storage configuration such as local paths, Drive folder IDs, S3 bucket/prefix information, and stable identity keys for future runs. While this is operationally useful, these values can reveal sensitive filesystem layout, cloud resource identifiers, and repository locations if stored insecurely or without user awareness, creating avoidable privacy and targeting risk.
The documentation states 'write ONLY into a dedicated bot section', implying modifications are confined to an existing marked section. However, ensureBotSuggestedSection creates a new titled section and bot marker block when the section is absent, which modifies document structure beyond merely updating an existing dedicated section.
The header says the script 'does NOT call any LLM', which is literally true, but the surrounding documentation presents the script as limited to a non-LLM role while the implementation materially participates in an LLM workflow by creating request JSON in prepare mode and writing LLM suggestions into Drive in apply mode. That documentation understates the script's effective behavior and intent.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
async function oauthAccessTokenFromRefresh({ refreshToken, clientId, clientSecret }) {
const res = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
async function oauthAccessTokenFromRefresh({ refreshToken, clientId, clientSecret }) {
const res = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
async function oauthAccessTokenFromRefresh({ refreshToken, clientId, clientSecret }) {
const res = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
async function oauthAccessTokenFromRefresh({ refreshToken, clientId, clientSecret }) {
const res = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
The top-level documentation says the script 'only' performs Drive listing, state comparison, and optional header updating. However, the implementation explicitly downloads changed files to /tmp before modifying them, which is a material side effect not reflected in that enumerated behavior.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def sh(cmd: List[str], env: Dict[str, str] | None = None) -> str:
out = subprocess.check_output(cmd, env=env, text=True)
return out
The code passes GOG_ACCOUNT and GOG_KEYRING_PASSWORD into sudo ... env when listing and downloading Drive files, which involves handling sensitive credentials and making networked access to Google Drive. Although the module docstring mentions that gog must already be authenticated, there is no user-facing warning, confirmation, or logging near these credential-using operations explaining that credential environment variables are consumed for remote access.
The comment 'Optionally stamp header on changed files' implies the files themselves are updated. In practice, the script downloads the file, edits a local /tmp copy, and prints that agent upload is still needed; no remote Drive update occurs.
The script downloads remote Drive content to a predictable path under /tmp using the Drive file ID as the filename, without warning users or using secure temporary-file creation. On multi-user systems this can expose sensitive document contents, enable symlink clobbering attacks, or leave recoverable remnants of downloaded files in a shared temporary directory.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
else:
# NOTE: actual upload/in-place update should be implemented by the agent using Drive API files.update.
# We intentionally do not embed secrets/token logic here.
tmp.write_text(new, encoding="utf-8")
print(f"[needs-agent-upload] updated local copy for {f.get('name')} ({fid})")
state["lastScanAtUtc"] = iso_utc_now()
Detected: suspicious.dangerous_exec, suspicious.env_credential_access, suspicious.exposed_secret_literal (+1 more)