Back to skill

Security audit

petTracer pet tracking

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to perform PetTracer tracking as advertised, but it handles sensitive credentials and live location data with overly broad endpoint controls.

Review before installing. Use this only in a trusted agent environment, do not set custom PetTracer API or WebSocket base URLs unless you control and trust them, prefer environment-injected short-lived tokens over passwords, avoid passing passwords on the command line, and treat all stdout, generated PNGs, map links, and history/live output as sensitive location data.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/pettracer_cli.py:41
Finding
Configurable REST API Origin Can Exfiltrate PetTracer Credentials and Bearer Tokens<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pettracer_cli.py:41-42, 243-255, 339-366`; equivalent behavior also appears in `scripts/pettracer_watch.py:36-37, 64-77, 100-122` **Vulnerability Type**: Unvalidated security-sensitive endpoint configuration **Risk Level**: High ### Vulnerable Code ```python DEFAULT_API_BASE_URL = "https://portal.pettracer.com/api" API_BASE_URL = os.getenv("PETTRACER_API_BASE", DEFAULT_API_BASE_URL).rstrip("/") ``` ```python url = endpoint_or_url if endpoint_or_url.startswith("/"): url = f"{API_BASE_URL}{endpoint_or_url}" headers = { "Accept": "application/json, text/plain, */*", "Content-Type": "application/json", "User-Agent": USER_AGENT, "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", } if token: headers["Authorization"] = f"Bearer {token}" ``` ```python def get_token_or_login(*, username: Optional[str], password: Optional[str], timeout_s: int, retries: int) -> str: """Return bearer token from env or by logging in.""" token = _env_first("PETTRACER_TOKEN") if token: return token username = username or _env_first("PETTRACER_USERNAME", "PETTRACER_EMAIL") password = password or _env_first("PETTRACER_PASSWORD") if not username or not password: raise PetTracerAuthError( "Missing credentials. Set PETTRACER_TOKEN or (PETTRACER_USERNAME/PETTRACER_EMAIL + PETTRACER_PASSWORD)." ) payload = {"login": username, "password": password} resp = _request("POST", LOGIN_ENDPOINT, json_body=payload, timeout_s=timeout_s, retries=retries) if not isinstance(resp, dict): raise PetTracerAuthError("Login response was not a JSON object.") token = resp.get("access_token") or resp.get("token") or resp.get("id_token") if not token: raise PetTracerAuthError("Login response did not contain an access token.") return str(token) def fetch_devices(*, token: str, timeout_s: int, retries: int) -> List[Dict[str, Any]]: ...[truncated 2573 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fix the production API origin to `https://portal.pettracer.com/api`. 2. If custom API origins are operationally necessary, parse them with `urllib.parse.urlsplit` and enforce: - Scheme exactly equal to `https`. - Hostname exactly equal to an explicit allowlist. - No URL user information. - No fragment. - Only an approved port and path prefix. 3. Reject invalid configuration before reading credentials or constructing an authenticated request. 4. Separate test-server support from production behavior. Require an explicit development-only flag and prohibit production tokens when it is enabled. 5. Apply the same validation centrally to both `pettracer_cli.py` and `pettracer_watch.py` to prevent implementation drift. 6. Consider certificate pinning only if PetTracer's deployment and certificate-rotation process can support it safely. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/pettracer_watch.py:43
Finding
Arbitrary WebSocket Origin Receives the PetTracer Bearer Token in the URL<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pettracer_watch.py:43, 212-241, 412-413, 432` **Vulnerability Type**: Bearer-token exfiltration through an unvalidated WebSocket endpoint **Risk Level**: High ### Vulnerable Code ```python DEFAULT_WS_BASE = os.getenv("PETTRACER_WS_BASE", "wss://pt.pettracer.com/sc").rstrip("/") ``` ```python class SockJsStompClient: def __init__(self, *, ws_base: str, token: str, device_ids: List[int], verbose: bool = False) -> None: self.ws_base = ws_base.rstrip("/") self.token = token self.device_ids = [int(x) for x in device_ids] self.verbose = verbose self._running = True # Lazily imported dependency try: import aiohttp # noqa: F401 except Exception as e: raise RuntimeError("aiohttp is required. Install with: pip install aiohttp") from e async def run_forever(self) -> None: import aiohttp backoff_s = 10 backoff_max_s = 60 while self._running: session_id = _rand_session_id() server_id = _rand_server_id() url = f"{self.ws_base}/{server_id}/{session_id}/websocket?access_token={self.token}" if self.verbose: print(f"[pettracer_watch] connecting: {_redact_access_token(url)}", file=sys.stderr) try: async with aiohttp.ClientSession() as session: async with session.ws_connect(url, heartbeat=30) as ws: ``` ```python p.add_argument("--ws-base", default=DEFAULT_WS_BASE, help="SockJS WS base (default: PetTracer).") ``` ### Technical Analysis The live-tracking client accepts a WebSocket base through either `PETTRACER_WS_BASE` or the command-line `--ws-base` option. It does not verify that the scheme is `wss`, that the destination hostname is `pt.pettracer.com`, or that the destination otherwise belongs to PetTracer. The client appends the bearer token as the `access_token` quer ...[truncated 1629 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `--ws-base` and `PETTRACER_WS_BASE` from production operation if endpoint substitution is unnecessary. 2. Otherwise, validate the endpoint before token retrieval: - Require the `wss` scheme. - Require the exact hostname `pt.pettracer.com`. - Restrict the port to the approved TLS port. - Require the expected `/sc` path. - Reject user information, fragments, and ambiguous encoded hostnames. 3. Use an authorization header instead of a query parameter if the PetTracer protocol supports it. 4. If query authentication is mandatory, keep the URL lifetime minimal and ensure it is never exposed to logs, telemetry, proxies, or exception messages. 5. Provide a separate test mode that refuses real PetTracer tokens and requires an explicitly marked test credential. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pettracer_watch.py:234
Finding
Unredacted WebSocket Exceptions May Leak Bearer Tokens to Logs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pettracer_watch.py:234-261` **Vulnerability Type**: Sensitive information exposure through exception logging **Risk Level**: Medium ### Vulnerable Code ```python url = f"{self.ws_base}/{server_id}/{session_id}/websocket?access_token={self.token}" if self.verbose: print(f"[pettracer_watch] connecting: {_redact_access_token(url)}", file=sys.stderr) try: async with aiohttp.ClientSession() as session: async with session.ws_connect(url, heartbeat=30) as ws: if self.verbose: print("[pettracer_watch] websocket connected", file=sys.stderr) # Reset backoff after a successful connection. backoff_s = 10 async for msg in ws: if not self._running: break if msg.type == aiohttp.WSMsgType.TEXT: await self._handle_sockjs_frame(ws, msg.data) elif msg.type in (aiohttp.WSMsgType.ERROR, aiohttp.WSMsgType.CLOSED): if self.verbose: print(f"[pettracer_watch] websocket closed/error: {msg.type}", file=sys.stderr) break except asyncio.CancelledError: break except Exception as e: print(f"[pettracer_watch] connection error: {e}", file=sys.stderr) ``` ### Technical Analysis The code correctly redacts the token when explicitly printing the connection URL in verbose mode. However, exceptions raised by `aiohttp`, TLS handling, proxies, DNS resolution, handshake failures, or an unexpected server are converted directly to text and printed without applying the same redaction. HTTP and WebSocket client exceptions can include the request URL. Because the URL contains the bearer token in its query string, a failed connection may cause the token to be written to standard error. Standard error is commonly captured by agent runtimes, service managers, CI systems, shell redirection, and centralize ...[truncated 999 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Redact all exception text before logging: ```python print( f"[pettracer_watch] connection error: {_redact_access_token(str(e))}", file=sys.stderr, ) ``` 2. Generalize redaction to cover case variations, percent-encoded forms, and authorization headers. 3. Prefer structured error codes over printing complete third-party exception objects. 4. Avoid embedding credentials in URLs if protocol support permits header-based authentication. 5. Add tests that simulate DNS, TLS, proxy, and WebSocket handshake failures and assert that the token never appears in captured output. 6. Configure log processors to apply defense-in-depth redaction for `access_token`, `Authorization`, and known token formats. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/requirements.txt:1
Finding
Unbounded and Unhashed aiohttp Dependency Creates Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `scripts/requirements.txt:1`; installation is instructed at `SKILL.md:144-150` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Medium ### Vulnerable Code ```text aiohttp>=3.8 ``` The Skill directs users or agents to install this dependency: ```bash # Install dependency pip install -r scripts/requirements.txt # Stream updates python scripts/pettracer_watch.py --pet "Fluffy" ``` ### Technical Analysis The requirement specifies only a lower version bound. Every future `aiohttp` release satisfying `>=3.8` is therefore eligible for installation. No lock file, upper bound, exact version, package hash, or trusted-index constraint is provided. This makes installation non-reproducible and causes the effective code executed by the Skill to depend on the package index state at installation time. A compromised upstream release, compromised package index, unsafe future release, or maliciously configured package mirror could introduce arbitrary code into the agent environment. This is a supply-chain weakness rather than evidence that the current `aiohttp` package is malicious. ### Attack Path 1. The agent follows the documented command and runs `pip install -r scripts/requirements.txt`. 2. Pip queries its configured package index or mirror. 3. The resolver selects any available release satisfying `aiohttp>=3.8`. 4. A compromised index, mirror, account, or future release supplies malicious package content. 5. Package installation or subsequent import executes attacker-controlled code in the agent's Python environment. 6. The malicious dependency inherits the process's access to PetTracer credentials, map API keys, location data, files, and network connectivity. ### Impact Assessment Compromised dependency code runs with the same operating-system privileges as the user or agent executing the Skill. It could potentially: - Read `PETTRACER_PASSWORD`, `PETTRACER_TOKEN`, and `GO ...[truncated 416 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `aiohttp` and its transitive dependencies to reviewed exact versions. 2. Generate and commit a lock file using a dependency-management tool such as `pip-tools`, Poetry, or uv. 3. Record cryptographic hashes and install with `pip --require-hashes`. 4. Use an explicitly trusted package index and prevent unintended fallback to untrusted mirrors. 5. Review dependency advisories regularly and update pins through a controlled testing process. 6. Install the dependency in an isolated virtual environment under a non-privileged account. 7. Avoid recommending global or elevated `pip` installation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pettracer_cli.py:763
Finding
Command-Line Password Options Expose PetTracer Credentials Through Process Metadata<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pettracer_cli.py:763-777`; the same option appears in `scripts/pettracer_mapshot.py:199-201` and `scripts/pettracer_watch.py:414-415` **Vulnerability Type**: Sensitive credential exposure through command-line arguments **Risk Level**: Medium ### Vulnerable Code ```python p.add_argument("--username", help="PetTracer login (prefer env var PETTRACER_USERNAME).") p.add_argument("--password", help="PetTracer password (prefer env var PETTRACER_PASSWORD).") ``` ```python common_global = argparse.ArgumentParser(add_help=False) common_global.add_argument("--timeout-s", type=int, default=DEFAULT_TIMEOUT_S) common_global.add_argument( "--retries", type=int, default=int(os.getenv("PETTRACER_RETRIES", str(DEFAULT_RETRIES))), ) common_global.add_argument("--username") common_global.add_argument("--password") ``` Equivalent options are exposed by the other scripts: ```python p.add_argument("--username", help="PetTracer login (prefer env var PETTRACER_USERNAME).") p.add_argument("--password", help="PetTracer password (prefer env var PETTRACER_PASSWORD).") ``` ### Technical Analysis Although the documentation prefers environment variables, all three executable scripts accept a plaintext PetTracer password as a command-line argument. Command-line arguments may be exposed through process listings, process inspection interfaces, shell history, job-control systems, agent execution logs, CI metadata, audit tooling, and crash reports. The password remains present in the process argument vector for the lifetime of the command. On systems where other users or monitoring agents can inspect command lines, this unnecessarily broadens credential visibility. ### Attack Path 1. A user or agent invokes a script with `--password secret`. 2. The shell records the command in history or an orchestration platform records the invocation. 3. While the process is running, another local user, monitoring agent, or proce ...[truncated 774 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--password` option from all scripts. 2. Support an interactive hidden prompt using `getpass.getpass()` when standard input is attached to a terminal. 3. For automation, use a dedicated secret manager, protected file descriptor, or narrowly scoped environment injection. 4. If environment variables remain supported, document that they may also be visible to same-user processes on some platforms and should be injected only into the target process. 5. Prefer short-lived, narrowly scoped PetTracer tokens over reusable account passwords where the service permits it. 6. Ensure agent runtimes and CI systems redact credential fields from command traces and execution metadata. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (17)

Tainted flow: 'req' from os.getenv (line 84, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = Request(url, data=data, headers=headers, method=method.upper())
    try:
        with urlopen(req, timeout=timeout_s) as resp:
            raw = resp.read().decode("utf-8")
            return json.loads(raw)
    except HTTPError as e:
Confidence
90% confidence
Finding
The script allows API and WebSocket base URLs to be fully overridden via environment variables and then sends bearer tokens and/or user credentials to those endpoints. In an agent or multi-tenant environment, a malicious or poisoned environment configuration could redirect requests to an attacker-controlled host, causing credential and token exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This skill introduces an additional external dependency on Google Maps Static API beyond the core PetTracer tracking function, but that dependency is not clearly reflected in an enforceable permission/resource declaration. Hidden or underdeclared third-party data flows are risky because precise pet GPS coordinates may be transmitted to another vendor without clear disclosure or policy control.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
This skill introduces an additional external dependency on Google Maps Static API beyond the core PetTracer tracking function, but that dependency is not clearly reflected in an enforceable permission/resource declaration. Hidden or underdeclared third-party data flows are risky because precise pet GPS coordinates may be transmitted to another vendor without clear disclosure or policy control.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
This skill introduces an additional external dependency on Google Maps Static API beyond the core PetTracer tracking function, but that dependency is not clearly reflected in an enforceable permission/resource declaration. Hidden or underdeclared third-party data flows are risky because precise pet GPS coordinates may be transmitted to another vendor without clear disclosure or policy control.

Ae1

High
Category
analysis-evasion
Content
pip install -r scripts/requirements.txt
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
If Google Maps Static API isn’t viable, alternatives include:

- **Mapbox Static Images API** (needs Mapbox access token)
- **OSM-derived static map services** (often free/cheap, but you must respect provider terms; OSM Foundation tile servers are best-effort and can block heavy usage)

If you need one of these alternatives, extend `pettracer_mapshot.py` with a `--provider` switch.
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
raise RuntimeError("Login response was not a JSON object.")
    token = resp.get("access_token") or resp.get("token") or resp.get("id_token")
    if not token:
        raise RuntimeError("Login response did not contain an access token.")
    return str(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
raise RuntimeError("Login response was not a JSON object.")
    token = resp.get("access_token") or resp.get("token") or resp.get("id_token")
    if not token:
        raise RuntimeError("Login response did not contain an access token.")
    return str(token)
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill requires access to environment secrets and outbound network communication but does not declare any explicit tool scope or allowed-tools boundaries. In an agent environment, this increases the risk of overbroad execution, secret access, and unintended external requests because the runtime cannot enforce least privilege from the manifest alone.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The activation guidance uses broad natural-language triggers such as asking where a pet is or requesting a map image, which can cause over-triggering in loosely related conversations. In a location-tracking skill, accidental activation is more dangerous than usual because it may access sensitive geolocation data and account inventory without sufficiently explicit user intent.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The documentation instructs users how to authenticate and retrieve precise pet location and history data, but it provides no guidance on protecting credentials, bearer tokens, or sensitive GPS information. In the context of a tracking skill, this increases the chance that implementers will log tokens, expose screenshots or history to unintended recipients, or mishandle highly sensitive location data tied to a household.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script sends precise pet GPS coordinates to the Google Maps Static API to render a map image, but it provides no user-facing disclosure, consent check, or privacy control before transferring that location data to a third-party service. Because this skill handles sensitive real-world location information, that silent external sharing creates a meaningful privacy and data-handling risk, especially in an agent context where users may not realize a map screenshot requires disclosure of coordinates to Google.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The script prints live pet telemetry, including device identifiers and precise location data, directly to stdout as NDJSON. In agent workflows, stdout is commonly logged, persisted, or forwarded, so this can unintentionally disclose sensitive real-time location information without explicit user confirmation or data-handling safeguards.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The request headers hard-code `Accept-Language` to `en-GB,en-US;q=0.9,en;q=0.8`, which imposes a specific language/locale preference in outbound API calls. This is a natural-language policy concern because the skill does not offer any user choice or documented opt-in for locale selection.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The request headers hard-code "Accept-Language: en-GB,en-US;q=0.9,en;q=0.8", which imposes a specific language/locale preference. Under the policy, locale constraints should be optional, user-selectable, or clearly justified as region-specific; none of those conditions are present here.

Unpinned Dependencies

Low
Category
Supply Chain
Content
aiohttp>=3.8
Confidence
98% confidence
Finding
The dependency is specified as `aiohttp>=3.8`, which allows installation of any future version and does not guarantee reproducible or reviewed builds. This creates supply-chain risk because vulnerable or breaking releases could be pulled in implicitly, and it is impossible to verify from the manifest whether a safe version is consistently used.

Unverifiable Dependency: aiohttp has 16 known advisory(ies) (CVE-2024-52303 (aiohttp has a memory leak when middleware is enabled when requesting a resource ); CVE-2026-54279 (aiohttp: Host-Only Cookies Become Domain Cookies After CookieJar Persistence); CVE-2026-34514 (AIOHTTP has CRLF injection through multipart part content type header constructi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
94% confidence
Finding
The manifest references `aiohttp` without pinning to a specific version, while the package has multiple known advisories. In a network-facing skill that tracks pet location and likely performs authenticated API calls and image retrieval, using an unpinned version means a vulnerable release could be installed, potentially exposing request handling, cookies, or headers to known flaws.