Back to skill

Security audit

openfin-enable-banking

Security checks for vulnerabilities and agentic risk

Overview

This banking skill appears purpose-built, but it needs Review because its callback and local storage handling could expose or corrupt sensitive financial data.

Review before installing. Only run this in a trusted environment, do not expose the callback server publicly without fixing state validation and path handling, avoid forwarding authorization links through casual messaging, and treat stored mandant/data JSON files as sensitive financial records.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/callback_server.py:72
Finding

Path Traversal Through Unvalidated Tenant and OAuth State Identifiers

Content
View full analysis
dict: """Load mandanten/{mandant_id}.json.""" MANDANTEN_DIR.mkdir(parents=True, exist_ok=True) path = MANDANTEN_DIR / f"{mandant_id}.json" if not path.exists(): print(f"❌ Mandant not found: {path}", file=sys.stderr) sys.exit(1) with open(path) as f: return json.load(f) def save_mandant(mandant_id: str, data: dict) -> None: """Save mandanten/{mandant_id}.json (chmod 600).""" MANDANTEN_DIR.mkdir(parents=True, exist_ok=True) path = MANDANTEN_DIR / f"{mandant_id}.json" ``` `scripts/callback_server.py:72-91`: ```python if parsed.path == "/callback": params = parse_qs(parsed.query) code = params.get("code", [None])[0] state = params.get("state", [None])[0] if code and state: # Save callback PENDING_DIR.mkdir(parents=True, exist_ok=True) callback_data = { "code": code, "state": state, "receivedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), } path = PENDING_DIR / f"{state}.json" with open(path, "w") as f: json.dump(callback_data, f, indent=2) os.chmod(path, 0o600) ``` `scripts/fetch.py:205-213`: ```python # Save to data directory data_dir = output_dir or DATA_DIR mandant_data_dir = data_dir / mandant_id mandant_data_dir.mkdir(parents=True, exist_ok=True) today = datetime.now().strftime("%Y-%m-%d") data_file = mandant_data_dir / f"{today}.json" with open(data_file, "w") as f: json.dump(result, f, indent=2, ensure_ascii=False) os.chmod(data_file, 0o600) ``` ### Technical Analysis The code interpolates attacker-controlled ` ...[truncated 2045 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/onboard.py:52
Finding

Predictable OAuth State and Acceptance of Unsolicited Callbacks

Content
View full analysis
dict | None: """POST /auth to get redirect URL and state.""" payload = { "aspsp": { "name": bank_name, "country": country, }, "state": f"openclaw-{int(time.time())}", "redirect_url": callback_url, "psu_type": psu_type, "app": { "name": "FinRobotics", "description": "Bank data integration for tax advisors", }, "accounts": [{"iban": None}], # Request all accounts "scopes": ["aiia.accounts", "aiia.balances", "aiia.transactions"], } return api_request("POST", "/auth", token, json=payload) ``` `scripts/callback_server.py:72-91`: ```python if parsed.path == "/callback": params = parse_qs(parsed.query) code = params.get("code", [None])[0] state = params.get("state", [None])[0] if code and state: # Save callback PENDING_DIR.mkdir(parents=True, exist_ok=True) callback_data = { "code": code, "state": state, "receivedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), } path = PENDING_DIR / f"{state}.json" with open(path, "w") as f: json.dump(callback_data, f, indent=2) os.chmod(path, 0o600) ``` ### Technical Analysis OAuth state is intended to bind an authorization response to the browser session and authorization request that initiated it. Here, state is based solely on the current Unix timestamp: ```python f"openclaw-{int(time.time())}" ``` The renewal flow uses the same construction with an `openclaw-renew-` pre ...[truncated 1800 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/callback_server.py:103
Finding

Reflected HTML Injection in OAuth Callback Error Response

Content
View full analysis

✗ Autorisierung fehlgeschlagen

Fehler: {error}

""".encode()) ``` ### Technical Analysis The `error` query parameter is attacker-controlled and is inserted directly into an HTML document without output encoding. A value containing HTML markup can therefore alter the response document. Script-capable markup may execute in the callback service's origin when a user opens the crafted URL. No Content Security Policy is set to limit script execution. The fact that the response has HTTP status 400 does not prevent the browser from interpreting its body as HTML. ### Attack Path 1. An attacker constructs a callback URL whose `error` parameter contains encoded HTML or script-capable markup. 2. The attacker sends the URL to a user or causes the user's browser to navigate to it during a deceptive authorization flow. 3. The callback server parses the parameter and interpolates it into the HTML response. 4. The browser interprets the injected markup in the callback origin. 5. The payload may alter the displayed authorization result, issue same-origin requests, or execute browser-side code where browser policy permits. ### Impact Assessment Successful exploitation can execute attacker-controlled content in the callback server's browser origin and can display a fraudulent authorization result. Potential consequences include user deception and interaction with same-origin callback endpoints. The callb ...[truncated 262 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding

Unbounded Dependency Version Ranges Make Installations Non-Reproducible

Content
View full analysis
=2.0.0 cryptography>=3.0 requests>=2.25.0 ``` ### Technical Analysis Each dependency has only a minimum version and no upper bound or exact reviewed version. A later installation can therefore resolve package releases that did not exist when the Skill was audited. This prevents reproducible builds and may introduce incompatible behavior or a compromised future release without a corresponding change to the Skill repository. The package names are legitimate and no typosquatting, dependency confusion, malicious package, or unsafe package index was identified. The risk arises from uncontrolled future resolution rather than a confirmed malicious dependency. ### Attack Path 1. The project is installed at a later date using `pip install -r requirements.txt`. 2. The package resolver selects newer versions than those used during review. 3. A selected release contains a security regression, compromised code, or behavior incompatible with the Skill. 4. The dependency executes in the context of the banking scripts and inherits their access to private-key material, API tokens, session records, and financial data. ### Impact Assessment A compromised dependency would run with the privileges of the invoking user and could access all data available to the Python process, including the private key loaded for JWT signing, generated bearer tokens, tenant session identifiers, and fetched financial records. No such compromise was found in the audited files. This is a preventative supply-chain and reproducibility weakness. ]]>
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (20)

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The onboarding flow says the authorization URL may be sent via WhatsApp or email without warning that the link is sensitive and may grant or facilitate account access if intercepted. Because this is a banking OAuth-style authorization step, transmitting the link over insecure or misaddressed channels materially raises the risk of account compromise or unauthorized session creation.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill documents capabilities that perform network access, shell execution, and file writes, but it does not declare any explicit tool scope or permission boundaries. In an agent environment, that makes the effective privilege surface ambiguous and increases the risk of overbroad execution, unintended data access, or unsafe tool use when handling banking workflows.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill handles highly sensitive financial information, including bank authorization flows, account identifiers, balances, and transactions, but the documentation does not include an explicit privacy or sensitivity warning. This omission can lead operators or downstream agents to mishandle regulated PSD2 and financial data, store it insecurely, or disclose it without informed consent.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 94)May include surrounding context.

md
| `generate_jwt(config)` | RS256 JWT for API auth |
| `api_request(method, endpoint, token, **kwargs)` | Authenticated API call with retry (429, timeout) |
| `load_mandant(mandant_id)` | Load `mandanten/{id}.json` |
| `save_mandant(mandant_id, data)` | Save mandant file (chmod 600) |
| `list_mandanten()` | List all mandant IDs |

### `scripts/callback_server.py` — HTTPS Callback Server

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/lib/auth.py (reported line 146)May include surrounding context.

python
| `generate_jwt(config)` | RS256 JWT for API auth |
| `api_request(method, endpoint, token, **kwargs)` | Authenticated API call with retry (429, timeout) |
| `load_mandant(mandant_id)` | Load `mandanten/{id}.json` |
| `save_mandant(mandant_id, data)` | Save mandant file (chmod 600) |
| `list_mandanten()` | List all mandant IDs |

### `scripts/callback_server.py` — HTTPS Callback Server

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This markdown file documents production use for linked bank accounts and explicitly mentions reading real client data, which can affect user privacy and expose highly sensitive financial information. The section explains how to enable access but does not include any warning or disclosure about consent, secure handling, or privacy implications.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The manifest describes PSD2/Open Banking integration for fetching balances and transactions, but this file also provisions local TLS material by invoking the external openssl binary. Running subprocesses is a separate host-level capability that is not an obvious requirement of the stated banking data integration scope, especially since certificate generation is ancillary infrastructure rather than core banking functionality.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/callback_server.py (reported line 46)May include surrounding context.

python
return
    print(f"🔐 Generating self-signed certificate...", file=sys.stderr)
    KEYS_DIR.mkdir(parents=True, exist_ok=True)
    subprocess.run(
        [
            "openssl", "req", "-x509", "-newkey", "rsa:2048",
            "-keyout", str(KEY_FILE),

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The success and failure HTML responses display German-only text such as 'Autorisierung erfolgreich' and 'Autorisierung fehlgeschlagen'. This imposes a specific language on all users with no opt-in, fallback, or documentation indicating a justified region-specific constraint.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script persists highly sensitive banking data, including account identifiers, balances, and transactions, to local JSON files by default. Although it applies restrictive file permissions after writing, there is no explicit consent prompt, storage warning, encryption at rest, or retention control, which increases the risk of unintended exposure on multi-user systems, backups, synced folders, or compromised hosts.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The onboarding flow persists sensitive banking session metadata and account details to a local mandant JSON file via save_mandant() without any visible consent prompt, warning, encryption, or storage-scope controls in this script. In a tax advisory automation context, this data can expose account identifiers, session identifiers, and account metadata to other local users, backups, logs, or misconfigured storage, increasing privacy and unauthorized-access risk.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

Several descriptive labels are presented in German, such as balance and transaction field explanations, without indicating that the skill is intentionally region-specific or giving users an alternative language. This creates a locale policy concern because the documentation implicitly forces one language in otherwise general API reference material.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The transaction field table uses German labels such as 'Buchungsdatum', 'Wertstellungsdatum', and 'Verwendungszweck' in a general-purpose reference. Because no language selection or regional limitation is stated, this appears to enforce a specific locale in violation of the language-choice policy.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
94% confidence
Finding

The dependency specification uses a lower-bound only constraint for PyJWT, which makes builds non-reproducible and can pull in different versions over time. In a banking integration that handles authentication tokens, an unexpected vulnerable or behavior-changing PyJWT release could weaken JWT validation and create authentication or SSRF-related exposure depending on usage.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
PyJWT>=2.0.0
cryptography>=3.0
requests>=2.25.0

Unverifiable Dependency: PyJWT has 16 known advisory(ies) (CVE-2026-32597 (PyJWT accepts unknown `crit` header extensions); CVE-2024-53861 (PyJWT Issuer field partial matches allowed); CVE-2026-48522 (PyJWKClient: missing scheme allowlist enables CVE-2024-21643-class SSRF + token ) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding

PyJWT has multiple known advisories, and because the manifest does not pin a version, there is no way to verify that deployed environments avoid affected releases. Given this skill's PSD2/open banking context, any JWT parsing or validation weakness is more dangerous because it may affect authentication flows, token trust decisions, or access delegation.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
93% confidence
Finding

The cryptography package is not pinned, so deployments may resolve to different versions across environments or at different times. For software processing sensitive banking data, drifting cryptographic library versions can introduce known OpenSSL-related flaws or cryptographic weaknesses without visibility or change control.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
PyJWT>=2.0.0
cryptography>=3.0
requests>=2.25.0

Unverifiable Dependency: cryptography has 16 known advisory(ies) (GHSA-39hc-v87j-747x (Vulnerable OpenSSL included in cryptography wheels); CVE-2023-50782 (Python Cryptography package vulnerable to Bleichenbacher timing oracle attack); GHSA-537c-gmf6-5ccf (Vulnerable OpenSSL included in cryptography wheels) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
87% confidence
Finding

cryptography has known security advisories, but the unpinned manifest prevents verification that the installed version is patched. In a banking automation skill, cryptographic assurance is core to protecting secrets and transport security, so uncertainty around the exact crypto package version materially increases risk.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
93% confidence
Finding

The requests dependency is specified with only a minimum version, allowing uncontrolled upgrades and unverifiable resolved versions. In a skill that communicates with banking APIs, this is risky because requests has had issues affecting redirect handling, credential leakage, and TLS/session behavior, which could impact confidentiality of tokens or account data.

Content

Scanner excerpt · requirements.txt (reported line 3)May include surrounding context.

text
PyJWT>=2.0.0
cryptography>=3.0
requests>=2.25.0

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding

requests has several published advisories, and the absence of version pinning means the installed version may be vulnerable without operators realizing it. Because this skill likely sends authenticated requests to bank APIs, any issue involving credential leakage, redirect handling, or certificate/session verification could expose highly sensitive financial data or tokens.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The CLI argument for country silently defaults to Germany (DE), which imposes a locale-specific behavior unless the user notices and overrides it. The file does not explain why DE is the default or present it as an explicit user choice.

Content

No source excerpt is available for this finding.