T09 · Insecure Skill Coding Practices
- Location
scripts/gtm.py:673- Finding
Hard-Coded Exa API Credential
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill mostly matches a GTM tracking tool, but it includes under-disclosed production CRM access and an embedded Exa API key that users should review before installing.
Install only after reviewing and disabling or fixing the sensitive integrations: remove and rotate the embedded Exa key, require explicit user-provided credentials, document all third-party API destinations, and gate HubSpot production sync behind clear configuration and confirmation. Treat the local SQLite database as containing sensitive CRM/contact data if hubspot-sync is used.
scripts/gtm.py:673Hard-Coded Exa API Credential
scripts/gtm.py:772Undocumented Retrieval of a Production HubSpot Secret and Local Replication of CRM PII
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
try:
req = urllib.request.Request(url, headers={'User-Agent': 'GTM-Tracker/1.0'})
with urllib.request.urlopen(req, timeout=15) as response:
data = json.loads(response.read().decode())
except (urllib.error.URLError, ConnectionResetError, ssl.SSLError) as e:
print(f" ⚠️ Algolia error for '{query[:40]}': {e}")
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
try:
req = urllib.request.Request(url, headers={'User-Agent': 'GTM-Tracker/1.0'})
with urllib.request.urlopen(req, timeout=15) as response:
data = json.loads(response.read().decode())
except (urllib.error.URLError, ConnectionResetError, ssl.SSLError) as e:
print(f" ⚠️ Algolia error for '{query[:40]}': {e}")
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
try:
req = urllib.request.Request(url, headers={'User-Agent': 'GTM-Tracker/1.0'})
with urllib.request.urlopen(req, timeout=15) as response:
data = json.loads(response.read().decode())
except (urllib.error.URLError, ConnectionResetError, ssl.SSLError) as e:
print(f" ⚠️ Algolia error for '{query[:40]}': {e}")
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
headers={'User-Agent': 'GTM-Tracker/1.0'}
)
try:
with urllib.request.urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode())
except (urllib.error.URLError, json.JSONDecodeError):
continue
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
headers={'User-Agent': 'GTM-Tracker/1.0'}
)
try:
with urllib.request.urlopen(req, timeout=10) as response:
data = json.loads(response.read().decode())
except (urllib.error.URLError, json.JSONDecodeError):
continue
The Exa request includes an API key sourced from the environment or a hardcoded fallback and transmits it to an external service. While that is functionally expected, embedding a fallback secret and automatically using it increases the chance of unauthorized service use, secret leakage, and accidental billing exposure if the code is redistributed.
)
try:
with urllib.request.urlopen(req, timeout=15) as response:
data = json.loads(response.read().decode())
except (urllib.error.URLError, json.JSONDecodeError) as e:
print(f" ⚠️ Exa query failed: {e}")
A hardcoded default Exa API key in source code is a real secret-management vulnerability. Anyone with access to the code can reuse the key, incur charges, access associated account resources, and the secret is difficult to rotate once propagated.
The skill describes collecting data from multiple external sources and sending notifications through Telegram, yet it does not warn users that business/contact information and crawled content may be transmitted to third parties or stored in logs/cache. This creates privacy and data-handling risk, especially if personally identifiable information, business intelligence, or sensitive opportunity data is pushed through Telegram or external crawlers without explicit disclosure and minimization.
The README explicitly encourages unconstrained natural-language Telegram interactions such as adding notes and finding opportunities, but provides no indication of command scoping, confirmation requirements, or input validation. In an agent-driven environment, broad NL triggers can cause unintended state changes, prompt-injection propagation from chat content, or execution of sensitive actions from ambiguous user input.
The document positively recommends developer-signal and telemetry platforms that deanonymize sign-ups, track docs visits, API usage, and related behavioral signals without any accompanying privacy, consent, retention, or legal-compliance cautions. In a GTM strategy context, this omission can directly normalize collection of personal or pseudonymous user data in ways that create privacy, compliance, and reputational risk.
The section recommends website visitor identification tools and frames them as beneficial for seeing which companies visit the site, but does not warn about the tracking implications or limits of such identification. This is dangerous because teams may deploy these tools without evaluating consent requirements, notice obligations, IP-based identification accuracy, or whether the resulting data could be used in intrusive outreach.
The skill explicitly instructs users to run crawler commands against external sources (HN, Reddit, GitHub) but provides no warning that these actions require network access and may transmit system-identifying information such as IP address, request headers, query terms, or tracked keywords to third-party services. In an agent-skill context, undisclosed network activity is risky because users may invoke the commands expecting only local GTM workflow operations, while the crawl feature reaches outside the trust boundary.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
for query in pain_queries:
encoded = urllib.parse.quote(query)
req = urllib.request.Request(
f'https://api.search.brave.com/res/v1/web/search?q={encoded}&count=10&freshness=pm',
headers={'X-Subscription-Token': brave_key, 'Accept': 'application/json'}
)
try:
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
try:
for term in search_terms:
req = urllib.request.Request(
f'https://api.github.com/search/repositories?q={term}&sort=updated&per_page=20',
headers={
'User-Agent': 'GTM-Tracker/1.0',
'Accept': 'application/vnd.github.v3+json'
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}).encode()
req = urllib.request.Request(
'https://api.exa.ai/search',
data=payload,
headers={
'Content-Type': 'application/json',
The hubspot_sync routine retrieves a CRM access token, calls the HubSpot API, and pulls contact records including names, emails, companies, and engagement timestamps into the local database. While there are status prints during execution, there is no explicit warning in code comments, prompts, or surrounding documentation that this command processes potentially sensitive contact data from an external service and stores it locally.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# Get HubSpot API token
try:
result = subprocess.run(
['doppler', 'secrets', 'get', 'HUBSPOT_API_TOKEN', '--plain', '--project', 'openclaw-server', '--config', 'prd'],
capture_output=True, text=True, timeout=10
)
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
thirty_days_ago = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
# HubSpot contacts API with engagement filter
contacts_url = f"https://api.hubapi.com/crm/v3/objects/contacts?limit=100&properties=firstname,lastname,email,company,jobtitle,lastmodifieddate,hs_latest_open_date,hs_latest_click_date"
req = urllib.request.Request(
contacts_url,
Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.
high_items = [e for e in escalations if e['priority'] == 'HIGH']
if high_items:
output.append("**HIGH:**")
for e in high_items[:3]: # Limit to top 3 to avoid spam
output.append(f" • {e['message']}")
conn.close()
The webhook example ingests third-party signals and stores request data directly with no warning about sanitization, minimization, retention, or access controls for potentially user-related information. Although it is only illustrative documentation, it can encourage implementers to persist external behavioral data by default, increasing privacy and data-governance risk.
This markdown file instructs users to identify companies, use LinkedIn Sales Navigator, gather contact details, and send personalized cold emails, which implies processing prospect personal/business data. It does not include any warning about privacy, consent, or compliance considerations for outreach and data handling.
The document proposes a web page with a lead capture form, which would collect user information, but provides no disclosure about privacy, retention, or secure handling of submitted data. For a markdown skill description, this is a user-data-affecting behavior that should be explicitly warned about.
The docstring for generate_daily_digest says it is suitable for sending via Telegram, which implies messaging-oriented behavior, but the implementation only builds a markdown-like string, prints it, and returns it. This is a documentation-to-code mismatch because no Telegram integration or delivery occurs.
No suspicious patterns detected.