T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/mastodon_scout.py:78
- Finding
- OAuth Bearer Token Can Be Disclosed to an Arbitrary or Plaintext Endpoint## Vulnerability Details **File Location**: `scripts/mastodon_scout.py:78-84`, `scripts/mastodon_scout.py:105-116`, and credential-bearing call sites through `scripts/mastodon_scout.py:146` **Related Documentation**: `SKILL.md:27` and `SKILL.md:45-46` **Vulnerability Type**: Unvalidated credential-bearing network destination **Risk Level**: High ### Vulnerable Code The API helper attaches the OAuth token to every supplied URL: ```python def api_get(url, token): req = urllib.request.Request(url, headers={ 'Authorization': f'Bearer {token}', 'Accept': 'application/json', }) try: with urllib.request.urlopen(req) as resp: return resp.status, resp.read().decode('utf-8') except urllib.error.HTTPError as e: return e.code, e.read().decode('utf-8') ``` The destination is controlled by a command-line option or environment variable without validating its scheme or origin: ```python parser.add_argument('--instance', default=os.environ.get('MASTODON_INSTANCE', 'https://mastodon.social')) parser.add_argument('--limit', type=int, default=int(os.environ.get('LIMIT', '20'))) parser.add_argument('--json', action='store_true', dest='raw_json') args = parser.parse_args() token = os.environ.get('MASTODON_TOKEN', '') if not token: print('Error: MASTODON_TOKEN is not set', file=sys.stderr) sys.exit(1) base = args.instance.rstrip('/') if args.command == 'home': status, body = api_get(f'{base}/api/v1/timelines/home?limit={args.limit}', token) ``` Other commands similarly send the token to URLs derived from `base`: ```python elif args.command == 'user-tweets': status, body = api_get(f'{base}/api/v1/accounts/verify_credentials', token) check(status, body) acct_id = json.loads(body)['id'] status, body = api_get(f'{base}/api/v1/accounts/{acct_id}/statuses?limit={args.limit}', token) elif args.command == 'mentions ...[truncated 3772 chars]
- Remediation
- ## Remediation Suggestions 1. **Require HTTPS** - Parse the instance with `urllib.parse.urlsplit`. - Reject every scheme other than `https`. - Reject missing hostnames, URL fragments, and embedded usernames or passwords. - Permit HTTP only through an explicit development-only override that never uses production credentials. 2. **Bind credentials to an exact origin** - Store each token together with the exact normalized scheme, hostname, and port of the issuing instance. - Refuse to send a token when the requested origin differs from the token's configured origin. - Prefer instance-specific variables or configuration records instead of one global token combined with an arbitrary URL. 3. **Constrain redirects** - Disable automatic redirects for authenticated API requests or implement a custom redirect handler. - If redirects are needed, follow them only when the destination has the same validated HTTPS origin. - Never forward the `Authorization` header across origins. 4. **Validate the instance before constructing API URLs** - Accept only an origin, not arbitrary paths or query strings. - Normalize internationalized hostnames and ports before comparison. - Consider an explicit trusted-instance allowlist for automated agent environments. 5. **Require informed confirmation for origin changes** - Display the exact normalized destination hostname before first use. - Require explicit user approval before associating a token with a new instance. - Do not infer permission to transmit credentials merely because an untrusted prompt supplied `--instance`. 6. **Apply defense in depth** - Continue recommending minimal read-only OAuth scopes. - Document that Mastodon tokens are instance-specific and must not be reused across unrelated hosts. - Avoid logging authorization headers and redact tokens from all errors. - Provide token revocation and rotation instructions ...[truncated 24 chars]
