T09 · Insecure Skill Coding Practices
- Location
scripts/replay_hemlane_graphql.py:6- Finding
Generic GraphQL replay can transmit Hemlane credentials to an arbitrary endpoint
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This Hemlane automation skill is coherent in purpose, but it handles live browser session credentials and can perform real account actions with weak safeguards.
Install only after reviewing the live-account impact. Use a dedicated low-privilege Hemlane account if possible, avoid capturing auth from a normal browser profile, keep auth files private and short-lived, do not set HEMLANE_GRAPHQL_ENDPOINT unless you fully trust the destination, and treat the write tools as real production actions that can message tenants, post comments, submit referrals, or create lease records.
scripts/replay_hemlane_graphql.py:6Generic GraphQL replay can transmit Hemlane credentials to an arbitrary endpoint
scripts/query_catalog_operation.py:12Catalog replay sends runtime credentials to an environment-controlled destination
scripts/capture_hemlane_auth_via_cdp.py:214CDP authentication capture exposes complete browser session secrets through MCP output and insecure files
scripts/send_hemlane_tenant_reply.py:31Write wrappers use predictable shared temporary files for sensitive and state-changing payloads
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
payload = {'operationName': operation_name, 'query': query, 'variables': variables}
req = request.Request(ENDPOINT, data=json.dumps(payload).encode(), headers=headers, method='POST')
try:
with request.urlopen(req, timeout=60) as resp:
body = resp.read().decode('utf-8', 'replace')
except error.HTTPError as e:
body = e.read().decode('utf-8', 'replace')
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
method='POST'
)
try:
with request.urlopen(req, timeout=60) as resp:
body = resp.read().decode('utf-8', errors='replace')
try:
data = json.loads(body)
If the skill or associated code performs hidden caller identity checks and privileged-write authorization via environment/metadata inspection or hardcoded identity material, that behavior is materially different from the declared Hemlane analysis purpose. Undisclosed identity gating can conceal backdoor-like control paths, create unexpected denial of service for legitimate users, and undermine trust in what the skill is actually doing.
If the skill or associated code performs hidden caller identity checks and privileged-write authorization via environment/metadata inspection or hardcoded identity material, that behavior is materially different from the declared Hemlane analysis purpose. Undisclosed identity gating can conceal backdoor-like control paths, create unexpected denial of service for legitimate users, and undermine trust in what the skill is actually doing.
This script deliberately harvests live Hemlane authentication artifacts from an authenticated browser session, including authorization headers, CSRF tokens, and cookies, then prints or writes them to disk without any interactive confirmation, masking, or scope restriction. In the context of a skill whose stated purpose is reconstructing Hemlane workflows from browser captures, this enables direct session hijacking and unauthorized API access, making the finding substantially more dangerous than a generic debugging utility.
The README explicitly instructs operators to supply or capture live Hemlane session artifacts including cookies, CSRF tokens, authorization headers, user agent, referer, and origin, and it also exposes a write-capable auth capture path. Even though it says secrets should be runtime-only, the documentation does not prominently warn that these browser session materials can grant direct account access and enable state-changing actions, increasing the risk of credential misuse, accidental disclosure, or unintended writes.
The skill advertises substantial capabilities—shell, network, file access, env access, and write operations—without any explicit permission scoping or allowed-tools declaration. In a skill that can capture browser auth artifacts and replay authenticated requests, the lack of declared boundaries increases the risk of overbroad execution and misuse beyond the user's expected task.
The documentation explicitly covers capture and replay of authenticated browser artifacts but does not present a clear warning about privacy exposure, token sensitivity, or account-impact risks. Because these artifacts can grant direct access to user data and actions, omission of strong warnings increases the chance of accidental credential handling mistakes or unsafe replay against production accounts.
The listed tools include write-capable operations such as tenant replies, referrals, work-order comments, maintenance comments, and lease creation, yet the markdown does not clearly warn that these are live, potentially irreversible actions. In this context, a user may mistake them for analysis helpers and unintentionally send messages, create records, or alter production state.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
cmd = ["python3", str(script_path)] + (args or [])
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
if result.returncode != 0:
return {"error": result.stderr, "stdout": result.stdout}
return {"success": True, "output": result.stdout}
Several write-capable tools advertise an auth_file parameter, but the implementation ignores it and instead relies on separate runtime auth handling or environment state. This mismatch can cause operators or higher-level agents to believe a write is bound to specific captured credentials when it is not, increasing the risk of confused-deputy behavior and unintended actions performed under ambient privileged session context.
This document embeds HAR-derived GraphQL examples containing real-looking personal and financial data, including names, emails, phone numbers, user IDs, bank/plaid metadata, transaction details, property information, and tenant communications. Even without secrets like passwords, publishing realistic captured session data in a reusable skill materially increases privacy, compliance, and social-engineering risk, and the skill context makes this more dangerous because it is explicitly designed to reconstruct workflows from saved browser captures.
The file catalogs broad data-access operations spanning current user identity, financial accounts, tenant groups, transactions, phone numbers, emails, maintenance records, and Plaid-linked banking metadata. In combination with browser session artifacts, this serves as a high-value blueprint for large-scale authenticated data harvesting that exceeds the narrow workflow-analysis description and increases the chance of privacy abuse or overcollection.
The catalog is not limited to passive HAR analysis or documentation; it explicitly enumerates live GraphQL mutations such as message creation, maintenance comment creation, and form submission with realistic input shapes. In a skill whose stated purpose includes reconstructing workflows from browser artifacts, this materially lowers the barrier to replaying authenticated write actions against a live Hemlane account and turns reference data into an operational action map.
The sample variables contain real-looking personal data, including full names, phone numbers, and email addresses, embedded directly in a reference artifact. Even if sourced from captures, retaining such PII in reusable skill content creates unnecessary exposure risk, enables secondary misuse, and signals weak data-handling practices for a tool built around browser-derived artifacts.
The document describes syncing tenant and lease data, including names, emails, lease dates, and rent amounts, but provides no warning that the workflow handles sensitive personal and financial information. In this context, an operator could process or export regulated data without appropriate consent, minimization, or safeguards, increasing the risk of privacy violations or unauthorized disclosure.
The runbook explicitly instructs reconstructing and submitting referral data containing personal information, then sending it with fresh authenticated session cookies and CSRF-related headers. That enables real-world authenticated actions and external transmission of PII without any safety gate, user-consent check, or warning that this will perform a live submission.
This instruction sends data to an external Hemlane GraphQL endpoint using fresh browser session cookies and CSRF/header material, which indicates authenticated live transmission rather than offline analysis. In the context of a skill built from HAR-derived patterns and browser session artifacts, this is more dangerous because it normalizes replaying captured authenticated requests and transmitting referral PII to a third-party service.
1. Use `references/graphql-operations.md` and locate `HubspotFormSubmit`.
2. Reconstruct the mutation payload with fields for first name, last name, phone, email, referrer name, and referrer email.
3. Preserve the captured `formId` from the HAR-derived sample unless a newer form capture supersedes it.
4. Submit to `https://api.hemlane.com/graphql` with fresh browser session cookies and any CSRF/header requirements available at runtime.
5. Validate success by checking `hubspotFormSubmission.id` and empty `errors`.
Key operation:
The runbook directs the operator to send live tenant messages and confirm delivery by re-querying message threads, which is a user-facing external communication action. Without warnings, approval requirements, recipient verification, or irreversible-action safeguards, the skill can be used to send unintended or unauthorized messages to tenants.
The runbook instructs posting comments to maintenance requests or work orders and preserving visibility flags exactly as captured. Because these comments can affect tenants, owners, and operational workflows, the absence of warnings and approval controls creates risk of unauthorized or harmful user-facing updates, including accidental disclosure through incorrect visibility settings.
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.
def get_tabs():
"""Get all CDP tabs from Brave."""
import urllib.request
resp = urllib.request.urlopen('http://127.0.0.1:19222/json')
return json.loads(resp.read().decode())
def ensure_hemlane_cdp_context(close_extras=False):
This request sends cookie and CSRF authentication material to an external service while performing a lease-creation mutation. Although the destination is the expected Hemlane API over HTTPS, the dangerous aspect is that the skill is explicitly built to use captured browser session artifacts, so execution can replay privileged actions on behalf of a user without independent authentication or strong operator safeguards.
if survey:
variables["input"]["survey"] = survey
response = requests.post(
HEMLANE_API,
headers=headers,
json={
The script performs state-changing lease creation and optional e-sign creation using captured session cookies and CSRF tokens, but the normal execution path proceeds immediately after parsing arguments with no explicit confirmation, consent gate, or prominent warning about reusing browser session artifacts. In the context of a skill designed to reconstruct workflows from HAR/session captures, this materially increases the risk of unauthorized account actions if the script is run with stale, borrowed, or mistakenly supplied credentials.
This second outbound request reuses captured authentication headers to create an e-sign packet, a further state-changing workflow step with potentially legal/business consequences. In this skill context, chaining sensitive mutations from saved browser artifacts increases the chance of unauthorized document generation or signature workflow initiation if the artifact set was obtained improperly or supplied by mistake.
"user-agent": auth.get("user-agent", "Mozilla/5.0 OpenClaw Hemlane Skill"),
}
response = requests.post(
HEMLANE_API,
headers=headers,
json={
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
}
response = requests.post(
endpoint,
json={"query": RENT_ROLL_QUERY, "variables": variables},
headers=auth_headers
No suspicious patterns detected.