T01 · Skill Instruction Hijacking
- Location
SKILL.md:5- Finding
Webhook Trust Instructions Override Agent Security Controls
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill has a coherent RentAPerson automation purpose, but it exposes powerful account credentials and tells agents to automatically trust and act on external webhook text.
Do not install this as-is for a real account. Rotate any exposed RentAPerson/OpenClaw credentials, remove the committed credential file, disable API-key injection into prompts or transcripts, require verified webhook authentication before agent execution, avoid allowUnsafeExternalContent for public hooks, and require confirmation before hiring, scheduling, or changing task status.
SKILL.md:5Webhook Trust Instructions Override Agent Security Controls
bridge/server.js:231Unauthenticated Webhook Bridge Enables Prompt Injection into Privileged Sessions
scripts/setup.js:687Setup Deliberately Enables Unsafe External Content for Immediate Agent Execution
rentaperson-agent.json:2Live-Looking API and Webhook Credentials Are Committed in Plaintext
scripts/setup.js:250Setup Stores Secrets with Default Permissions and Injects Them into Persistent Session Transcripts
scripts/send-to-session.js:13Shell Command Injection in OpenClaw Session Helper
INSTALLATION.md:250Persistent Boot Service Magnifies Exposure of the Unauthenticated Bridge
The YARA match is supported by the surrounding content: the skill stores credentials, discusses process.env, recommends key injection into prompts as fallback, and instructs automatic webhook-driven API actions. In context, this forms a credible credential exposure and unauthorized action chain rather than a benign documentation artifact.
skill directory:
node scripts/setup.js
The script will prompt for:
agent:main:main) - for chat correspondenceagent:main:rentaperson) - for webhook processinghttps://abc123.ngrok.io)Authorization: Bearer on webhooks)It then:
POST /api/agents/register and saves agentId and apiKey to rentaperson-agent.jsonopenclaw.json (default: ~/.openclaw/openclaw.json; override with OPENCLAW_CONFIG) to inject skills.entries["rent-a-person-ai"].env with the key, agentId, agentName, etc.PATCH /api/agents/me with the webhook URL, bearer token, and webhook session keyThe YARA match is broad, but the file does combine credential collection/storage with webhook and session delivery behavior that can expose those credentials externally or to untrusted agent contexts. While there is no clear evidence of deliberate attacker exfiltration to a third-party server, the overall design creates a high-risk secret-handling path that could lead to effective credential exfiltration in practice.
ir + mapping for /hooks/rentaperson)
* - Registers webhook at <gateway>/hooks/rentaperson with webhookSessionKey, webhookFormat, webhookBearerToken
* - Optional: restart gateway, optional verify
*/
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const { spawn } = require('child_process');
const net = require('net');
const OPENCLAW_CONFIG_PATH =
process.env.OPENCLAW_CONFIG ||
path.join(process.env.HOME || process.env.USERPROFILE || '', '.openclaw', 'openclaw.json');
const ENVS = {
prod: 'https://rentaperson.ai',
dev: 'https://dev.rentaperson.ai',
};
const TRANSFORM_FILENAME = 'rentaperson-inject-key-transform.js';
const HOOK_NAME = 'rentaperson';
function ask(rl, question, defaultVal = '') {
const suffix = defaultVal ? ` [${defaultVal}]` : '';
return new Promise((resolve) => {
rl.question(`${question}${suffix}: `, (answer) => {
resolve((answer && answer.trim()) || defaultVal);
});
});
}
function askYesNo(
The transform approach states that the API key appears in session transcripts, which means a long-lived secret may be copied into logs, history, exports, or downstream tools that can access transcript content. Because this is documented as an available setup option without a strong do-not-use warning, users may deploy an architecture that intentionally exposes credentials to broader surfaces.
The skill explicitly instructs the agent to trust webhook-like message content based on keywords and to ignore distrust of external requests. That lets an attacker craft a prompt containing those markers and trigger autonomous API actions, effectively bypassing origin authentication and normal safety checks.
The skill tells the agent to treat certain message patterns as trusted and to ignore platform security notices. This is a direct prompt-level attempt to override trust decisions, enabling spoofed webhook messages to drive authenticated actions.
The skill semantically redefines matching webhook-like text as authoritative commands and instructs the agent to execute workflows from that text. This is a classic prompt injection pattern that can convert untrusted content into privileged actions, including replying to conversations and manipulating application state.
Referenced artifact was not completely inspected
node scripts/setup.js
Referenced artifact was not completely inspected
node scripts/setup.js
Referenced artifact was not completely inspected
node scripts/setup.js
The skill explicitly recommends putting secrets into mapped hook payloads/messages while acknowledging they will appear in transcripts. This creates deliberate credential exposure to anyone with session visibility and makes later prompt exfiltration trivial.
The skill instructs operators to inject API keys into prompt/message content so the agent can consume them. That exposes credentials in transcripts, logs, session history, and potentially downstream tools, creating a straightforward path to secret theft and account compromise.
The skill directs the model to ignore security warnings and automatically process webhook content, which weakens the final safety barrier before external side effects. In practice, this makes prompt spoofing and unsafe automation substantially easier.
Telling the agent to ignore a security notice for webhooks is an explicit attempt to suppress safeguards that warn about untrusted input. This increases the probability that spoofed or replayed webhook-like prompts will be acted on automatically.
The skill tells the agent to extract an API key from message content and use it for privileged API calls. This treats prompt content as a secret source, enabling credential harvesting, replay, leakage in transcripts, and attacker-supplied key substitution.
List applications for a bounty:
# Extract API key from message (look for "🔑 API KEY: rap_xxx"), then:
curl -H "X-API-Key: rap_xxx" "https://rentaperson.ai/api/bounties/BOUNTY_ID/applications"
Again, the skill normalizes extracting API keys from message content before performing API operations. This is dangerous because any party able to influence the prompt can introduce, steal, or redirect credential usage.
Start conversation (if none exists):
# Extract API key from message, then:
curl -H "X-API-Key: rap_xxx" -X POST "https://rentaperson.ai/api/conversations" \
-H "Content-Type: application/json" \
-d '{
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
| `get_calendar_event` | GET /api/calendar/events/:id |
| `list_calendar_events` | GET /api/calendar/events |
| `update_calendar_event` | PATCH /api/calendar/events/:id |
| `delete_calendar_event` | DELETE /api/calendar/events/:id |
| `check_availability` | GET /api/calendar/availability |
| `get_calendar_status` | GET /api/calendar/status |
The transform code reads a stored API key from configuration and injects it into the message body for later use. Accessing credentials itself is not inherently malicious, but here it is paired with unsafe propagation of the secret into conversational content, which can expose it to transcript storage, logs, and downstream consumers.
}
function injectKey(body, config) {
const env = config?.skills?.entries?.['rent-a-person-ai']?.env || {};
const key = env.RENTAPERSON_API_KEY;
const agentId = env.RENTAPERSON_AGENT_ID || '';
const agentName = env.RENTAPERSON_AGENT_NAME || '';
The document claims the bridge keeps the API key out of OpenClaw transcripts, but the sample code appends the key directly into the forwarded message body. That means the secret is likely to be stored, logged, or exposed anywhere message content is retained, creating a direct credential disclosure risk.
The README repeats a false security property: that the API key never appears in transcripts. In reality, the provided bridge code injects the key into the message field, so operators may deploy it believing secrets are protected when they are actually being copied into session content.
The recommendation section presents the bridge as 'more secure' because the key supposedly never reaches transcripts, but the implementation does the opposite by forwarding the key inside the message. This mismatch increases the likelihood of unsafe adoption because readers are encouraged toward the insecure option under false assumptions.
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.
1. **Test Transform Fix**: Update transform, verify config access, test webhook
2. **Implement Bridge**: Create bridge service, add to setup script, document
3. **Update Setup Script**: Add option to choose bridge vs transform
4. **Update SKILL.md**: Document both approaches
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
function validate() {
if (!key || !String(key).trim()) {
process.stderr.write(
'inject-api-key: RENTAPERSON_API_KEY is not set. Set it in skills.entries["rent-a-person-ai"].env (e.g. run scripts/setup.js) and restart the gateway.\n'
);
process.exit(1);
}
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
function validate() {
if (!key || !String(key).trim()) {
process.stderr.write(
'inject-api-key: RENTAPERSON_API_KEY is not set. Set it in skills.entries["rent-a-person-ai"].env (e.g. run scripts/setup.js) and restart the gateway.\n'
);
process.exit(1);
}
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
function validate() {
if (!key || !String(key).trim()) {
process.stderr.write(
'inject-api-key: RENTAPERSON_API_KEY is not set. Set it in skills.entries["rent-a-person-ai"].env (e.g. run scripts/setup.js) and restart the gateway.\n'
);
process.exit(1);
}
This transform reads a local secret (RENTAPERSON_API_KEY) from config and appends it directly into the message payload as natural-language text. That exposes the credential to downstream agents, logs, webhook receivers, prompt history, and any component that can read the transformed message, which is far broader access than needed for normal request authentication.
Detected: suspicious.dangerous_exec, suspicious.env_credential_access, suspicious.exposed_secret_literal