Back to skill

Security audit

smartbill

Security checks for vulnerabilities and agentic risk

Overview

This SmartBill invoicing skill is purpose-aligned, but it needs review because it can send credentials and invoice data to an unrestricted API URL and logs sensitive billing data by default.

Review before installing. Use only the official SmartBill HTTPS endpoint, avoid setting SMARTBILL_API_BASE or --base-url unless using isolated test credentials, and assume command stderr/stdout may contain customer, invoice, and response data. Do not run final invoice creation unless you have explicitly confirmed the payload and intended SmartBill account.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/smartbill_cli.py:58
Finding
Arbitrary API Base URL Can Expose SmartBill Credentials and Invoice Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/smartbill_cli.py:58-59, 100-109, 122-154` **Vulnerability Type**: Unrestricted credential and sensitive-data destination **Risk Level**: High ### Vulnerable Code ```python base_url = args.base_url if args.base_url is not None else os.getenv("SMARTBILL_API_BASE", DEFAULT_BASE_URL) ``` ```python return cls( username=username, token=token, base_url=base_url.rstrip("/"), timeout_seconds=timeout, retries=retries, ) ``` ```python class SmartBillClient: def __init__(self, config: ClientConfig): self.config = config auth_value = f"{config.username}:{config.token}".encode("utf-8") self._auth_header = f"Basic {base64.b64encode(auth_value).decode('ascii')}" def _request( self, method: str, path: str, query: Optional[Dict[str, Any]] = None, json_body: Optional[Dict[str, Any]] = None, accept: str = "application/json", expect_binary: bool = False, ) -> Tuple[Any, Dict[str, str]]: url = f"{self.config.base_url}{path}" if query: compact_query = {k: v for k, v in query.items() if v is not None} if compact_query: url = f"{url}?{urlencode(compact_query)}" payload: Optional[bytes] = None if json_body is not None: payload = json.dumps(json_body).encode("utf-8") for attempt in range(self.config.retries + 1): headers = { "Authorization": self._auth_header, "Accept": accept, } if payload is not None: headers["Content-Type"] = "application/json" request = Request(url=url, data=payload, headers=headers, method=method) try: with urlopen(request, timeout=self.config.timeout_seconds) as response: ``` ### Technical Analysis The CLI accepts the API destination from either the `--base-url` argument ...[truncated 2839 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Allowlist the production origin** - Parse the URL with `urllib.parse.urlsplit`. - Require the normalized production origin to be exactly `https://ws.smartbill.ro`. - Require the expected `/SBORO/api` base path. 2. **Require encrypted transport** - Reject all non-HTTPS URLs. - Reject URLs containing user information, fragments, unexpected ports, or malformed hostnames. 3. **Make custom endpoints explicitly unsafe** - If custom endpoints are genuinely required for development, disable them in normal operation. - Require a separate explicit option such as `--allow-unsafe-custom-endpoint`. - Display the normalized destination and require confirmation before attaching credentials. - Use separate test credentials that cannot access production SmartBill data. 4. **Restrict redirects** - Disable redirects for authenticated API calls or permit only same-origin HTTPS redirects. - Never forward the Authorization header when the scheme, hostname, or port changes. 5. **Separate trust domains** - Do not automatically reuse production credentials for custom endpoints. - Require credentials to be explicitly associated with an approved endpoint. 6. **Add security tests** - Verify rejection of HTTP URLs, attacker-controlled hosts, embedded credentials, alternate ports, malformed URLs, and cross-origin redirects. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/smartbill_cli.py:136
Finding
Unconditional Debug Logging Exposes Invoice PII and API Response Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/smartbill_cli.py:136-150, 157-170, 189-202` **Vulnerability Type**: Sensitive information exposure through logs **Risk Level**: Medium ### Vulnerable Code ```python # --- request debug log --- log_entry: Dict[str, Any] = { "smartbill_request": { "attempt": attempt + 1, "method": method, "url": url, "headers": {k: v for k, v in headers.items() if k != "Authorization"}, } } if payload is not None: try: log_entry["smartbill_request"]["body"] = json.loads(payload.decode("utf-8")) except Exception: log_entry["smartbill_request"]["body"] = payload.decode("utf-8", errors="replace") print(json.dumps(log_entry, ensure_ascii=False), file=sys.stderr) ``` ```python # --- response debug log --- try: response_body_log: Any = json.loads(response_body.decode("utf-8")) except Exception: response_body_log = response_body.decode("utf-8", errors="replace") if not expect_binary else f"<binary {len(response_body)} bytes>" print(json.dumps({ "smartbill_response": { "status": response.status, "headers": response_headers, "body": response_body_log, } }, ensure_ascii=False), file=sys.stderr) ``` ```python # --- error response debug log --- try: error_body_log: Any = json.loads(error_body.decode("utf-8")) except Exception: error_body_log = error_body.decode("utf-8", errors="replace") print(json.dumps({ "smartbill_response": { "status": exc.code, "headers": error_headers, "body": error_body_log, } }, ensure_ascii=False), file=sys.stderr) ``` ### Technical Analysis Every network request writes structured debug data to stderr without requiring a debug option. For invoice creation, the logged request body is the complete normalized invoice payload. Typical payload fields include customer names, postal addresses, email addresses, VAT codes, purchased products, prices, and invoice dat ...[truncated 2315 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Disable verbose body logging by default** - Remove unconditional request and response debug output. - Add an explicit `--debug` option if diagnostic logging is necessary. 2. **Apply field-level redaction** - Redact or omit customer names, addresses, email addresses, VAT identifiers, product details, prices, and free-form text. - Use a conservative allowlist of fields rather than attempting to enumerate every sensitive field. 3. **Minimize routine audit logs** - Log only the HTTP method, approved endpoint path, attempt number, status code, duration, rate-limit metadata, and a generated correlation identifier. - After successful creation, record only the minimum invoice identifiers required by the documented workflow. 4. **Sanitize headers and URLs** - Allowlist safe response headers rather than logging all headers. - Redact query parameters containing CIF, series, invoice numbers, or other identifiers. 5. **Protect diagnostic mode** - Display a warning before enabling sensitive diagnostics. - Ensure debug mode remains disabled in automated Agent and CI execution. - Where diagnostic bodies are indispensable, write them to a protected file with restrictive permissions and bounded retention. 6. **Document retention requirements** - Define retention and access-control requirements for logs containing invoice identifiers. - Ensure centralized logging systems do not ingest raw invoice payloads. 7. **Add regression tests** - Assert that stderr and standard logs never contain representative names, emails, addresses, VAT codes, invoice bodies, or raw API responses during normal operation. ]]>
Vulnerability Patterns
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (8)

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

Critical
Category
Data Flow
Content
request = Request(url=url, data=payload, headers=headers, method=method)
            try:
                with urlopen(request, timeout=self.config.timeout_seconds) as response:
                    response_body = response.read()
                    response_headers = dict(response.headers.items())
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill instructs use of a local Python CLI that reads credentials from environment variables, processes local files, can write output PDFs, and performs network calls to the SmartBill API, but it declares no explicit tool scope or permissions boundary. Without a declared allowlist, an agent runtime may expose broader capabilities than intended, increasing the risk of unauthorized access to secrets, unintended file operations, or external requests if the skill is invoked or modified unsafely.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The guidance to log the final SmartBill response can lead to unnecessary retention of customer and invoice data, including identifiers such as invoice number, company/client details, and potentially other business-sensitive fields returned by the API. In an automation skill handling billing data, broad response logging increases exposure through log aggregation systems, support access, and long-term storage, making this a real privacy and data-minimization issue even if not directly exploitable as code execution.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The CLI logs full request and response bodies to stderr for every API call, including invoice payloads and SmartBill responses. These payloads can contain customer names, addresses, VAT codes, product lines, pricing, and other sensitive billing data that may be captured by terminal logs, CI systems, orchestration layers, or agent transcripts without the user's awareness.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
On HTTP errors, the code logs the full error response body and headers to stderr before raising an exception. Error responses from billing APIs often echo submitted invoice/customer data or include account metadata, so this behavior can leak sensitive information into logs even when requests fail.

Natural-Language Policy Violations

Low
Confidence
80% confidence
Finding
This JSON example uses Romanian-specific values such as VAT codes prefixed with "RO", Romanian addresses, country, currency "RON", and localized tax naming. Because the file provides only one locale-specific format and does not document that it is a Romania-specific example, it may implicitly force a specific locale without user choice.

Natural-Language Policy Violations

Low
Confidence
80% confidence
Finding
The file uses locale-specific values such as `country: "Romania"`, Romanian field examples, and Romanian response text like `Factura a fost emisa.` and `Descriere eroare` without indicating that the skill is region-specific or giving the user a locale choice. Under the policy rule, forcing a specific language or locale without opt-in can be a natural-language policy violation.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The command creates parent directories and writes the downloaded invoice PDF to the specified path, which is a file-write operation affecting local user data. Although the command accepts an output path, there is no explicit warning or disclosure in comments/help text about creating directories and writing files on disk.