T09 · Insecure Skill Coding Practices
- Location
scripts/fgo_cli.py:82- Finding
Configurable API Base URL Allows Sensitive Invoice Data Exfiltration
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill is a legitimate FGO invoicing helper, but it gives an agent high-impact accounting powers with weak safeguards around API destination, destructive invoice actions, and sensitive debug output.
Install only if you trust the publisher and can run it in a constrained environment. Keep FGO credentials out of prompts and logs, avoid FGO_DEBUG and full payload output with real customer data, use UAT for testing, verify the API host before every real operation, and require human approval before issuing, cancelling, deleting, or reversing invoices.
scripts/fgo_cli.py:82Configurable API Base URL Allows Sensitive Invoice Data Exfiltration
scripts/fgo_cli.py:158Debug and Inspection Modes Disclose Authentication Hashes and Invoice PII
scripts/fgo_cli.py:607Destructive Invoice Operations Execute Without Explicit Confirmation Gates
The request target is derived from configurable environment/CLI input via base_url and then used directly in urlopen, so invoice payloads and authentication material can be transmitted to an arbitrary host. In this skill’s context, that is especially dangerous because the form body includes CodUnic, computed auth hashes, and full invoice/customer data, enabling exfiltration or misuse through SSRF-like redirection of trusted automation.
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())
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
The `--input` argument is validated before any file is read:
1. **Extension check** — only `.json` files are accepted. Passing `/etc/passwd`, `~/.ssh/id_rsa`, or any non-JSON path raises an error immediately.
2. **Path confinement** — the resolved path must be within the current working directory or a recognised OpenClaw media root (`/tmp/openclaw`, `~/.openclaw/workspace`, etc.). Paths that escape these roots via `../` traversal or absolute references are rejected.
Always pass `--input` with a path to a file you created (e.g. a temp file written in the agent workspace). Never set `--input` to a path supplied by untrusted external content.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
The `--input` argument is validated before any file is read:
1. **Extension check** — only `.json` files are accepted. Passing `/etc/passwd`, `~/.ssh/id_rsa`, or any non-JSON path raises an error immediately.
2. **Path confinement** — the resolved path must be within the current working directory or a recognised OpenClaw media root (`/tmp/openclaw`, `~/.openclaw/workspace`, etc.). Paths that escape these roots via `../` traversal or absolute references are rejected.
Always pass `--input` with a path to a file you created (e.g. a temp file written in the agent workspace). Never set `--input` to a path supplied by untrusted external content.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
The `--input` argument is validated before any file is read:
1. **Extension check** — only `.json` files are accepted. Passing `/etc/passwd`, `~/.ssh/id_rsa`, or any non-JSON path raises an error immediately.
2. **Path confinement** — the resolved path must be within the current working directory or a recognised OpenClaw media root (`/tmp/openclaw`, `~/.openclaw/workspace`, etc.). Paths that escape these roots via `../` traversal or absolute references are rejected.
Always pass `--input` with a path to a file you created (e.g. a temp file written in the agent workspace). Never set `--input` to a path supplied by untrusted external content.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
The `--input` argument is validated before any file is read:
1. **Extension check** — only `.json` files are accepted. Passing `/etc/passwd`, `~/.ssh/id_rsa`, or any non-JSON path raises an error immediately.
2. **Path confinement** — the resolved path must be within the current working directory or a recognised OpenClaw media root (`/tmp/openclaw`, `~/.openclaw/workspace`, etc.). Paths that escape these roots via `../` traversal or absolute references are rejected.
Always pass `--input` with a path to a file you created (e.g. a temp file written in the agent workspace). Never set `--input` to a path supplied by untrusted external content.
The CLI accepts --base-url or FGO_API_BASE without constraining the destination, allowing an operator or compromised agent prompt to redirect authenticated invoice operations to an arbitrary server. Because this tool is specifically built to send business-sensitive invoice data externally, that configurability materially increases exfiltration risk in this skill context.
The skill declares access to environment variables, local files, and networked API calls but does not define an explicit tool scope or permission boundary. In an agent setting, that increases the chance the skill is invoked with broader capabilities than necessary, enabling unintended access to secrets or filesystem content during invoicing workflows.
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
- `delete-invoice`
- Delete an invoice via `POST /factura/stergere`.
- `reverse-invoice`
- Create a storno (reversal) invoice via `POST /factura/stornare`.
- `get-nomenclator`
- Fetch a nomenclature list (no auth required): `tara`, `judet`, `tva`, `banca`, `tipincasare`, `tipfactura`, `tipclient`, `valuta`.
The document asserts that input path validation and confinement happen before any file is read, but SKILL.md itself cannot enforce that behavior. If the underlying script does not actually implement these checks exactly as described, an agent may trust the documentation and pass attacker-controlled paths, leading to local file disclosure or processing of unintended files.
The document instructs users to obtain and use a private API key and suggests storing it in environment variables, but provides no guidance on secure storage, rotation, redaction, or log hygiene. In an automation/agent context, this increases the risk that credentials are exposed in prompts, traces, shell history, CI logs, or misconfigured environments, enabling unauthorized invoice operations.
This markdown file documents /factura/stergere and /factura/anulare, which can remove or cancel invoices, but it does not include any warning about data loss, irreversibility, or the need for operator confirmation. For markdown files, SQP-2 applies when descriptions omit warnings about behaviors that could affect user data or system integrity.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
from urllib.parse import urlencode
from urllib.request import Request, urlopen
DEFAULT_BASE_URL = "https://api.fgo.ro/v1"
DEFAULT_TIMEOUT_SECONDS = 30
DEFAULT_RETRIES = 2
RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
from urllib.parse import urlencode
from urllib.request import Request, urlopen
DEFAULT_BASE_URL = "https://api.fgo.ro/v1"
DEFAULT_TIMEOUT_SECONDS = 30
DEFAULT_RETRIES = 2
RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
from urllib.parse import urlencode
from urllib.request import Request, urlopen
DEFAULT_BASE_URL = "https://api.fgo.ro/v1"
DEFAULT_TIMEOUT_SECONDS = 30
DEFAULT_RETRIES = 2
RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
from urllib.parse import urlencode
from urllib.request import Request, urlopen
DEFAULT_BASE_URL = "https://api.fgo.ro/v1"
DEFAULT_TIMEOUT_SECONDS = 30
DEFAULT_RETRIES = 2
RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}
Debug mode logs full request bodies and parsed responses to stderr, which can expose invoice contents, customer PII, invoice identifiers, and API-returned links or status data to logs, terminals, or higher-level agent telemetry. In an automation/agent setting, stderr is often captured centrally, so this exceeds the invoicing purpose and can create durable sensitive-data leakage.
Cancel and delete operations execute immediately once invoked, with no confirmation gate analogous to --allow-final for invoice issuance. In an agent-driven workflow, a mistaken instruction, prompt injection, or misrouted automation step could irreversibly alter accounting records without human acknowledgement.
This code file enables a debug mode that prints full request/response payloads to stderr, and the implementation logs form bodies and response bodies verbatim. Those payloads can include invoice/customer data and authentication-derived request content, but there is no explicit warning in the user-facing help text that debug output may expose sensitive business data in logs or terminal history.
The phrase "Human-readable Romanian error message" indicates a fixed language expectation, which may conflict with language/locale policy when no user opt-in or justification is provided. SQP-3 applies to natural-language statements in any file that force a specific language without choice.
This JSON example uses Romanian-specific field names and locale-bound values such as "Valuta": "RON", "Tara": "Romania", and labels like "Judet" and "Localitate". Because the file provides no accompanying indication that it is intentionally Romania-specific, it may implicitly force a locale without opt-in or documented justification.
No suspicious patterns detected.