T09 · Insecure Skill Coding Practices
- Location
records.md:13- Finding
Credential and Salesforce Data Disclosure Through Unvalidated Destination Variables
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This Salesforce helper is transparent about its purpose, but it should be reviewed carefully because it can use powerful Salesforce credentials and persist business context locally without asking each time.
Install only if you are comfortable granting the skill Salesforce API authority and access to its declared local note folders. Use a least-privileged integration user, verify SF_INSTANCE_URL and SF_LOGIN_HOST are trusted Salesforce hosts, keep tokens and private keys out of Clawic data files, and review or disable automatic local memory writes if shared contacts, project notes, or subscription costs should not persist across sessions.
records.md:13Credential and Salesforce Data Disclosure Through Unvalidated Destination Variables
SKILL.md:22Excessive Default Access to Shared Contact, Project, Profile, and Financial Data Stores
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
| Signature | Most likely cause | First move |
|---|---|---|
| 401 `INVALID_SESSION_ID` | Access token expired, or a sandbox token sent to production | Refresh; if refresh also fails, the login host is wrong (`test.salesforce.com` for sandboxes) |
| 400 `invalid_grant` at the token endpoint | JWT `aud`, `sub` or clock; or the user is not pre-authorized on the Connected App | Check `aud` matches the environment and that the user's profile is on the app (`auth.md`) |
| 400 `INVALID_FIELD` on a field you can see in the UI | Field-level security on the integration user, or the label instead of the API name | Rule 7 — check FLS first, then `describe` for the real API name |
| 400 `MALFORMED_QUERY` | Syntax, or a relationship name used where a field name belongs | `Account.Name` traverses, `AccountId` is the field; child subqueries use the plural relationship name |
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
| api_version | text (`vNN.0`) | none | Version segment in every URL and example; while unset, name the assumed version before acting (Rule 2) |
| default_org | text (org alias) | none | Which org in `## Org Context` / `orgs.md` an unqualified request means; unset means ask nothing and say which org the answer assumes |
| auth_flow | jwt-bearer \| web-server \| refresh-token \| client-credentials \| device | jwt-bearer | Which flow `auth.md` walks and what the examples pass to the token endpoint |
| code_style | curl \| python \| node \| sf-cli | curl | Language of every emitted example |
| bulk_threshold | number (records) | 5000 | Switch point from sObject Collections to Bulk 2.0 in Rule 3 |
| all_or_none | bool | true | Default for Collections and Composite: whole-batch rollback vs per-record results |
| sandbox_first | bool | true | Whether bulk writes, deletes and metadata deploys are rehearsed in a sandbox before production (Rule 8) |
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
| Treating a 403 as "no permission" | `REQUEST_LIMIT_EXCEEDED` is also a 403, and the fix is the opposite of granting access | Branch on `errorCode`, never on the HTTP status alone |
| Retrying a failed insert without an idempotency key | Salesforce processed the first one; the timeout was the response, not the write | Upsert on external id, or query for the record before retrying (Rule 4) |
| Mass owner changes during business hours | Sharing recalculation cascades and can lock the org for everyone | Defer sharing calculation, run off hours, and stage in a sandbox first (`migration.md`) |
| Storing an access token or a JWT private key in the notes file | It leaks with the first backup or screen share | Pointer only: `keychain:sf-prod`, `file:~/.certs/sf-jwt.key` |
| Testing a load against 10 records | Governor limits and lock contention only appear at chunk boundaries — 10 records never crosses one | Rehearse with at least 1,000 records, spanning more than one 200-record chunk (Rule 5) |
## Where Experts Disagree
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
| A migration or integration the user tracks as a piece of work | `~/Clawic/data/projects/<project>.md` (**shared**) | One file per project |
| Salesforce edition, license count and what it costs | `~/Clawic/data/finances/subscriptions.md` (**shared**) | One row, amount with currency inside the value |
| **Anything durable this table does not name** | `~/Clawic/data/salesforce-api-integration/<plural-noun>.md`, or `artifacts/<kebab-name>.md` if it is a long text read whole | Name the file after what it holds, never after when it was made; add its `## Boxes` line in the same turn |
| Access tokens, client secrets, private keys, passwords, security tokens | Nowhere under `~/Clawic/data/` | Pointer only — see Secrets |
Deciding where something new goes, in order: **would another skill want to read it?** → shared box. **Is it a text read whole when its subject comes up** (design, mapping, runbook, policy)? → `artifacts/`. **Is it one more row of something that accumulates?** → a section of `memory.md` until the threshold, then its own box.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
| A migration or integration the user tracks as a piece of work | `~/Clawic/data/projects/<project>.md` (**shared**) | One file per project |
| Salesforce edition, license count and what it costs | `~/Clawic/data/finances/subscriptions.md` (**shared**) | One row, amount with currency inside the value |
| **Anything durable this table does not name** | `~/Clawic/data/salesforce-api-integration/<plural-noun>.md`, or `artifacts/<kebab-name>.md` if it is a long text read whole | Name the file after what it holds, never after when it was made; add its `## Boxes` line in the same turn |
| Access tokens, client secrets, private keys, passwords, security tokens | Nowhere under `~/Clawic/data/` | Pointer only — see Secrets |
Deciding where something new goes, in order: **would another skill want to read it?** → shared box. **Is it a text read whole when its subject comes up** (design, mapping, runbook, policy)? → `artifacts/`. **Is it one more row of something that accumulates?** → a section of `memory.md` until the threshold, then its own box.
Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.
| The exact number the sales meeting quotes | The report | It carries the org's own filters, groupings and currency conversion. A SOQL rebuild that differs by 2% is worse than useless |
| Rows to process, load or export | SOQL or Bulk (`soql.md`, `bulk.md`) | No row ceiling, no factMap to unpick |
| More than a couple of thousand rows | Bulk query | The synchronous report path truncates |
| A recurring extract | SOQL, written once | Reports get edited by whoever owns them, without warning |
The trap in both directions: rebuilding a trusted report in SOQL and quietly getting a different number, or scraping a report for bulk data it was never meant to deliver.
The skill metadata requires SF_INSTANCE_URL from the environment while the body also states the instance URL must come from the OAuth token response. This inconsistency can cause the agent to send authenticated Salesforce requests to the wrong host, risking misrouting, failed requests, or accidental disclosure of API traffic to an unintended Salesforce domain if stale or incorrect environment values are used.
The skill says memory-template.md is the only file opened for writing, but later instructs writes to shared contacts, projects, and finances files. Contradictory persistence rules create ambiguity about where data may be stored, which can lead an agent to write sensitive CRM-related information into broader shared note locations without consistent safeguards or user awareness.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
A class annotated as a REST resource is served at "$SF_INSTANCE_URL/services/apexrest/<urlMapping>" — a different base path from /services/data/, and it carries no API version segment.
curl -X POST "$SF_INSTANCE_URL/services/apexrest/orders/v1" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"customerKey":"EXT-4471","lines":[{"sku":"A-1","qty":2}]}'
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
The assertion's claims: iss = Consumer Key, sub = the integration user's username (the sandbox username in a sandbox), aud as above, exp a few minutes out — Salesforce rejects a long-lived assertion, so mint a fresh one per token request rather than caching the JWT.
curl -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
-d "assertion=$SIGNED_JWT"
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.
| 401 `INVALID_SESSION_ID` on a token minted seconds ago | Token sent to the wrong instance URL | Use the `instance_url` from that token response |
| Everything fails after a deactivation | The integration user was disabled | Reactivate or repoint; no token can outlive its user |
**Before finishing an auth session**: write the org's alias, type, instance URL, API version, flow and credential *pointer* into `## Org Context` — or `orgs.md` from the second org — and put any certificate or secret expiry into the `## Due` table of `memory.md`. Never write the token, the secret, the key or an `sfdx` auth URL; the pointer is `keychain:sf-prod`, `env:SF_CLIENT_SECRET`, `file:~/.certs/sf-jwt.key`.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# 1. Create the job
curl -X POST "$SF_INSTANCE_URL/services/data/v62.0/jobs/ingest" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"operation":"upsert","object":"Account","externalIdFieldName":"ERP_Id__c",
"contentType":"CSV","lineEnding":"LF"}'
The documentation instructs the agent to append to and potentially create local files under ~/Clawic/data/salesforce-api-integration/loads/<year>.md and related artifacts. That expands the skill's behavior beyond Salesforce API actions into persistent filesystem writes, which can occur without explicit per-run user consent and can leak sensitive operational metadata such as object names, row counts, job IDs, mappings, and failure causes onto the local machine.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
Up to 200 records, one operation, one call. Mixed object types are allowed in the same payload via attributes.type.
curl -X POST "$SF_INSTANCE_URL/services/data/v62.0/composite/sobjects" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"allOrNone": false,
"records":[
This markdown file demonstrates a deletion call (DELETE ...?ids=...) but does not include any warning about data loss, confirmation, or the irreversible nature of the operation. Under the markdown-specific SQP-2 criteria, user-facing documentation should warn when described behavior can affect user data or system integrity.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
curl -X POST "$SF_INSTANCE_URL/services/data/v62.0/sobjects/ContentVersion" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"Title":"Signed contract","PathOnClient":"contract.pdf","VersionData":"<base64>"}'
The instruction 'Read this file only when WRITING' does not define what counts as 'WRITING' or provide any exclusion conditions. In a markdown skill file, this broad trigger can overlap with many ordinary agent actions and may cause unintended invocation because there is no explicit scope or negative examples.
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.
Everything except artifacts, per-object schema files, load records and the shared boxes begins inside `memory.md`. Splitting is a procedure, not a suggestion:
1. **Who and when**: the agent about to append counts the section's entries **before** adding the one that would cross the line.
2. **Threshold**: past **~15 entries or ~40 lines of real content** — scaffolding, headings and comments do not count, and in tables the entry count rules — then, in the same turn: create the new file in `~/Clawic/data/salesforce-api-integration/`, move the whole section into it, **delete the section from `memory.md`**, add its line to `## Boxes`, and append the new entry to the new file.
3. **Identical headings on both sides** of the move, so the split is a copy-paste and never a rewrite.
4. **Precedence**: never leave a copy behind. If the same data ever appears in both places, the extracted file wins and the `memory.md` copy is deleted.
The default bypass_validation_rules: ask-first normalizes a potentially dangerous operational mode in which business-rule enforcement may be disabled. In a Salesforce integration context, validation rules often protect data integrity and policy compliance; making bypass an expected option increases the chance of unsafe writes, especially under time pressure or vague user approval.
csv_date_format: "YYYY-MM-DDThh:mm:ssZ" safety_posture: hard_delete: forbidden bypass_validation_rules: ask-first sync_posture: poll_minutes: 15
The section explicitly discusses irreversible and destructive recovery actions such as hard delete and restarting from an empty org, but it does not begin with a prominent safety warning or operator confirmation guidance before presenting those options. In a migration skill, this can normalize risky actions and increase the chance that a user performs irreversible deletion without adequate backup, approval, or validation.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# Whole record, every field the user can see
curl "$SF_INSTANCE_URL/services/data/v62.0/sobjects/Account/001xx000003DGbYAAW" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN"
# Named fields only — smaller, faster, and it fails loudly on a field you cannot read
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
POST the report id with a modified reportMetadata to filter without touching the saved report:
curl -X POST "$SF_INSTANCE_URL/services/data/v62.0/analytics/reports/00Oxx0000012345?includeDetails=true" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"reportMetadata":{"reportFilters":[
{"column":"OPPORTUNITY.STAGE_NAME","operator":"equals","value":"Proposal"}],
The skill tells the agent to create or modify local tracking files but does not pair that instruction with an explicit warning that it will write to the filesystem. This weakens user awareness and consent, increasing the chance of unexpected local state changes or retention of potentially sensitive migration and failure data.
This markdown file includes a concrete download command that saves remote content directly to contract.pdf via --output, which is a file-write operation. While the section explains the API behavior, it does not explicitly warn the user that running the command will create or overwrite a local file.
The line 'Runs everything from Python' imposes a specific implementation language in the template content without offering the user a choice or documenting an opt-in. This can violate language or locale policy where skills should not force a language absent explicit user preference or justified scope.
Detected: suspicious.exposed_secret_literal