T09 · Insecure Skill Coding Practices
- Location
triage.py:42- Finding
Unredacted fintech customer messages are transmitted to a third-party LLM service
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill mostly matches its fintech support purpose, but it handles sensitive customer data with unsafe command/file patterns and under-disclosed third-party processing.
Review carefully before installing in a real support environment. Use only with a trusted, pinned package source; disable or explicitly approve third-party LLM classification for customer messages; replace shell-string examples with structured, non-shell tool calls; validate customer IDs and transaction references; and define retention, permissions, redaction, and approved network destinations for all stored or transmitted support data.
triage.py:42Unredacted fintech customer messages are transmitted to a third-party LLM service
SKILL.md:62Skill instructions interpolate untrusted customer content into shell command templates
handlers.py:48Unsanitized customer IDs allow persistent-memory path traversal
SKILL.md:22Executable package dependency is installed without a pinned version or integrity verification
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
url = f"{API_BASE}{path}"
req = urllib.request.Request(url, headers=HEADERS, method="GET")
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read())
except (urllib.error.URLError, json.JSONDecodeError) as exc:
print(f"[API ERROR] GET {path}: {exc}", file=sys.stderr)
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
url = f"{API_BASE}{path}"
req = urllib.request.Request(url, headers=HEADERS, method="GET")
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read())
except (urllib.error.URLError, json.JSONDecodeError) as exc:
print(f"[API ERROR] GET {path}: {exc}", file=sys.stderr)
The escalation webhook URL is taken directly from an environment variable and used for outbound POSTs without validation or allowlisting. If deployment configuration is compromised or mis-set, sensitive escalation payloads containing customer identifiers, summaries, and prior issue context could be exfiltrated to an attacker-controlled endpoint.
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=8) as resp:
return resp.status < 300
except urllib.error.URLError:
return False
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
)
try:
with urllib.request.urlopen(req, timeout=8) as resp:
data = json.loads(resp.read())
label = data["choices"][0]["message"]["content"].strip().upper()
if label in INTENT_LABELS:
The declared description presents a full customer support agent with operational support workflows and autonomous resolution capabilities. The supplied code only classifies a single incoming message into a small set of intents and prints the label. While the intent categories align with parts of the declared domain, the code does not execute any support actions, compose responses, retrieve transfer status, initiate refunds, handle suspensions, guide KYC, escalate cases, or maintain handoff context. Its actual purpose is a narrow triage/classification component, which is materially narrower than the declared end-to-end support agent.
The trigger patterns are extremely broad, including 'any message that reads like a support request in any language,' which can cause accidental activation on unrelated conversations. In a fintech setting, false activations may lead the agent to access transaction/account data, write memory, or initiate support workflows without sufficient relevance or user confirmation.
Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.
tell them you don't need that information and they should keep it private.
- If a customer appears distressed about a large sum of money, prioritise
speed — escalate immediately rather than attempting auto-resolution.
- Always respond in the language the customer used to contact you.
- Keep responses short. Customers using remittance apps are often on mobile,
often in a hurry. One or two paragraphs maximum unless they ask for detail.
- Do not use jargon. "Your transfer is being processed by the receiving
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
"fintech-support-agent": {
"env": {
"LLM_API_KEY": "your-openai-or-other-key",
"TRANSFER_API_BASE_URL": "https://api.yourproduct.com/v1",
"TRANSFER_API_KEY": "your-internal-api-key",
"SUPPORT_EMAIL": "ops@yourproduct.com",
"ESCALATION_WEBHOOK_URL": "https://hooks.slack.com/your-webhook"
The skill declares access to environment variables, Python execution, curl/network use, and implies file read/write behavior, but it does not define any explicit permission boundary such as allowed tools or scoped capabilities. In a fintech support context, this increases the chance of over-privileged execution, unintended data access, or misuse of sensitive credentials like API keys and webhook URLs.
The skill mandates persistent storage of customer IDs, issue types, outcomes, and timestamps without any notice, consent flow, retention limit, or privacy controls. Because this is a fintech support agent, the stored data can be sensitive and regulated, creating privacy, compliance, and unauthorized profiling risks if retained or reused improperly.
Customer-related support state is written to local disk under the user's home directory without any encryption, retention policy, access control checks, or disclosure. In a fintech context, even seemingly limited fields like transfer references, statuses, and escalation history can expose sensitive financial-support metadata to other local users, backups, or compromised processes.
The skill persistently stores all ticket events and uses them to compute weekly summaries and stale-ticket statistics. While basic case memory supports customer handling, aggregated operational analytics and digest generation are not an obvious requirement of resolving tier-1 fintech support conversations across messaging channels.
The ticket log appends customer-linked records to a local JSON file without user-facing notice or technical safeguards. Because this log aggregates incidents over time, it creates a richer dataset of support interactions that could reveal account issues, disputes, and escalations if the host is shared or compromised.
The manifest describes a customer support agent focused on handling transfer lookups, refunds, account suspensions, KYC guidance, and complaint escalation for customer tickets. The weekly_digest handler instead generates an internal operational analytics report from accumulated ticket logs, which is a distinct reporting/management function not reflected in the stated skill description.
The weekly digest prints customer-linked ticket details, including customer identifiers and issue types, in plain-language output intended for forwarding. In a fintech support environment, this increases the risk of unnecessary internal disclosure or accidental sharing of sensitive customer support history beyond personnel with a need to know.
Customer messages are sent verbatim to an external LLM API, which can expose sensitive financial-support content such as transfer details, account issues, KYC information, or complaints containing personal data. In a fintech support context, this increases privacy, compliance, and data-handling risk, especially if users are not informed and the message content is not minimized or redacted before transmission.
The code transmits customer-supplied support content to an external service endpoint. In a fintech-support skill, those messages may contain personal, financial, or identity-verification data, so the external transmission itself is security-relevant and potentially risky if not tightly governed.
}
req = urllib.request.Request(
"https://api.openai.com/v1/chat/completions",
data=json.dumps(payload).encode(),
headers={
"Content-Type": "application/json",
The module docstring says handlers provide human-readable summaries and never print raw JSON. However, the KYC handler outputs UPLOAD_URL taken directly from API response data, which contradicts the documented claim of exclusively human-readable summarized output and exposes raw machine-provided fields verbatim.
No suspicious patterns detected.