Back to skill

Security audit

Google Docs Skill

Security checks for vulnerabilities and agentic risk

Overview

This Google Docs skill is mostly coherent, but its examples expose live OAuth tokens and under-warn about document-changing API access.

Install only if you are comfortable granting Google Docs API access. Do not run the token examples as written in shared terminals, logs, CI, or agent transcripts; avoid printing refresh or access tokens, store credentials in a proper secret store, and revoke any token that may have been exposed.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:83
Finding
OAuth Access and Refresh Tokens Exposed in Console Output<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 83-89 and 111-117 **Vulnerability Type**: Plaintext credential disclosure through process output **Risk Level**: High ### Vulnerable Code ```python req = urllib.request.Request('https://oauth2.googleapis.com/token', data=data) response = json.load(urllib.request.urlopen(req)) print(f"\nRefresh Token: {response['refresh_token']}") print(f"Access Token: {response['access_token']}") print(f"\nSet your refresh token:") print(f"export GOOGLE_REFRESH_TOKEN=\"{response['refresh_token']}\"") ``` The access-token example repeats the exposure: ```python req = urllib.request.Request('https://oauth2.googleapis.com/token', data=data) response = json.load(urllib.request.urlopen(req)) return response['access_token'] # Store for reuse access_token = get_access_token() print(f"Access Token: {access_token}") ``` ### Technical Analysis The documented OAuth workflow prints both the short-lived access token and long-lived refresh token in plaintext. Although sending credentials to Google's official OAuth token endpoint is necessary for the declared functionality, displaying the returned credentials is not necessary. Console output can be retained by shell capture, CI/CD logs, agent transcripts, terminal session recording, debugging systems, or centralized log collectors. The refresh token is especially sensitive because it can repeatedly generate new access tokens until it is revoked or expires. The requested OAuth scope is `https://www.googleapis.com/auth/documents`, so a compromised token may authorize access to Google Docs resources available under that scope. ### Attack Path 1. A user follows the instructions and runs the OAuth setup or access-token example. 2. The script prints the access token and refresh token to standard output. 3. The output is retained in a terminal transcript, automation log, screen recording, support bundle, or agent conversation. 4. An attacker with access to that outpu ...[truncated 779 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove every statement that prints access tokens or refresh tokens. - Store refresh tokens in an operating-system credential manager or managed secret store rather than displaying them for manual copying. - If a local token file is unavoidable, restrict it to the owning user and document its sensitivity. - Redact authorization headers, token responses, and environment-variable values from logs and exception reports. - Keep access tokens only in memory for the minimum required duration. - Revoke and regenerate tokens if they may already have appeared in retained logs. - Prefer Google's maintained OAuth libraries, which provide safer token persistence and refresh behavior. - Update the example to print only a success message, such as `OAuth authorization completed successfully`. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:42
Finding
OAuth Callback Is Not Correlated with State and Does Not Use PKCE<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 42-83 **Vulnerability Type**: Insufficient OAuth authorization-response validation **Risk Level**: Medium ### Vulnerable Code ```python auth_url = ( f"https://accounts.google.com/o/oauth2/v2/auth?" f"client_id={CLIENT_ID}&" f"redirect_uri={REDIRECT_URI}&" f"response_type=code&" f"scope={urllib.parse.quote(SCOPES)}&" f"access_type=offline&" f"prompt=consent" ) print(f"Opening browser for authorization...") webbrowser.open(auth_url) # Step 2: Capture authorization code auth_code = None class OAuthHandler(BaseHTTPRequestHandler): def do_GET(self): global auth_code query = urllib.parse.urlparse(self.path).query params = urllib.parse.parse_qs(query) auth_code = params.get('code', [None])[0] self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(b'<html><body><h1>Authorization successful!</h1><p>You can close this window.</p></body></html>') server = HTTPServer(('localhost', 8080), OAuthHandler) server.handle_request() # Step 3: Exchange code for tokens if auth_code: data = urllib.parse.urlencode({ 'code': auth_code, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, 'redirect_uri': REDIRECT_URI, 'grant_type': 'authorization_code' }).encode() req = urllib.request.Request('https://oauth2.googleapis.com/token', data=data) ``` ### Technical Analysis The authorization request does not include a cryptographically random OAuth `state` value, and the callback handler does not validate that the response corresponds to the authorization request initiated by the script. It accepts the first `code` query parameter received by the local HTTP server. The flow also omits Proof Key for Code Exchange (PKCE). PKCE binds the authorization code to a verifier held by the initiating client and ...[truncated 1980 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Generate a cryptographically random `state` value before opening the authorization URL. - Include `state` in the authorization request and retain it only for the active authorization attempt. - Require the callback's `state` parameter to match the retained value using a constant-time comparison; reject missing or mismatched values. - Implement PKCE using a high-entropy `code_verifier` and its corresponding SHA-256 `code_challenge`. - Include `code_challenge_method=S256` in the authorization request and submit the original `code_verifier` during token exchange. - Validate the callback path and handle OAuth `error` responses explicitly. - Stop the listener after the expected validated callback and reject unsolicited or duplicate callbacks. - Prefer Google's maintained OAuth client libraries instead of implementing the authorization flow manually. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (13)

Credential Access

High
Category
Privilege Escalation
Content
response = json.load(urllib.request.urlopen(req))
    
    print(f"\nRefresh Token: {response['refresh_token']}")
    print(f"Access Token: {response['access_token']}")
    print(f"\nSet your refresh token:")
    print(f"export GOOGLE_REFRESH_TOKEN=\"{response['refresh_token']}\"")
```
Confidence
99% confidence
Finding
This example prints live OAuth refresh and access tokens directly, exposing secrets that can be reused to access the user's Google Docs data. A leaked refresh token is especially dangerous because it can be exchanged for fresh access tokens long after the initial session ends.

Credential Access

High
Category
Privilege Escalation
Content
print(f"export GOOGLE_REFRESH_TOKEN=\"{response['refresh_token']}\"")
```

### Getting Access Token

Before making API calls, get a fresh access token:
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### Getting Access Token

Before making API calls, get a fresh access token:

```python
import urllib.request
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### Getting Access Token

Before making API calls, get a fresh access token:

```python
import urllib.request
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### Getting Access Token

Before making API calls, get a fresh access token:

```python
import urllib.request
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### Getting Access Token

Before making API calls, get a fresh access token:

```python
import urllib.request
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### Getting Access Token

Before making API calls, get a fresh access token:

```python
import urllib.request
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### Getting Access Token

Before making API calls, get a fresh access token:

```python
import urllib.request
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Store for reuse
access_token = get_access_token()
print(f"Access Token: {access_token}")
```

## Base URL
Confidence
98% confidence
Finding
This example prints the access token after retrieval, making it easy to leak via logs or console history. Even short-lived access tokens can grant unauthorized document access during their validity period.

Credential Access

High
Category
Privilege Escalation
Content
| 429 | Rate Limited - Too many requests |

### Token Refresh
Access tokens expire after 1 hour. If you get a 401 error, refresh the token:

```python
try:
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## Best Practices

1. **Token Management**
   - Cache access tokens (valid for 1 hour)
   - Store refresh token securely
   - Implement automatic token refresh
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill describes creating, reading, and modifying Google Docs content but does not clearly warn that document data will be transmitted to Google and that API operations can alter remote documents. This omission can mislead users about data handling and action side effects, increasing the chance of unintended disclosure or destructive changes.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The documentation explicitly prints both the refresh token and access token to stdout, which can expose credentials through terminal history, logs, screen recording, or shared sessions. Because refresh tokens enable ongoing API access, this creates a realistic credential leakage risk even though it appears intended as a convenience example.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
SKILL.md:116