T09 · Insecure Skill Coding Practices
- Location
rh-client.mjs:17- Finding
OAuth Tokens Stored in the Project Directory Without the Claimed Git Ignore Protection
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill is transparent about connecting an agent to Robinhood trading, but it gives broad financial authority and stores reusable tokens locally with incomplete safeguards.
Review this carefully before installing. Use only with funds you are willing to let an agent manage in the dedicated Agentic account, remember read-only tools may expose information from all Robinhood accounts, run review_equity_order before any trade, and revoke access in Robinhood settings when done. Store RH_TOKEN_FILE outside any repository or shared folder, add .rh-tokens.json and .rh-tokens.json.tmp to gitignore yourself, and treat the token file like a financial credential.
rh-client.mjs:17OAuth Tokens Stored in the Project Directory Without the Claimed Git Ignore Protection
Skill accesses MCP server configuration files (mcp.json). MCP configs contain server URLs, authentication tokens, and tool definitions — reading them allows the skill to discover and potentially abuse other tool integrations.
# Check auth status
node rh-client.mjs status
# Discover available MCP tools
node rh-client.mjs list-tools
# Call a tool
Referenced artifact was not completely inspected
The MCP client (`rh-client.mjs`) handles OAuth 2.1 PKCE authentication to `https://agent.robinhood.com/mcp/trading` and manages token storage/refresh automatica
Skill accesses MCP server configuration files (mcp.json). MCP configs contain server URLs, authentication tokens, and tool definitions — reading them allows the skill to discover and potentially abuse other tool integrations.
Returns a JSON object with `authenticated`, `expired`, `expiresAt`, `hasRefreshToken`, and `savedAt`.
### List available MCP tools
node rh-client.mjs list-tools
Skill accesses MCP server configuration files (mcp.json). MCP configs contain server URLs, authentication tokens, and tool definitions — reading them allows the skill to discover and potentially abuse other tool integrations.
```
Returns a JSON object with `authenticated`, `expired`, `expiresAt`, `hasRefreshToken`, and `savedAt`.
### List available MCP tools
```
node rh-client.mjs list-tools
```
The skill handles access and refresh tokens for Robinhood trading access and stores them locally for automatic reuse. Even though local storage with 0600 permissions is better than plaintext exposure, these credentials grant persistent access to financial operations, so theft, accidental disclosure, or misuse by the agent/runtime could lead to unauthorized account access or trades.
Output depends on the MCP tool response — may be plain text or JSON. Error messages go to stderr.
### Refresh token (auto)
Token refresh happens automatically when the access token is within 5 minutes of expiry (only if a refresh token is available). No manual intervention needed unless the refresh token itself expires.
## What OpenClaw Can Do
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const expiresMs = tokens.expires_in
? Date.now() + Number(tokens.expires_in) * 1000
: Date.now() + 3600 * 1000;
console.error(` Access token expires: ${new Date(expiresMs).toISOString()}`);
if (tokens.refresh_token) {
console.error(' Refresh token: available');
}
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const expiresMs = tokens.expires_in
? Date.now() + Number(tokens.expires_in) * 1000
: Date.now() + 3600 * 1000;
console.error(` Access token expires: ${new Date(expiresMs).toISOString()}`);
if (tokens.refresh_token) {
console.error(' Refresh token: available');
}
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const expiresMs = tokens.expires_in
? Date.now() + Number(tokens.expires_in) * 1000
: Date.now() + 3600 * 1000;
console.error(` Access token expires: ${new Date(expiresMs).toISOString()}`);
if (tokens.refresh_token) {
console.error(' Refresh token: available');
}
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
if (Date.now() > expiresAt - 300_000 && state.tokens.refresh_token) {
// Token expires in < 5 minutes, refresh if we have a refresh token
console.error('🔄 Refreshing access token...');
try {
const freshTokens = await refreshAuthorization(
state.serverInfo.authorizationServerUrl,
The skill exposes code-backed capabilities but does not declare any explicit tool scope such as permissions or allowed-tools. In practice this weakens least-privilege controls and makes it easier for an agent or surrounding runtime to access broader functionality than a user may expect, especially in a trading context where environment data and local execution matter.
The skill explicitly states that OpenClaw can call any tool Robinhood exposes through the MCP server, including order placement and cancellation. That is effectively unrestricted delegated access to a financially sensitive API, so if the agent is prompted maliciously, misaligned, or compromised, it could execute trades or access account data without granular guardrails.
## What OpenClaw Can Do
After auth, OpenClaw can call any tool Robinhood exposes through the MCP server. Based on official docs, the available tools are:
> **Read-only tools** (portfolio, positions, quotes, etc.) work on **all** your Robinhood accounts. **Trading tools** (place/cancel orders) are restricted to the dedicated Agentic account — fund it separately.
OAuth access and refresh tokens are written to a local JSON file on disk. Although the code attempts to set mode 0600, local token persistence still increases exposure to credential theft from malware, backups, shared environments, misconfigured file permissions, or an attacker able to influence RH_TOKEN_FILE to point at a less protected location.
The client connects to a remote HTTPS MCP service and includes the OAuth bearer token in the Authorization header. While network communication is inherent to this client's purpose, the file does not clearly warn users that authenticated requests and tool arguments may be sent to Robinhood's remote endpoint.
The dependency uses a caret range (^1.29.0), which allows newer compatible releases to be installed without explicit review. This weakens build reproducibility and can unintentionally introduce vulnerable or behavior-changing SDK versions into a trading-related client.
"status": "node rh-client.mjs status"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0"
}
}
The manifest references @modelcontextprotocol/sdk without exact pinning, and that package has multiple known advisories affecting some releases. Because this skill brokers access to Robinhood trading via MCP, an affected SDK version could expose cross-client data, enable denial of service, or weaken local-network protections in a financially sensitive context.