Back to skill

Security audit

FGO Invoicing

Security checks for vulnerabilities and agentic risk

Overview

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.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fgo_cli.py:82
Finding
Configurable API Base URL Allows Sensitive Invoice Data Exfiltration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fgo_cli.py:82-84`, `scripts/fgo_cli.py:144-146`, and `scripts/fgo_cli.py:177` **Vulnerability Type**: Unrestricted destination for authenticated network requests **Risk Level**: High ### Complete Code Snippet ```python base_url_arg = getattr(args, "base_url", None) base_url = base_url_arg if base_url_arg is not None else os.getenv( "FGO_API_BASE", DEFAULT_BASE_URL ) ``` ```python 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)}" ``` ```python 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() ``` ### Technical Analysis The CLI permits the API base URL to be supplied through either the global `--base-url` argument or the `FGO_API_BASE` environment variable. It does not validate the URL scheme or restrict the destination hostname. Authenticated FGO operations send form-encoded bodies containing company identifiers, operation-specific authentication hashes, invoice numbers, and potentially customer PII and financial information. Invoice issuance may additionally transmit names, addresses, email addresses, phone numbers, bank details, line items, and pricing. Custom API destinations can be useful during development, but unrestricted production behavior exceeds the minimum network privileges required for the declared FGO functionality. The normal Skill only needs to communicate with the production and UAT FGO hosts. ### Attack Path 1. An attacker influences the process environment, command arguments, wrapper configuration, or agent-generated command. 2. The attacker sets `FGO_API_BASE` or `--base-url` to an attacker-controlled endpoint, such as `https://attacker.example/v1`. 3. A user or agent invokes `emit-invoic ...[truncated 1011 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the configured URL and require the `https` scheme. 2. Allowlist the documented FGO hosts: - `api.fgo.ro` - `api-testuat.fgo.ro` 3. Reject embedded credentials, fragments, unexpected ports, and hostnames that only end with or resemble an allowed domain. 4. If custom development endpoints are required, place them behind a separate, conspicuous option such as `--allow-custom-api-host`. 5. Require explicit confirmation when a custom host is used and never enable it solely through an inherited environment variable. 6. Display the selected environment and hostname before high-impact operations. 7. Consider certificate pinning or additional server identity verification where operationally practical. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fgo_cli.py:158
Finding
Debug and Inspection Modes Disclose Authentication Hashes and Invoice PII<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fgo_cli.py:158-170`, `scripts/fgo_cli.py:524-531`, and `scripts/fgo_cli.py:554-556` **Vulnerability Type**: Sensitive information exposure through stdout and stderr **Risk Level**: Medium ### Complete Code Snippet ```python if self.config.debug: log_entry: Dict[str, Any] = { "fgo_request": { "attempt": attempt + 1, "method": method, "url": url, "headers": headers, } } if payload is not None: log_entry["fgo_request"]["body"] = payload.decode( "utf-8", errors="replace" ) print(json.dumps(log_entry, ensure_ascii=False), file=sys.stderr) ``` ```python output = { "valid": not errors, "errors": errors, "warnings": warnings, "normalizedPayload": invoice if args.show_payload else None, } print_json(output) ``` ```python if args.dry_run: print_json({"dryRun": True, "payload": invoice}) return 0 ``` ### Technical Analysis `normalize_invoice_payload()` inserts the computed `Hash` into the invoice object. The complete normalized object is then printed by `validate-payload --show-payload` and `emit-invoice --dry-run`. When debug mode is enabled through `--debug` or `FGO_DEBUG`, the complete URL-encoded request body is also written to stderr. No field-level redaction is applied. Invoice payloads can contain customer names, tax identifiers, personal identifiers, addresses, telephone numbers, email addresses, bank accounts, invoice contents, prices, and authentication hashes. Although these modes are opt-in, the Skill documentation actively recommends dry runs and payload inspection. Agent transcripts, CI output, shell history capture, centralized logging, and monitoring systems may retain these values beyond the command's execution. ### Attack Path 1. A user, agent, CI job, or inherited environment enables `--show-payload`, `--dry-run`, `--debug`, or `FGO_DEBUG=1`. 2. The CLI ...[truncated 918 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Redact the `Hash` field in all output modes. 2. Mask sensitive invoice fields by default, including: - Personal or company tax identifiers. - Names and addresses. - Email addresses and telephone numbers. - Bank account details. - External customer identifiers. 3. Make dry-run output a structural summary rather than a complete payload. 4. Add a separate, explicit option such as `--show-sensitive-payload` for exceptional local troubleshooting. 5. Print a warning before producing unredacted output. 6. Ensure documentation does not recommend full-payload output in shared terminals, agent transcripts, or CI environments. 7. Avoid logging complete server responses when they may contain invoice links, payment links, or other sensitive information. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fgo_cli.py:607
Finding
Destructive Invoice Operations Execute Without Explicit Confirmation Gates<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fgo_cli.py:607-651` and `scripts/fgo_cli.py:757-785` **Vulnerability Type**: Missing authorization safeguard for destructive business operations **Risk Level**: Medium ### Complete Code Snippet ```python def run_cancel_invoice(args: argparse.Namespace) -> int: config = ClientConfig.from_args(args) client = FgoClient(config) cod_unic = resolve_cod_unic(args) cheie_privata = resolve_cheie_privata(args) payload = { "CodUnic": cod_unic, "Hash": _hash_invoice_op(cod_unic, cheie_privata, args.numar), "Numar": args.numar, "Serie": args.serie, "PlatformaUrl": _platform_url(), } response, _headers = client.cancel_invoice(payload) print_json({"ok": True, "response": response}) return 0 ``` ```python def run_delete_invoice(args: argparse.Namespace) -> int: config = ClientConfig.from_args(args) client = FgoClient(config) cod_unic = resolve_cod_unic(args) cheie_privata = resolve_cheie_privata(args) payload = { "CodUnic": cod_unic, "Hash": _hash_invoice_op(cod_unic, cheie_privata, args.numar), "Numar": args.numar, "Serie": args.serie, "PlatformaUrl": _platform_url(), } response, _headers = client.delete_invoice(payload) print_json({"ok": True, "response": response}) return 0 ``` ```python def run_reverse_invoice(args: argparse.Namespace) -> int: config = ClientConfig.from_args(args) client = FgoClient(config) cod_unic = resolve_cod_unic(args) cheie_privata = resolve_cheie_privata(args) payload: Dict[str, Any] = { "CodUnic": cod_unic, "Hash": _hash_invoice_op(cod_unic, cheie_privata, args.numar), "Numar": args.numar, "Serie": args.serie, "PlatformaUrl": _platform_url(), } if args.serie_storno: payload["SerieStorno"] = args.serie_storno if args.numar_storno: payload["NumarS ...[truncated 1826 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require dedicated authorization flags for destructive operations, for example: - `--allow-cancel` - `--allow-delete` - `--allow-reversal` 2. Refuse execution when the corresponding flag is absent. 3. Require explicit user confirmation in `SKILL.md` and `agents/openai.yaml` before all destructive accounting operations. 4. Display the exact company identifier, invoice series, invoice number, operation, and selected API environment before execution. 5. Consider a two-step workflow that first retrieves and displays invoice status, then performs the destructive action only after confirmation. 6. Require production-specific confirmation when the selected host is `api.fgo.ro`. 7. Add an idempotency or duplicate-protection strategy for reversal operations where supported by the FGO API. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (20)

Tainted flow: 'request' from os.getenv (line 175, 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
93% confidence
Finding
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.

Credential Access

High
Category
Privilege Escalation
Content
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.
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
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.
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
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.
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
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.
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
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.

Session Persistence

Medium
Category
Rogue Agent
Content
- `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`.
Confidence
60% confidence
Finding
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.

Intent-Code Divergence

Medium
Confidence
85% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
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.

External Transmission

Medium
Category
Data Exfiltration
Content
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}
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
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}
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
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}
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
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}
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
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.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
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.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
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.

Static analysis

No suspicious patterns detected.