T09 · Insecure Skill Coding Practices
- Location
scripts/questrade_cli.py:154- Finding
OAuth token files are written without explicitly restrictive permissions
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This is a disclosed Questrade API skill, but it needs review because it can submit or cancel brokerage orders with partner credentials and its safeguards for financial write actions are weak.
Install only if you understand that this skill can access sensitive Questrade account data and, with partner-level credentials, may place or cancel real orders. Keep QUESTRADE_READ_ONLY=true unless you intentionally need trading access, avoid using --force for live accounts, protect the ~/.openclaw credential files, and prefer a pinned dependency set.
scripts/questrade_cli.py:154OAuth token files are written without explicitly restrictive permissions
scripts/questrade_cli.py:80Trading protections fail open and can be bypassed with the force option
requirements.txt:2Third-party dependency is not reproducibly pinned
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
# Questrade docs show this as a GET with query params; POST with params= is
# equivalent (params become the query string either way) and aligns with
# RFC 6749 which recommends POST for token endpoints.
resp = requests.post(
login_url,
params={"grant_type": "refresh_token", "refresh_token": refresh_token},
timeout=15,
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
> **Important:** Questrade refresh tokens *rotate* — every time the token is
> used to obtain a new access token, Questrade issues a new refresh token.
> This script automatically saves the new refresh token back to the credentials
> file, so you only need to paste the initial token once.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
```
> **Important:** Questrade refresh tokens *rotate* — every time the token is
> used to obtain a new access token, Questrade issues a new refresh token.
> This script automatically saves the new refresh token back to the credentials
> file, so you only need to paste the initial token once.
The skill includes support for order cancellation and placement against brokerage endpoints, which are high-risk state-changing operations in a financial context. Although the documentation notes personal tokens cannot use these endpoints, the capability becomes dangerous if partner credentials are present, since prompt abuse or operator error could trigger destructive trading actions.
## Safety Notes
- **Personal API tokens cannot place trades.** Questrade restricts order placement
and cancellation (`POST`/`DELETE /accounts/{id}/orders`) to partner-level API
access only. Attempting either with a personal token returns `403 Forbidden`.
- The `order` and `cancel-order` commands are included for completeness but will
not work unless you have Questrade partner API access.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
- The `order` and `cancel-order` commands are included for completeness but will
not work unless you have Questrade partner API access.
- The refresh token rotates on every use; do **not** share the credentials file.
- Access tokens expire in ~30 minutes; the script caches and refreshes them automatically.
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
---
### DELETE /v1/accounts/{id}/orders/{orderId} *(partner access)*
Cancel an order. Returns updated order detail.
The explicit rm ~/.openclaw/data/questrade-token-cache.json command is a direct local file-deletion action that could be executed by a user or agent without understanding its consequences. In a skill context, this is dangerous because it mixes executable host-level remediation with passive API documentation, creating a path for unintended local state manipulation.
Delete the token cache file and retry — the CLI will refresh automatically:
rm ~/.openclaw/data/questrade-token-cache.json
If the refresh token itself is expired (7-day window), generate a new one from
The script caches live access tokens to a JSON file on disk, and no permission hardening is visible around file creation. Access tokens grant API access for their lifetime, so if another local user, process, backup system, or malware can read the file, they may gain temporary unauthorized access to account and trading data, and potentially trading actions depending on scope.
# Questrade refresh tokens rotate — save the new one
_save_refresh_token(new_refresh, practice)
# Cache access token (subtract 60 s as buffer)
TOKEN_CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
expires_at = datetime.now(timezone.utc) + timedelta(seconds=max(expires_in - 60, 0))
with open(TOKEN_CACHE_FILE, "w") as f:
The skill documents a --force flag that bypasses interactive confirmation for order placement, enabling fully autonomous trade execution if the underlying account has partner-level trading permissions. In a financial skill, removing human approval on a state-changing action materially increases the risk of unintended or prompt-induced transactions.
python3 scripts/questrade_cli.py order sell 12345678 8049 5 StopLimit --stop-price 140.00 --limit-price 139.50
python3 scripts/questrade_cli.py order buy 12345678 8049 10 Market --force
The reference documents order placement and cancellation endpoints without a prominent warning that these actions can affect real brokerage accounts and financial positions. In an agent or automation context, omission of that warning increases the chance of accidental live trading or cancellation when users assume the examples are informational only.
The document includes a destructive local shell command to delete an authentication cache file, which is unrelated to the remote API specification itself and encourages users or downstream agents to modify local state. In an agent setting, copying such remediation text into automated execution paths could cause unintended deletion of credentials or authentication state on the host machine.
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
def _invalidate_config_cache() -> None:
"""Clear the config cache after a write so the next read is fresh."""
global _config_cache
_config_cache = None
The script automatically persists rotated refresh tokens to disk without an explicit user-facing warning or consent at write time. Because refresh tokens are long-lived credentials, silent storage increases the risk of credential theft from local compromise, backups, shared home directories, or permissive file permissions, especially in an agent-skill environment that may run unattended.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
def api_post(path: str, body: Optional[dict] = None) -> dict:
base, headers = _auth()
resp = requests.post(
f"{base}{path}",
headers={**headers, "Content-Type": "application/json"},
json=body,
Order cancellation is a destructive financial action, yet the cancel-order command executes immediately once invoked, with no confirmation prompt, dry-run mode, or explicit warning. In an agent or automation context, a mistaken command, argument mix-up, or prompt-injection-driven invocation could cancel legitimate open orders and materially affect trading outcomes.
The token-cache deletion instruction omits a warning that it removes local authentication state and may disrupt access until credentials are refreshed. While not inherently malicious, this can mislead users or agents into performing unnecessary destructive local actions during routine error handling.
The dependency specification uses a lower-bound range (requests>=2.28.0) instead of pinning to an exact version, which makes builds non-reproducible and can unexpectedly pull in different package versions over time. In a security context this increases supply-chain risk and makes it harder to verify whether known-vulnerable or regressed releases are being installed.
# Python 3.9+ required (uses zoneinfo, f-strings, typing.Optional)
requests>=2.28.0
Because requests is not pinned, the manifest does not prove which concrete release will be installed, so you cannot determine whether deployment will include a version affected by one of the known advisories. This is dangerous primarily as an uncertainty and supply-chain hygiene issue: depending on environment and install time, a vulnerable version could be selected without visibility.
No suspicious patterns detected.