Back to skill

Security audit

OpenClaw Agreement Sender

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it advertises, but it handles sensitive agreements in ways that need review before use.

Review before installing or using this skill with real agreements. Use only approved HTTPS NanoPDF and DocuSign endpoints, prefer draft mode with `--status created`, manually verify signer-to-tab mappings, and keep the output directory out of shared folders or repositories because it may contain complete agreements and signer information.

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/send_agreement.py:216
Finding
Unvalidated Service Endpoints Can Expose Agreements and Bearer Credentials<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_agreement.py`, lines 216–221, 235–246, and 259–264 **Vulnerability Type**: Arbitrary network destination and insecure transport configuration **Risk Level**: High ### Vulnerable Code ```python nanopdf_base = read_env("NANOPDF_BASE_URL") nanopdf_key = read_env("NANOPDF_API_KEY") nanopdf_detect_path = read_env( "NANOPDF_DETECT_PATH", required=False, default="/v1/signature-blocks", ) docusign_base = read_env("DOCUSIGN_BASE_URL") docusign_account_id = read_env("DOCUSIGN_ACCOUNT_ID") docusign_token = read_env("DOCUSIGN_ACCESS_TOKEN") ``` ```python detect_url = f"{nanopdf_base.rstrip('/')}/{nanopdf_detect_path.lstrip('/')}" nanopdf_payload = { "document_name": pdf_path.name, "document_base64": pdf_b64, "labels": ["signature"], } nanopdf_response = http_json( url=detect_url, method="POST", payload=nanopdf_payload, headers={"Authorization": f"Bearer {nanopdf_key}"}, ) ``` ```python envelope_url = ( f"{docusign_base.rstrip('/')}/restapi/v2.1/" f"accounts/{docusign_account_id}/envelopes" ) envelope_result = http_json( url=envelope_url, method="POST", payload=docusign_payload, headers={"Authorization": f"Bearer {docusign_token}"}, ) ``` ### Technical Analysis The Skill must transmit agreement data to NanoPDF and DocuSign to perform its declared function. Base64 encoding of the PDF is part of those documented API request formats and is not, by itself, evidence of covert exfiltration. However, the destinations are taken directly from environment variables without enforcing HTTPS or validating the destination hostname. The generic `urlopen` request path will therefore send the PDF and corresponding bearer credential to any syntactically accepted URL supplied through the environment. This exceeds safe least-privilege network behavior because the Skill needs access only to the intended NanoPDF and DocuSign services, not arbitrary network d ...[truncated 1807 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require `https` for both configured service URLs and reject plaintext HTTP. 2. Parse URLs with `urllib.parse.urlsplit` rather than relying on string concatenation. 3. Maintain explicit hostname allowlists for approved NanoPDF and DocuSign environments. 4. Reject URLs containing user information, unexpected ports, fragments, or malformed hostnames. 5. Reject loopback, link-local, private, and metadata-service destinations unless an explicitly approved private deployment requires them. 6. Disable automatic redirects or validate every redirect target against the same scheme and hostname policy. 7. Keep separate credentials for each service and never forward one service's credential across origin boundaries. 8. Validate `DOCUSIGN_ACCOUNT_ID` as an expected identifier before inserting it into the request path. 9. Use narrowly scoped and short-lived access tokens. 10. Log only the normalized destination hostname, never authorization headers or complete request bodies. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/send_agreement.py:266
Finding
Complete Agreement and Signer PII Are Persisted in Plaintext Debug Artifacts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_agreement.py`, lines 266–276 **Vulnerability Type**: Plaintext storage of confidential documents and personal information **Risk Level**: Medium ### Vulnerable Code ```python out_dir = Path(args.output_dir) out_dir.mkdir(parents=True, exist_ok=True) (out_dir / "nanopdf_blocks.json").write_text( json.dumps(blocks, indent=2), encoding="utf-8", ) (out_dir / "docusign_payload.json").write_text( json.dumps(docusign_payload, indent=2), encoding="utf-8", ) (out_dir / "envelope_result.json").write_text( json.dumps(envelope_result, indent=2), encoding="utf-8", ) ``` The persisted payload is constructed as follows: ```python payload: Dict[str, Any] = { "emailSubject": subject, "documents": [ { "documentBase64": pdf_b64, "name": pdf_path.name, "fileExtension": "pdf", "documentId": "1", } ], "recipients": {"signers": docusign_signers}, "status": status, } if message: payload["emailBlurb"] = message ``` ### Technical Analysis The generated `docusign_payload.json` contains the complete base64-encoded agreement, signer names and email addresses, envelope subject, optional email message, routing information, and signature coordinates. Base64 is reversible encoding and does not provide confidentiality. Anyone able to read the artifact can reconstruct the original PDF. The output directory is created using normal process defaults, and the files are written without explicit owner-only permissions or a retention policy. Although `SKILL.md` declares that audit files will be written, retaining the entire document is not necessary for a minimally privileged audit trail. Metadata, hashes, envelope identifiers, and redacted recipient information would ordinarily be sufficient. The behavior therefore creates an avoidable secondary copy of sensitive contract data. ### Attack Path 1. A user processes a ...[truncated 1114 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not persist `documentBase64` by default. 2. Write a redacted audit record containing only necessary metadata, such as: - A cryptographic document hash. - Document filename or a sanitized identifier. - Envelope ID and status. - Block coordinates and confidence values. - Redacted or hashed recipient identifiers. 3. Make full request-body logging an explicit, prominently documented opt-in. 4. Create the output directory with owner-only permissions, such as mode `0700`. 5. Create sensitive files atomically with owner-only mode `0600`, independent of the process umask. 6. Refuse output paths that resolve through unsafe symbolic links. 7. Define and enforce an artifact-retention period and provide secure cleanup guidance. 8. Warn users not to place output directories inside source repositories, shared directories, or automatically uploaded build-artifact locations. 9. If full payload retention is operationally required, encrypt it using a managed key and restrict decryption access. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/send_agreement.py:117
Finding
Ambiguous Signature Blocks Can Be Assigned and Sent Without Confirmation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_agreement.py`, lines 117–122; related sending behavior at lines 191 and 259–264 **Vulnerability Type**: Unsafe automatic recipient mapping and immediate dispatch **Risk Level**: Medium ### Vulnerable Code ```python signer_idx = 0 for block in unassigned: signer = signers[signer_idx % len(signers)] assignments[signer["email"]].append(block) signer_idx += 1 ``` ```python p.add_argument( "--status", default="sent", choices=["sent", "created"], help="Envelope status", ) ``` ```python envelope_url = f"{docusign_base.rstrip('/')}/restapi/v2.1/accounts/{docusign_account_id}/envelopes" envelope_result = http_json( url=envelope_url, method="POST", payload=docusign_payload, headers={"Authorization": f"Bearer {docusign_token}"}, ) ``` ### Technical Analysis Blocks with no `signer_key`, or with a `signer_key` that does not match any configured signer, are silently placed into `unassigned`. They are then distributed round-robin according to signer-list order. This fallback does not establish that a block belongs to the selected signer. It only ensures that every signer receives a block when enough blocks exist. The script checks whether a signer has zero assigned blocks, but it does not detect whether the resulting assignment is ambiguous or semantically wrong. The risk is amplified because the command defaults to `status="sent"`. Consequently, an ambiguous mapping can be submitted and dispatched without a mandatory review stage. This conflicts with the safety rule in `SKILL.md` requiring the workflow to stop and request explicit mapping when signer mapping is ambiguous. ### Attack Path 1. A PDF is processed for two or more signers. 2. NanoPDF returns signature blocks with missing, misspelled, attacker-influenced, or otherwise unknown `signer_key` values. 3. The script treats those blocks as unassigned rather than rejecting the response. 4. It distributes ...[truncated 1094 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fail closed when a block has no `signer_key` or an unknown `signer_key` in a multi-signer envelope. 2. Require an explicit command-line option, such as `--allow-order-fallback`, before positional assignment is permitted. 3. Change the default envelope status from `sent` to `created`. 4. Require explicit confirmation before changing a draft with ambiguous or inferred mappings to `sent`. 5. Reject duplicate signer keys and duplicate signer email addresses. 6. Validate that every configured signer key is represented and that every returned signer key is known. 7. Define and enforce an acceptable NanoPDF confidence threshold. 8. Produce a pre-send mapping summary that lists every signer, block ID, page, and coordinate. 9. Separate envelope creation and envelope sending into distinct operations for high-impact workflows. 10. Record whether each assignment was explicit or inferred in the redacted audit output. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill invokes a Python script that reads files, writes audit outputs, consumes environment secrets, and makes outbound API calls, but the manifest declares no explicit tool scope or permission boundaries. This creates an authorization gap where an agent may run with broader-than-expected capabilities, increasing the chance of unintended file access, secret exposure, or network misuse if the skill is invoked in a permissive runtime.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The documented request contract sends the entire PDF as base64 to an external NanoPDF endpoint, but the skill documentation provides no warning that potentially sensitive agreement contents and signatures are being disclosed to another service. In this skill context, agreements commonly contain legal, financial, and personal data, so omission of disclosure, minimization, or trust-boundary guidance creates a real confidentiality and compliance risk.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script sends the entire agreement PDF to NanoPDF and then sends the document plus signer names/emails to DocuSign, which exposes potentially sensitive contractual and personal data to third-party services. In this skill context that behavior is expected for functionality, but the lack of any explicit consent, warning, or data-handling controls increases the risk of unintentional disclosure, misuse of production data, or violating privacy/compliance expectations.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script writes NanoPDF signature block output, the full DocuSign payload containing base64-encoded document content and signer details, and the envelope response to disk in a predictable output directory. Those artifacts can persist sensitive agreements, coordinates, signer PII, and API workflow metadata beyond the immediate run, increasing exposure through local compromise, backups, shared workspaces, or accidental check-in.