T09 · Insecure Skill Coding Practices
- Location
scripts/execute.py:147- Finding
Context-Blind Bulk Lifecycle Mutation Contradicts the Documented Assignment Policy
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This HubSpot cleanup skill has a legitimate purpose, but its executable script can bulk change CRM records to Lead without adequate safeguards or matching the more careful instructions in the skill text.
Review and modify this skill before using it on a real HubSpot portal. Run only the audit first, verify the target account and record counts, use a narrowly scoped token or sandbox, export record-level backups, and require an explicit apply step before any lifecycle-stage changes or workflow activation.
scripts/execute.py:147Context-Blind Bulk Lifecycle Mutation Contradicts the Documented Assignment Policy
scripts/execute.py:38High-Volume Non-Transactional CRM Updates Lack Adequate Execution Safeguards
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"properties": ["lifecyclestage"],
"limit": 1,
}
resp = requests.post(
f"{BASE}/crm/v3/objects/{object_type}/search",
headers=HEADERS, json=body,
)
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"properties": ["lifecyclestage"],
"limit": 1,
}
resp = requests.post(
f"{BASE}/crm/v3/objects/{object_type}/search",
headers=HEADERS, json=body,
)
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
body["after"] = after
for attempt in range(MAX_RETRIES):
resp = requests.post(
f"{BASE}/crm/v3/objects/{object_type}/search",
headers=HEADERS, json=body,
)
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
}
for attempt in range(MAX_RETRIES):
resp = requests.post(
f"{BASE}/crm/v3/objects/{object_type}/batch/update",
headers=HEADERS, json=body,
)
The declared description presents an active remediation skill: it should update HubSpot records and create workflows to prevent future issues. The supplied code only queries HubSpot for counts of lifecycle stages on contacts and companies, checks whether disallowed stages or empty values remain, exports results to CSV, and prints PASS/FAIL plus manual workflow recommendations. There are no API calls that update objects or create workflows—only search/read operations. Therefore the actual behavior is materially narrower and different from the declared purpose.
The declared purpose promises corrective and preventive actions: updating missing lifecycle stages, repairing invalid stages, and creating workflows to prevent recurrence. However, this code chunk is a 'before state' audit script. It makes read-only search requests to HubSpot, aggregates counts, outputs a CSV report, and notes that another execute script would handle changes. Because the actual behavior is limited to auditing and reporting, the description materially overstates what this supplied code chunk does.
The code does partially align with the description in that it fixes contacts and companies with missing or disallowed lifecycle stages using the HubSpot API. However, the declared description is broader and claims an additional capability the code does not perform: creating prevention workflows. The script only outputs instructions suggesting that a human create workflows afterward. Also, the stated purpose implies ensuring appropriate lifecycle stages generally, but the actual implementation applies a single hardcoded remediation—moving all matched records to Lead. That is a materially narrower and more specific behavior than the description suggests.
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.
A direct set to an earlier stage will be **silently rejected** — no error, no warning, the value simply does not change. This is the single most common gotcha when fixing lifecycle stages.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
from dotenv import load_dotenv
# ── Configuration ────────────────────────────────────────────────
load_dotenv(os.path.join(os.path.dirname(__file__), "..", ".env"))
TOKEN = os.environ["HUBSPOT_ACCESS_TOKEN"]
BASE = "https://api.hubapi.com"
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
from dotenv import load_dotenv
# ── Configuration ────────────────────────────────────────────────
load_dotenv(os.path.join(os.path.dirname(__file__), "..", ".env"))
TOKEN = os.environ["HUBSPOT_ACCESS_TOKEN"]
BASE = "https://api.hubapi.com"
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
from dotenv import load_dotenv
# ── Configuration ────────────────────────────────────────────────
load_dotenv(os.path.join(os.path.dirname(__file__), "..", ".env"))
TOKEN = os.environ["HUBSPOT_ACCESS_TOKEN"]
BASE = "https://api.hubapi.com"
The skill includes code and instructions that use environment-sourced credentials and make live HubSpot API calls, yet it declares no explicit tool scope or permission boundaries. In an agent environment, missing scope declarations can allow unintended access to network, secrets, or write-capable tooling without clear operator review.
The skill instructs bulk modification of CRM lifecycle stages and creation of prevention workflows in a live system without strong warnings, dry-run guidance, rollback planning, or approval checkpoints. In a production CRM, this can cause large-scale unintended data changes, reporting corruption, and workflow side effects if used incautiously.
This code reads HUBSPOT_ACCESS_TOKEN from the environment and uses it to authenticate requests to HubSpot, which is a sensitive credential access plus network transmission of account data. While the module docstring describes the script's functional purpose, it does not disclose that it will use an access token and make live API calls to an external service.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
"properties": ["lifecyclestage"],
"limit": 1,
}
resp = requests.post(
f"{BASE}/crm/v3/objects/{object_type}/search",
headers=HEADERS, json=body,
)
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
"properties": ["lifecyclestage"],
"limit": 1,
}
resp = requests.post(
f"{BASE}/crm/v3/objects/{object_type}/search",
headers=HEADERS, json=body,
)
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
"properties": ["lifecyclestage"],
"limit": 1,
}
resp = requests.post(
f"{BASE}/crm/v3/objects/{object_type}/search",
headers=HEADERS, json=body,
)
The documentation states disallowed stages include '(empty)' and describes a general clear-then-set fix for such records. However, the code uses the search operator 'NOT_HAS_PROPERTY', which matches records lacking the property rather than necessarily all records whose lifecycle stage is empty, creating a contradiction between stated intent and implemented selection logic.
This script performs bulk modifications of CRM records and can reset lifecycle stages for large numbers of contacts and companies with minimal operator friction. In the skill context, that is more dangerous because a mistaken run, bad assumptions about 'disallowed' stages, or incomplete empty-stage detection can cause high-impact integrity damage to production CRM data.
}
for attempt in range(MAX_RETRIES):
resp = requests.post(
f"{BASE}/crm/v3/objects/{object_type}/batch/update",
headers=HEADERS, json=body,
)
The manifest description says the skill 'creates prevention workflows to stop future gaps,' implying automated workflow creation as part of the skill's behavior. In this file, the code only outputs human-readable instructions to create two HubSpot workflows and performs no API calls or automation for workflow creation.
The script writes after_fix_lifecycle.csv and later opens it in write mode, which can create or overwrite a local file. Although the docstring mentions a CSV audit trail and the script prints the output path after writing, there is no advance warning before the file write occurs.
The script reads HUBSPOT_ACCESS_TOKEN from the environment to authenticate against HubSpot, but there is no comment, prompt, or explicit user-facing warning near that access. For a code file, accessing credentials is a safety-relevant operation that should be disclosed unless clearly warned elsewhere in the skill materials.
No suspicious patterns detected.