Back to skill

Security audit

LegiScan Bill Search

Security checks for vulnerabilities and agentic risk

Overview

This skill is a straightforward LegiScan bill-search helper, with a real but limited API-key handling risk.

Before installing, use a dedicated revocable LegiScan API key, avoid logging request URLs, and be aware that your configured key, state, and search keywords are sent to LegiScan. Scheduling it with cron is a user choice and should be done only at a frequency compatible with your API quota.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
search.py:12
Finding
API Credential Exposed in Request URLs<![CDATA[ ## Vulnerability Details **File Location**: `search.py:12-14` and `search.py:35-37` **Vulnerability Type**: API credential exposure through URL query parameters **Risk Level**: Medium ### Vulnerable Code ```python url = f"https://api.legiscan.com/?key={api_key}&op=getSearch&state={state}&query={query_param}" try: response = requests.get(url).json() ``` ```python url = f"https://api.legiscan.com/?key={api_key}&op=getBill&id={bill_id}" try: res = requests.get(url).json() ``` ### Technical Analysis The Skill reads `LEGISCAN_API_KEY` from the environment and interpolates it directly into URLs sent to the official LegiScan API. Network access and authentication to LegiScan are necessary for the declared bill-tracking functionality, and the requests use HTTPS. Therefore, this behavior is not evidence of unauthorized data exfiltration. However, credentials in URL query parameters can be exposed through: - LegiScan access logs - Forward-proxy or TLS-inspection logs - HTTP debugging and application-performance monitoring systems - Exception telemetry that records request URLs - Shell, test, or diagnostic output added around the HTTP client The user-controlled state and keyword values are also manually interpolated rather than passed through the HTTP client's parameter-encoding interface. Although this does not create a demonstrated command-execution path, it makes request construction less robust. ### Attack Path 1. A user configures `LEGISCAN_API_KEY` and runs the Skill. 2. The Skill constructs a request URL containing the complete API key. 3. A server, proxy, monitoring agent, debugger, or telemetry platform records the requested URL. 4. An attacker or unauthorized operator with access to those records extracts the `key` query parameter. 5. The attacker submits requests to LegiScan using the recovered credential. This path requires access to infrastructure that records request URLs; the project itself does not intentionally print or trans ...[truncated 647 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use an authentication header instead of a query parameter if the LegiScan API supports one: ```python endpoint = "https://api.legiscan.com/" headers = {"Authorization": f"Bearer {api_key}"} params = { "op": "getSearch", "state": state, "query": query_param, } response = requests.get( endpoint, headers=headers, params=params, timeout=15, ) response.raise_for_status() data = response.json() ``` 2. If LegiScan requires the key in the query string: - Use `params` for correct parameter encoding. - Configure proxies, telemetry, and HTTP debugging tools to redact the `key` parameter. - Never log `response.request.url` without sanitization. - Use a dedicated, revocable API key with the lowest available quota and privileges. - Rotate the key immediately if URLs containing it may have been retained in logs. 3. Add explicit request timeouts and status handling so network failures cannot leave scheduled executions indefinitely blocked: ```python endpoint = "https://api.legiscan.com/" params = { "key": api_key, "op": "getBill", "id": bill_id, } response = requests.get(endpoint, params=params, timeout=15) response.raise_for_status() data = response.json() ``` 4. Validate state input against valid two-letter state codes and reject empty keyword entries before making requests. This reduces malformed or unintended API operations, although it does not replace credential redaction. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Tainted flow: 'url' from os.environ.get (line 62, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
query_param = f'"{q}"' if ' ' in q and not q.startswith('"') else q
        url = f"https://api.legiscan.com/?key={api_key}&op=getSearch&state={state}&query={query_param}"
        try:
            response = requests.get(url).json()
            if response.get('status') == 'OK':
                search_results = response.get('searchresult', {})
                for key, bill in search_results.items():
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.environ.get (line 62, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
def get_bill_details(api_key, bill_id):
    url = f"https://api.legiscan.com/?key={api_key}&op=getBill&id={bill_id}"
    try:
        res = requests.get(url).json()
        if res.get('status') == 'OK':
            return res.get('bill')
    except Exception as e:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

External Transmission

Medium
Category
Data Exfiltration
Content
for q in queries:
        # Ensure query is properly quoted if it contains spaces
        query_param = f'"{q}"' if ' ' in q and not q.startswith('"') else q
        url = f"https://api.legiscan.com/?key={api_key}&op=getSearch&state={state}&query={query_param}"
        try:
            response = requests.get(url).json()
            if response.get('status') == 'OK':
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
for q in queries:
        # Ensure query is properly quoted if it contains spaces
        query_param = f'"{q}"' if ' ' in q and not q.startswith('"') else q
        url = f"https://api.legiscan.com/?key={api_key}&op=getSearch&state={state}&query={query_param}"
        try:
            response = requests.get(url).json()
            if response.get('status') == 'OK':
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.