Back to skill

Security audit

Webhook Router

Security checks for vulnerabilities and agentic risk

Overview

This webhook router is not clearly malicious, but it ships with a reusable public secret and unsafe routing/storage defaults that need careful review before use.

Do not deploy this as-is on a public endpoint. Rotate and remove the published hook token, require per-source secrets and provider signature verification, validate source names before any file or handler lookup, disable raw payload vault storage unless explicitly needed, and add redaction plus retention controls for logs and vault entries.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
register.sh:14
Finding
Published Hard-Coded Webhook Authentication Token<![CDATA[ ## Vulnerability Details **File Location**: `register.sh:14-17`, `register.sh:196-205`; `SKILL.md:33-43`, `SKILL.md:184-201`, `SKILL.md:218-225` **Vulnerability Type**: Hard-coded reusable credential **Risk Level**: High ### Vulnerable Code ```bash # register.sh:14-17 # Default values FUNNEL_URL="${FUNNEL_URL:-https://gregs-mac-mini.taila31444.ts.net}" HOOK_TOKEN="${HOOK_TOKEN:-19e78f0288d476ee1197d4b374b6f73394abe121c12cc38a}" ``` ```bash # register.sh:196-205 ║ Webhook URL: ${WEBHOOK_URL} ╠════════════════════════════════════════════════════════════════╣ ║ Headers to configure: ║ X-Hook-Token: ${HOOK_TOKEN} ║ Content-Type: application/json ... Headers: X-Hook-Token: ${HOOK_TOKEN} Content-Type: application/json ``` ```text # SKILL.md:35-43 https://gregs-mac-mini.taila31444.ts.net/hooks Authentication: Use OpenClaw's hook token in the X-Hook-Token header: X-Hook-Token: 19e78f0288d476ee1197d4b374b6f73394abe121c12cc38a ``` ### Technical Analysis The project embeds both a public webhook endpoint and a reusable authentication token in source code and documentation. `register.sh` uses the published token whenever `HOOK_TOKEN` is not explicitly configured, making the exposed value an operational default rather than merely an example. Secrets committed to a distributed project must be considered compromised. Any person with access to the Skill package or documentation can recover the token. Deployments that retain the default may consequently accept attacker-generated webhook requests as authenticated. The token is also shared across source registrations. Compromise of one integration can therefore affect every integration protected by the same token. ### Attack Path 1. An attacker downloads or inspects the Skill package. 2. The attacker extracts the endpoint and `X-Hook-Token` value from `SKILL.md` or `register.sh`. 3. The attacker submits arbitrary JSON to the documented `/hooks` endpoint using the published token. 4. The ups ...[truncated 945 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Immediately revoke and rotate the published token. 2. Remove the endpoint-specific credential from all source files, examples, generated output, and documentation history. 3. Require `HOOK_TOKEN` to be provided through a protected secret manager or deployment environment: ```bash : "${HOOK_TOKEN:?HOOK_TOKEN must be configured securely}" ``` 4. Do not provide a usable fallback token. 5. Generate a unique, cryptographically random secret during installation when secret-manager integration is unavailable. 6. Use separate secrets for each webhook source so compromise is isolated. 7. Avoid printing complete tokens to standard output; provide a protected configuration file or secret-reference identifier instead. 8. Add automated secret scanning to the repository and release pipeline. 9. Review webhook audit logs after rotation to identify possible requests made with the exposed credential. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
router.sh:176
Finding
Handler Path Traversal Can Execute Files Outside the Handler Directory<![CDATA[ ## Vulnerability Details **File Location**: `router.sh:99-102`, `router.sh:176-190`, `router.sh:216` **Vulnerability Type**: Path traversal leading to unintended local executable invocation **Risk Level**: Critical ### Vulnerable Code ```bash # router.sh:99-102 # Check query parameter style (from header or env) if [[ -n "${WEBHOOK_SOURCE:-}" ]]; then SOURCE="$WEBHOOK_SOURCE" ``` ```bash # router.sh:176-190 # Determine handler HANDLER="" SOURCE_TYPE="${SOURCE%%-*}" # Look for specific handler if [[ -x "${HANDLERS_DIR}/${SOURCE}.sh" ]]; then HANDLER="${HANDLERS_DIR}/${SOURCE}.sh" elif [[ -x "${HANDLERS_DIR}/${SOURCE_TYPE}.sh" ]]; then HANDLER="${HANDLERS_DIR}/${SOURCE_TYPE}.sh" elif [[ -x "${HANDLERS_DIR}/generic.sh" ]]; then HANDLER="${HANDLERS_DIR}/generic.sh" else echo '{"error": "No handler found"}' >&2 exit 1 fi ``` ```bash # router.sh:216 RESULT=$("$HANDLER" "$PAYLOAD" "$SOURCE" "$EVENT_TYPE" 2>&1) ``` ### Technical Analysis `SOURCE` can be supplied explicitly through `--source` or inherited from the webhook environment through `WEBHOOK_SOURCE`. It is not restricted to a safe identifier grammar before being concatenated into a filesystem path. A source containing `../` path components can escape `HANDLERS_DIR`. The `-x` check only determines whether the resulting path is executable; it does not verify that its canonical location remains under the approved handler directory. If the resolved path exists and is executable, the router invokes it. The `.sh` suffix limits candidate filenames, but it does not prevent traversal. For example, a value shaped like `../../../some/path/worker` resolves to an attempted target ending in `worker.sh` outside `handlers/`. ### Attack Path 1. An attacker gains the ability to invoke the router or submit a webhook that controls `WEBHOOK_SOURCE`. The published shared token may provide this ability in affected deployments. 2. The attacker identifies an executable `.sh` file reachable by ...[truncated 1274 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict source identifiers before any filesystem use: ```bash if [[ ! "$SOURCE" =~ ^[a-z0-9][a-z0-9_-]{0,63}$ ]]; then echo '{"error":"Invalid source identifier"}' >&2 exit 1 fi ``` 2. Resolve handlers through a fixed mapping rather than constructing paths from request data: ```bash case "$SOURCE_TYPE" in github) HANDLER="$HANDLERS_DIR/github.sh" ;; custom) HANDLER="$HANDLERS_DIR/custom.sh" ;; *) HANDLER="$HANDLERS_DIR/generic.sh" ;; esac ``` 3. If dynamic handler names are required, canonicalize the candidate and verify containment: ```bash handlers_real=$(realpath "$HANDLERS_DIR") candidate_real=$(realpath -e "$candidate") || exit 1 [[ "$candidate_real" == "$handlers_real/"* ]] || exit 1 ``` 4. Reject `/`, `\`, dot segments, control characters, whitespace, and encoded path separators. 5. Do not treat request-provided environment variables as trusted routing configuration. 6. Run handlers under a dedicated, minimally privileged account with a restricted environment. 7. Add tests covering `../`, absolute paths, symbolic links, encoded separators, and overlong identifiers. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
register.sh:78
Finding
Unvalidated Registration Type Enables File Writes Outside Intended Directories<![CDATA[ ## Vulnerability Details **File Location**: `register.sh:47-54`, `register.sh:78-91`, `register.sh:113-119`, `register.sh:173-176` **Vulnerability Type**: Path traversal and unsafe generated-file placement **Risk Level**: High ### Vulnerable Code ```bash # register.sh:47-54 SOURCE_TYPE="$1" NAME="$2" shift 2 CREATE_HANDLER=false EVENT_TYPES="generic" ``` ```bash # register.sh:78-91 # Generate unique source identifier # Format: <type>-<normalized-name> NORMALIZED_NAME=$(echo "$NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9_-') SOURCE_ID="${SOURCE_TYPE}-${NORMALIZED_NAME}" # Generate unique token for this source (optional extra validation) SOURCE_TOKEN=$(openssl rand -hex 16 2>/dev/null || head -c 32 /dev/urandom | xxd -p | tr -d '\n') # Save registration REGISTRATION_FILE="${CONFIG_DIR}/${SOURCE_ID}.json" ... echo "$REGISTRATION" > "$REGISTRATION_FILE" ``` ```bash # register.sh:113-119 if [[ "$CREATE_HANDLER" == "true" ]]; then HANDLER_FILE="${HANDLERS_DIR}/${SOURCE_TYPE}.sh" if [[ -f "$HANDLER_FILE" ]]; then echo "Note: Handler for '$SOURCE_TYPE' already exists at $HANDLER_FILE" else cat > "$HANDLER_FILE" << 'HANDLER_TEMPLATE' ``` ```bash # register.sh:173-176 # Replace template variables sed -i.bak "s/{{SOURCE_TYPE}}/${SOURCE_TYPE}/g" "$HANDLER_FILE" && rm -f "${HANDLER_FILE}.bak" chmod +x "$HANDLER_FILE" ``` ### Technical Analysis Although `NAME` is normalized, `SOURCE_TYPE` is accepted without validation. It is incorporated into: - `SOURCE_ID`, which is used in the registration JSON path. - `HANDLER_FILE`, which is created when `--handler` is specified. - A dynamically constructed `sed` replacement expression. Path separators and `..` components in `SOURCE_TYPE` can therefore escape `.config` or `handlers`. The generated shell template has fixed primary content, so this is not a direct primitive for writing fully arbitrary bytes. Nevertheless, it permits unintended `.sh` file creation or ...[truncated 1677 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `SOURCE_TYPE` and `NAME` using strict, length-limited allowlists: ```bash [[ "$SOURCE_TYPE" =~ ^[a-z0-9][a-z0-9_-]{0,31}$ ]] || exit 1 [[ "$NORMALIZED_NAME" =~ ^[a-z0-9][a-z0-9_-]{0,63}$ ]] || exit 1 ``` 2. Explicitly reject empty normalized names, path separators, dot segments, control characters, and symbolic-link destinations. 3. Canonicalize parent directories and verify every output remains beneath `CONFIG_DIR` or `HANDLERS_DIR`. 4. Create files without overwriting existing paths: ```bash set -o noclobber printf '%s\n' "$REGISTRATION" > "$REGISTRATION_FILE" ``` 5. Check for symbolic links before writing and use secure file-creation mechanisms where available. 6. Replace dynamic `sed` interpolation with a safe templating method that passes the value as data rather than executable substitution syntax. 7. Apply restrictive permissions to registration files because they contain source tokens: ```bash umask 077 ``` 8. Separate registration administration from the public webhook runtime and restrict who can invoke `register.sh`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
router.sh:99
Finding
Webhook Provider Signatures Are Detected but Never Verified<![CDATA[ ## Vulnerability Details **File Location**: `router.sh:99-125`; `SKILL.md:184-201` **Vulnerability Type**: Missing webhook authenticity and integrity validation **Risk Level**: High ### Vulnerable Code ```bash # router.sh:99-125 if [[ -z "$SOURCE" ]]; then # Check query parameter style (from header or env) if [[ -n "${WEBHOOK_SOURCE:-}" ]]; then SOURCE="$WEBHOOK_SOURCE" # Check for GitHub signature/header elif [[ -n "${HTTP_X_GITHUB_EVENT:-}" ]] || [[ -n "${HTTP_X_HUB_SIGNATURE:-}" ]]; then REPO=$(echo "$PAYLOAD" | jq -r '.repository.full_name // .repository.name // "unknown"' 2>/dev/null || echo "unknown") SOURCE="github-${REPO//\//-}" # Check for GitLab elif [[ -n "${HTTP_X_GITLAB_EVENT:-}" ]]; then PROJECT=$(echo "$PAYLOAD" | jq -r '.project.path_with_namespace // "unknown"' 2>/dev/null || echo "unknown") SOURCE="gitlab-${PROJECT//\//-}" # Check for Stripe elif [[ -n "${HTTP_STRIPE_SIGNATURE:-}" ]]; then SOURCE="stripe-webhook" ``` ```text # SKILL.md:184-192 ### GitHub 1. Go to repository → Settings → Webhooks → Add webhook 2. Payload URL: https://gregs-mac-mini.taila31444.ts.net/hooks?source=github-<repo> 3. Content type: application/json 4. Secret: (leave blank, token is in header) 5. Events: Select events or "Let me select individual events" 6. Add header: X-Hook-Token: 19e78f0288d476ee1197d4b374b6f73394abe121c12cc38a ``` ### Technical Analysis The router checks whether GitHub, GitLab, or Stripe-related headers are present, but it never validates their cryptographic values. A header's presence is used only as a source-classification hint. The documentation explicitly advises leaving the GitHub webhook secret blank. As a result, there is no provider-specific proof that a payload was created by GitHub, Stripe, or another claimed sender. All integrations instead rely on one shared OpenClaw hook token. A shared gateway token may protect endpoint access, but it doe ...[truncated 1396 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Configure a unique provider secret for every source registration. 2. Verify signatures against the exact raw request body before JSON parsing or routing. 3. For GitHub, validate `X-Hub-Signature-256` using HMAC-SHA-256 and constant-time comparison. 4. For Stripe, use the official signature format, enforce timestamp tolerance, and reject replayed events. 5. Implement the documented verification mechanism for GitLab and every other supported provider. 6. Reject requests with missing, malformed, expired, or invalid signatures. 7. Bind each provider secret to the expected source identifier rather than accepting a global shared secret. 8. Store processed provider event IDs with an expiration period to prevent replay. 9. Retain the gateway token only as an additional defense layer, not as a replacement for provider verification. 10. Update documentation so users are instructed to configure provider secrets rather than leave them blank. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
handlers/custom.sh:41
Finding
Webhook Payloads Are Persisted Without Field-Level Redaction<![CDATA[ ## Vulnerability Details **File Location**: `handlers/custom.sh:26-31`, `handlers/custom.sh:41-54`; `handlers/generic.sh:40-48`, `handlers/generic.sh:55-61`, `handlers/generic.sh:107-133`; `register.sh:138-169` **Vulnerability Type**: Excessive retention of untrusted and potentially sensitive data **Risk Level**: Medium ### Vulnerable Code ```bash # handlers/custom.sh:26-31 write_to_vault() { local path="$1" local content="$2" local tags="$3" if command -v vault &> /dev/null; then vault write "$path" --data "$content" --tags "$tags" 2>/dev/null || true fi } ``` ```bash # handlers/custom.sh:41-54 VAULT_CONTENT=$(jq -n \ --arg ts "$TIMESTAMP" \ --arg source "$SOURCE" \ --arg event "$EVENT_TYPE" \ --arg payload "$PAYLOAD" \ '{ timestamp: $ts, source: $source, event_type: $event, payload: $payload }') write_to_vault "${VAULT_SECTION}/${SOURCE}/${TIMESTAMP}" "$VAULT_CONTENT" "custom,webhook" ``` ```bash # handlers/generic.sh:55-62 # Truncate payload if too large PAYLOAD_SIZE=$(echo "$PAYLOAD" | wc -c) if [[ $PAYLOAD_SIZE -gt $LOG_LIMIT ]]; then TRUNCATED_PAYLOAD=$(echo "$PAYLOAD" | head -c $LOG_LIMIT) TRUNCATED_PAYLOAD="${TRUNCATED_PAYLOAD}... [truncated, total size: $PAYLOAD_SIZE bytes]" else TRUNCATED_PAYLOAD="$PAYLOAD" fi ``` ```bash # handlers/generic.sh:107-133 VAULT_CONTENT=$(jq -n \ --arg ts "$TIMESTAMP" \ --arg source "$SOURCE" \ --arg event "$EVENT_TYPE" \ --arg identifier "$IDENTIFIER" \ --arg service "$SERVICE_NAME" \ --arg event_name "$EVENT_NAME" \ --arg status "$STATUS" \ --arg user "$USER" \ --arg payload "$TRUNCATED_PAYLOAD" \ --argjson important "$IS_IMPORTANT" \ '{ timestamp: $ts, source: $source, event_type: $event, identifier: $identifier, service: $service, event_name: $event_name, status: $status, user: $user, payload: $p ...[truncated 2043 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store only allowlisted metadata required for operations, such as event ID, source, event type, timestamp, and a payload hash. 2. Disable full-payload persistence by default and require explicit per-source opt-in. 3. Recursively redact sensitive field names before storage, including: - `authorization` - `token` - `secret` - `password` - `api_key` - `cookie` - payment and personal-data fields 4. Apply redaction before truncation so secrets near the start of a payload are not retained. 5. Encrypt retained webhook data at rest and in transit. 6. Enforce least-privilege access controls on the vault and any synchronized copies or backups. 7. Define retention periods and automatically delete expired webhook records. 8. Avoid placing unvalidated source and event values directly into storage paths; normalize them first. 9. Correct the documentation to distinguish metadata audit logging from optional payload retention. 10. Add automated tests verifying that representative credentials and personal-data fields never appear in persisted output. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (15)

Missing User Warnings

High
Confidence
99% confidence
Finding
The skill publishes what appears to be a live hook authentication token and instructs users to reuse it in multiple webhook configurations. Exposing a bearer-style secret in documentation enables unauthorized parties to send forged webhooks to the public endpoint, potentially triggering actions, alerts, or downstream automation as trusted events.

Ae1

High
Category
analysis-evasion
Content
- `SKILL.md` - This documentation
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Missing User Warnings

High
Confidence
95% confidence
Finding
The generated handler forwards the complete webhook payload into a vault entry, which may transmit and retain highly sensitive information such as customer data, API metadata, or embedded secrets. Because this happens in a template intended for broad reuse and there is no warning or filtering, it increases the chance of compliance, privacy, and secret-exposure issues across all deployed handlers.

Chaining Abuse

High
Category
Tool Misuse
Content
HANDLER_TEMPLATE

        # Replace template variables
        sed -i.bak "s/{{SOURCE_TYPE}}/${SOURCE_TYPE}/g" "$HANDLER_FILE" && rm -f "${HANDLER_FILE}.bak"
        chmod +x "$HANDLER_FILE"
        
        echo "Created handler template: $HANDLER_FILE"
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

External Transmission

Medium
Category
Data Exfiltration
Content
Sends mock webhooks to test the routing system.

### Manual Test with curl

```bash
# GitHub push event
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The documentation says webhook payloads are logged as hashes, but the handler example explicitly writes the full payload to vault storage. That discrepancy can cause operators to handle sensitive webhook data under false assumptions, increasing the risk of credential, token, or personal data retention and disclosure.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script stores the full webhook payload in the vault verbatim, and webhook payloads often contain secrets, personal data, tokens, headers, URLs, or attacker-controlled content. Persisting all received data without minimization, filtering, or any disclosure increases the risk of sensitive-data retention, insider exposure, and later compromise if the vault contents are accessed or exported.

Intent-Code Divergence

Medium
Confidence
88% confidence
Finding
The file header describes this as a generic webhook handler that 'logs payload and attempts to extract meaningful information.' In practice, the script writes structured webhook data including payload content to a vault (L107-L133) and sends alert messages for important events (L157-L167), which are materially different side effects from simple logging.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The handler stores attacker-controlled webhook payload content into persistent vault storage by default, including potentially sensitive fields such as user identifiers, emails, status details, and raw payload fragments. In a generic webhook handler, this is dangerous because unknown third-party sources can cause sensitive, excessive, or maliciously crafted data to be retained indefinitely, increasing exposure in the event of vault misuse, over-retention, or downstream access by other tools.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
When a payload is classified as important, the script sends extracted webhook details to an external alert channel, potentially exposing sensitive operational, user, or incident information to systems outside the primary processing boundary. This is particularly risky in a generic webhook context because untrusted payload content can influence alert content, causing unintended data disclosure or message injection into downstream notification systems.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The script writes registration data including a per-source token to a local JSON file under .config without setting restrictive permissions or warning the user that secret material is being persisted. If the workspace is shared, backed up, or readable by other local users/processes, the token could be exposed and used to spoof webhook traffic if relied on elsewhere.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The generated webhook handler stores full inbound payloads in a vault path, which can capture secrets, PII, tokens, or proprietary event contents unrelated to the operational need of handling the webhook. In this context the behavior is built into an auto-generated template and not clearly disclosed, making accidental over-collection and sensitive data retention likely.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This code logs source, event type, repository, sender, payload hash, and handler information to a persistent file via LOG_FILE. For code files, safety-relevant data handling should have some visible disclosure such as a prompt, print statement, or explanatory comment/docstring, but the script performs the write silently from the user's perspective.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This line executes another shell script as a subprocess and passes it the webhook payload and derived metadata. Although subprocess execution is core behavior for a router, there is no visible runtime disclosure that untrusted webhook content will be handed off to executable handlers, which fits the missing-warning criteria for code files.

Intent-Code Divergence

Low
Confidence
82% confidence
Finding
The file header describes the script as a GitHub webhook handler that processes events and triggers appropriate actions. In practice, those actions include writing structured repository event data to a vault and sending notifications through an external message tool, which are meaningful side effects not reflected in the documentation.