T09 · Insecure Skill Coding Practices
- Location
SKILL.md:112- Finding
Live-Format Production API Key Embedded in Documentation
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This e-signature skill is purpose-aligned but needs Review because it permits agent-driven legally consequential signing and exposes risky bearer-token and credential-handling patterns.
Install only if you are comfortable with an agent handling contract workflows through SaySigned. Require your own approval before every send, sign, decline, void, or billing action; prefer email delivery for human signers; never put access tokens in URLs, logs, prompts, or saved memory; and treat the embedded example API key as exposed and unsafe.
SKILL.md:112Live-Format Production API Key Embedded in Documentation
SKILL.md:198Recipient Bearer Tokens May Be Exposed Through URLs and Have No Expiration
SKILL.md:62Legally Consequential Signing Operations Are Permitted Without Transaction-Specific Human Approval
The skill instructs the agent to capture and use recipient access tokens for signing. Because these tokens directly authorize recipient actions and the skill promotes autonomous handling, any leakage through logs, prompts, memory, or transcript storage could let an attacker sign or decline on someone else's behalf.
- **You don't have to use a template.** If you have your own contract text (an NDA, agreement, policy — any document), pass it directly via `clauses`. Templates are a convenience, not a requirement.
- **Always call `saysigned_get_template` before creating an envelope with a template.** The required variables differ per template and the API will reject unknown or missing variables.
- **`saysigned_send_envelope` returns access tokens.** You must capture and use these tokens for signing. They are 128-character hex strings.
- **Signing does not use your API key.** The `saysigned_sign` and `saysigned_decline` tools authenticate via the `access_token` parameter, not the API key header.
- **The envelope auto-completes** when the last recipient signs. You don't need to call a separate "complete" endpoint.
- **Auth header is forwarded automatically.** Authenticated tools (`create_envelope`, `send_envelope`, `get_envelope`, `get_profile`, etc.) use the API key from your MCP client's `Authorization` header. Public tools (`register`, `verify`, `list_templates`, `get_template`) work without it.
Documenting that send_envelope returns access tokens highlights a real sensitive capability. While the table itself is descriptive, in this skill context it reinforces a workflow where the agent obtains credentials that can be abused for recipient impersonation if not tightly controlled.
|------|------|-------------|
| `saysigned_register` | None | Register agent, get API key |
| `saysigned_create_envelope` | API key | Create draft envelope |
| `saysigned_send_envelope` | API key | Send for signing, get access tokens |
| `saysigned_get_envelope` | API key | Get envelope details + status |
| `saysigned_void_envelope` | API key | Cancel a sent (not completed) envelope |
| `saysigned_sign` | Access token | Sign as a recipient |
Marking the signing tool as authenticated solely by access token means possession of the token is enough to act as the recipient. In an agent setting, that creates a high-risk bearer-credential flow susceptible to misuse, leakage, or confused-deputy behavior.
| `saysigned_send_envelope` | API key | Send for signing, get access tokens |
| `saysigned_get_envelope` | API key | Get envelope details + status |
| `saysigned_void_envelope` | API key | Cancel a sent (not completed) envelope |
| `saysigned_sign` | Access token | Sign as a recipient |
| `saysigned_decline` | Access token | Decline to sign |
| `saysigned_verify` | None | Verify cryptographic integrity |
| `saysigned_get_audit_trail` | API key | Get hash-chain audit trail |
The decline tool is also bearer-token authorized, so leakage or misuse of the token can alter legal workflow by declining on a recipient's behalf. Although lower impact than unauthorized signing, it can still disrupt contracts and business processes.
| `saysigned_get_envelope` | API key | Get envelope details + status |
| `saysigned_void_envelope` | API key | Cancel a sent (not completed) envelope |
| `saysigned_sign` | Access token | Sign as a recipient |
| `saysigned_decline` | Access token | Decline to sign |
| `saysigned_verify` | None | Verify cryptographic integrity |
| `saysigned_get_audit_trail` | API key | Get hash-chain audit trail |
| `saysigned_list_templates` | None | List available contract templates |
The skill explicitly states that access tokens are passed as headers or query parameters. Query-parameter support is especially risky because URLs are commonly logged, cached, stored in browser history, and propagated via referrers, increasing the chance of credential disclosure.
## Key Concepts
- **IDs are UUIDs** — all envelope, recipient, and agent IDs are standard UUIDs (e.g., `2c272d15-cd4b-4c0d-96c0-bd7438b66699`)
- **Access tokens are hex strings** — 128-character hex, passed via `X-Access-Token` header (recommended) or `?access_token=` query parameter
- **API keys start with `ss_live_`** — REST: pass in `X-API-Key` header. MCP: pass as `Authorization: Bearer <key>` (auto-translated)
- **Templates** — use `GET /templates` to discover available templates and `GET /templates/:id` to see required variables
- **Signing order** — all recipients sign in parallel (no enforced order)
The documentation states that API delivery returns access tokens for programmatic signing, which is a credential-bearing design. In an agent workflow, this increases risk because the agent may receive secrets that enable legal actions on behalf of recipients.
Free tier: **5 envelopes/month**, no credit card required. Upgrade: `POST /billing/setup` with `{"plan": "payg"}`.
- **Delivery methods** — `"api"` (default) returns access tokens for programmatic signing; `"email"` sends an invitation email to a human who reviews and gives consent at `doc.saysigned.com`
- **Email recipients** — human recipients authenticate via a URL token in their email link, not an access token. They review the contract in a browser and click "Give Consent" or "Decline"
---
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Free tier: **5 envelopes/month**, no credit card required. Upgrade: `POST /billing/setup` with `{"plan": "payg"}`.
- **Delivery methods** — `"api"` (default) returns access tokens for programmatic signing; `"email"` sends an invitation email to a human who reviews and gives consent at `doc.saysigned.com`
- **Email recipients** — human recipients authenticate via a URL token in their email link, not an access token. They review the contract in a browser and click "Give Consent" or "Decline"
---
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Free tier: **5 envelopes/month**, no credit card required. Upgrade: `POST /billing/setup` with `{"plan": "payg"}`.
- **Delivery methods** — `"api"` (default) returns access tokens for programmatic signing; `"email"` sends an invitation email to a human who reviews and gives consent at `doc.saysigned.com`
- **Email recipients** — human recipients authenticate via a URL token in their email link, not an access token. They review the contract in a browser and click "Give Consent" or "Decline"
---
The send tool definition states that it generates access tokens for API recipients. In the broader skill context, this means the agent can mint or retrieve bearer credentials that allow legal actions, creating a significant impersonation and misuse risk if secrets are exposed or used without authority checks.
{
"name": "saysigned_send_envelope",
"description": "Send a draft envelope for signing. For API recipients, generates access tokens. For email recipients, sends an invitation email with a consent link to doc.saysigned.com (no access_token in response). Consumes 1 envelope (free tier) or reports 1 metered event (paid). Returns 402 if free tier exhausted.",
"inputSchema": {
"type": "object",
"properties": {
The security notes acknowledge that access tokens have no expiration. Non-expiring bearer tokens materially increase the blast radius of disclosure because a leaked token can be replayed indefinitely for the duration of the signing flow, especially in systems with verbose logging or retained transcripts.
- **API keys** are shown once at creation. Store securely. Rotate or delete keys that are no longer needed.
- **Config file security** — API keys stored in MCP config files should have appropriate file permissions. Limit access to the config directory.
- **Access tokens** are per-recipient, per-envelope. Do not reuse. Tokens are 128-character hex strings with no expiration — treat them as secrets for the duration of the signing flow.
- **Webhook secrets** are per-agent. Verify every webhook signature.
- All connections require **TLS** (HTTPS). Verify TLS certificates for `api.saysigned.com`, `mcp.saysigned.com`, and `doc.saysigned.com`.
- API keys are stored as **SHA-256 hashes** — SaySigned never stores plaintext keys.
The skill explicitly says the agent may handle signing autonomously, but signing a contract on behalf of a recipient has legal and business consequences and is not framed with a required user-facing approval or explicit authority check. In this context, the absence of a strong consent/authorization gate could cause an agent to bind a party or apply a signature using an access token without contemporaneous human confirmation.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -s -X POST https://api.saysigned.com/agents/register \
-H "Content-Type: application/json" \
-d '{"name": "My AI Agent", "email": "agent@example.com"}'
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -s -X POST https://api.saysigned.com/agents/register \
-H "Content-Type: application/json" \
-d '{"name": "My AI Agent", "email": "agent@example.com"}'
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -s -X POST https://api.saysigned.com/agents/register \
-H "Content-Type: application/json" \
-d '{"name": "My AI Agent", "email": "agent@example.com"}'
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -s -X POST https://api.saysigned.com/agents/register \
-H "Content-Type: application/json" \
-d '{"name": "My AI Agent", "email": "agent@example.com"}'
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -s -X POST https://api.saysigned.com/agents/register \
-H "Content-Type: application/json" \
-d '{"name": "My AI Agent", "email": "agent@example.com"}'
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -s -X POST https://api.saysigned.com/agents/register \
-H "Content-Type: application/json" \
-d '{"name": "My AI Agent", "email": "agent@example.com"}'
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -s -X POST https://api.saysigned.com/agents/register \
-H "Content-Type: application/json" \
-d '{"name": "My AI Agent", "email": "agent@example.com"}'
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -s -X POST https://api.saysigned.com/agents/register \
-H "Content-Type: application/json" \
-d '{"name": "My AI Agent", "email": "agent@example.com"}'
The example embeds a live-looking API key directly in documentation and shows it being sent in a request header. Even if intended as a sample, publishing realistic secrets normalizes unsafe handling, risks accidental reuse, and can lead operators or models to copy plaintext credentials into logs, chats, or configs.
Use the api_key from step 1 in the X-API-Key header. Use template_id for standard contracts or provide custom clauses.
curl -s -X POST https://api.saysigned.com/envelopes \
-H "Content-Type: application/json" \
-H "X-API-Key: ss_live_203bff0e53ba167462aa2cdcbd8e189e2909d37cf76c31da675cb1dba7dc0026" \
-d '{
This example again transmits a live-looking API key in plaintext within documentation. Exposure of an API key can let an attacker create, send, void, or inspect envelopes under the victim account, and examples like this encourage insecure copy/paste and logging practices.
This consumes 1 envelope from your quota (free tier) or reports 1 metered event (paid plan). Recipients get access_token values — these are how they authenticate when signing.
curl -s -X POST https://api.saysigned.com/envelopes/2c272d15-cd4b-4c0d-96c0-bd7438b66699/send \
-H "X-API-Key: ss_live_203bff0e53ba167462aa2cdcbd8e189e2909d37cf76c31da675cb1dba7dc0026"
The signing example includes a recipient access token directly in the request. These tokens authorize signature actions without an API key, so exposing them in docs, logs, or transcripts could allow unauthorized signing on behalf of that recipient.
# Alice signs
curl -s -X POST "https://api.saysigned.com/envelopes/2c272d15-cd4b-4c0d-96c0-bd7438b66699/recipients/62f73ba5-05a0-4223-8f55-e880047a7b3e/sign" \
-H "Content-Type: application/json" \
-H "X-Access-Token: 600b9b4297b2921396a1db138a80801c7dfb5101..." \
-d '{"signature_data": {"full_name": "Alice Chen", "title": "CEO"}}'
This second signing example similarly exposes a bearer-style recipient access token in a command. Because the token is sufficient to sign, disclosure could directly enable unauthorized contract execution.
# Bob signs (last signer → envelope automatically completes with RFC 3161 timestamp)
curl -s -X POST "https://api.saysigned.com/envelopes/2c272d15-cd4b-4c0d-96c0-bd7438b66699/recipients/cc0aeb32-eb9d-420d-ab93-1d42400f85b9/sign" \
-H "Content-Type: application/json" \
-H "X-Access-Token: 952340a770b96740e2696c306ad490b28556952e..." \
-d '{"signature_data": {"full_name": "Bob Smith", "title": "CTO"}}'
Detected: suspicious.exposed_secret_literal