Back to skill

Security audit

webhook-automation

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent webhook helper, but its sample server can accept unauthenticated events while presenting itself as signature-verified automation.

Review before installing or using. Do not expose the included server as-is. Require a configured secret, reject missing or invalid signatures, implement provider-specific verification for GitHub, Slack, and Stripe, limit request size, avoid logging or persisting full payloads, and treat webhook fields as untrusted data before passing anything to an agent or external integration.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
scripts/webhook_server.py:20
Finding

Webhook Authentication Fails Open for Unsigned Requests

Content
View full analysis
bool: """Verify HMAC-SHA256 signature from provider.""" if not secret: return True # Skip verification if no secret configured expected = hmac.new(secret.encode(), payload_bytes, hashlib.sha256).hexdigest() return hmac.compare_digest(f"sha256={expected}", signature) ``` ```python signature = self.headers.get("X-Hub-Signature-256", "") or \ self.headers.get("X-Signature-256", "") or \ self.headers.get("X-Slack-Signature", "") if signature and not verify_signature(body, signature, WEBHOOK_SECRET): logger.warning("Invalid signature — rejecting request") self.send_response(401) self.end_headers() return ``` ### Technical Analysis Signature verification is only performed when the request contains a recognized signature header. If the header is absent, the condition evaluates to false and request processing continues. In addition, `verify_signature()` explicitly returns `True` when no secret is configured. This creates two fail-open states: 1. A client can omit the signature header even when the server has a secret. 2. All verification is disabled when the secret file and `WEBHOOK_SECRET` environment variable are absent. Because the server binds to all network interfaces, this flaw can allow unauthenticated remote clients to submit forged events. ### Attack Path 1. An attacker identifies the webhook service listening on port 8443. 2. The attacker sends a JSON POST request without any recognized signature header. 3. The `if signature and ...` condition is skipped because `signature` is empty. 4. The attacker supplies an event type through ...[truncated 682 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/webhook_server.py:20
Finding

Incompatible Signature Verification Across Webhook Providers

Content
View full analysis
bool: """Verify HMAC-SHA256 signature from provider.""" if not secret: return True # Skip verification if no secret configured expected = hmac.new(secret.encode(), payload_bytes, hashlib.sha256).hexdigest() return hmac.compare_digest(f"sha256={expected}", signature) ``` ```python signature = self.headers.get("X-Hub-Signature-256", "") or \ self.headers.get("X-Signature-256", "") or \ self.headers.get("X-Slack-Signature", "") ``` ### Technical Analysis The implementation applies GitHub's `sha256=` signature format to every recognized provider. This is incompatible with the protocols used by Slack and Stripe: - Slack signs `v0:{timestamp}:{raw_body}` and uses a signature beginning with `v0=`. Its timestamp must also be validated to prevent replay. - Stripe uses the `Stripe-Signature` header containing timestamped elements such as `t=,v1=`. - The server does not read `Stripe-Signature` at all. Consequently, legitimate Slack signatures cannot be validated by this function. Stripe requests are treated as unsigned and pass through the missing-signature bypass described separately. The use of one shared secret and one verification algorithm for multiple providers also prevents proper key separation. ### Attack Path 1. An attacker submits a forged payload that claims to be a Stripe event. 2. The attacker omits the headers recognized by the server; `Stripe-Signature` is not required or processed. 3. No signature validation occurs. 4. The payload's `type` property is used as the event type. 5. The forged event proceeds to the configured Stripe or de ...[truncated 622 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/webhook_server.py:40
Finding

Unbounded Request Body Allows Webhook Service Denial of Service

Content
View full analysis
Remediation
View remediation
MAX_BODY_SIZE: self.send_response(413) self.end_headers() return ``` - Configure connection and read timeouts. - Reject malformed, negative, missing, or conflicting length declarations. - Deploy behind a production reverse proxy or WSGI/ASGI server that enforces request limits and concurrency controls. - Apply per-source rate limiting and connection limits. ]]>

T01 · Skill Instruction Hijacking

Error
Location
skill.md:330
Finding

Untrusted Webhook Fields Are Embedded Directly into Agent Instructions

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/webhook_server.py:27
Finding

Arbitrary Webhook Payload Contents Are Written to Logs

Content
View full analysis
dict: """Default handler for unknown events.""" logger.info(f"Default handler received: {payload}") return {"status": "processed"} ``` From `scripts/handlers.py`: ```python def handle_default(payload: dict) -> dict: """Catch-all for unhandled events.""" logger.info(f"Default handler: {json.dumps(payload)[:200]}") return {"status": "processed"} ``` ### Technical Analysis Both default handlers write attacker-controlled webhook content to application logs. One logs the entire payload, while the other logs the first 200 serialized characters. Webhook payloads may contain customer identifiers, email addresses, message contents, tokens, or other confidential values. Attacker-controlled strings may also contain newline or control characters. Depending on the log collector and output format, these values can forge additional log entries, interfere with monitoring, or conceal malicious activity. Truncating the serialized payload does not reliably remove secrets or neutralize control characters. ### Attack Path 1. An attacker sends an event that is handled by the default route. 2. The payload includes confidential-looking values, newlines, terminal control characters, or forged log prefixes. 3. The handler interpolates the payload into an informational log message. 4. The data persists in local or centralized logs and may be exposed to log readers. 5. Crafted formatting may mislead operators or interfere with downstream log parsing. ### Impact Assessment The vulnerability can disclose webhook content to users or systems with log access and can undermine the integrity of monitoring records. It does not directly provide higher sy ...[truncated 206 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The code is related to webhook processing and does match part of the declared purpose: it contains multi-provider event handlers and event-type-specific routing targets for GitHub, Slack, Stripe, plus a default handler. However, major advertised capabilities are absent from this chunk, especially HMAC/signature validation and retry/backoff behavior, which are central to the description. The code does not establish endpoints or perform inbound request verification; it only handles already-parsed payloads. Because the declared description emphasizes security verification and delivery retry workflows as key use cases, while the actual code is limited to simple payload handlers, this is a meaningful description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The code substantially matches the core idea of receiving webhooks, parsing payloads, basic signature verification, and routing by event type. However, the declared description explicitly emphasizes retry logic with exponential backoff and broad multi-provider support including Stripe, which are not present in the code. The server only handles incoming POST requests synchronously and returns responses; it does not schedule, retry, or redeliver failed events. Its signature handling is also simplistic and not consistent with provider-specific schemes such as Stripe or Slack timestamped signing. Because these are material declared capabilities rather than minor implementation details, this is a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/webhook_server.py (reported line 15)May include surrounding context.

python
logger = logging.getLogger(__name__)

# Configure your secrets here (or via env vars)
_config_path = Path(__file__).parent.parent / "config" / "webhook_secret.txt"
WEBHOOK_SECRET = _config_path.read_text().strip() if _config_path.exists() else os.environ.get("WEBHOOK_SECRET", "")

# Route table: event_type -> handler_function_name

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · skill.md (reported line 39)May include surrounding context.

md
logger = logging.getLogger(__name__)

# Configure your secrets here (or via env vars)
_config_path = Path(__file__).parent.parent / "config" / "webhook_secret.txt"
WEBHOOK_SECRET = _config_path.read_text().strip() if _config_path.exists() else os.environ.get("WEBHOOK_SECRET", "")

# Route table: event_type -> handler_function_name

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill description promises signature validation and spoofed-request filtering, but the implementation allows effectively unverified requests whenever the secret is unset. Because this is an event-driven automation endpoint, an attacker could send forged webhook events to trigger downstream actions or workflows.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The function is documented as performing HMAC verification, but when no secret is configured it returns True and silently disables authentication. In a webhook receiver, this means operators may believe requests are being verified while the endpoint accepts spoofed requests from anyone.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The code accepts webhook requests without verification when no secret is configured, and there is no prominent warning to the user that authentication is disabled. In this skill context, that is especially dangerous because webhooks are intended to originate from external providers and commonly trigger automated actions.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The example webhook server explicitly returns success from signature verification when no secret is configured, which turns an authenticity control into fail-open behavior. In the context of a webhook receiver, this allows any unauthenticated party to POST spoofed events and trigger downstream automation, making the example materially unsafe if copied into production.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The main server code uses the wrong headers for some providers and does not implement Slack timestamp checking, despite later documentation describing the correct mechanisms. This mismatch can cause developers to believe requests are authenticated when Stripe and Slack events are either not verified at all or are verified incorrectly, enabling spoofing or replay of webhook events.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The default handler logs up to 200 characters of the raw webhook payload, which can expose sensitive data such as tokens, email addresses, message content, customer details, or other provider-specific secrets into application logs. In a webhook automation skill, payloads originate from external systems and often contain untrusted but sensitive event data, so broad payload logging increases the risk of data leakage and log-based disclosure.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
85% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · scripts/webhook_server.py (reported line 24)May include surrounding context.

python
def verify_signature(payload_bytes: bytes, signature: str, secret: str = WEBHOOK_SECRET) -> bool:
    """Verify HMAC-SHA256 signature from provider."""
    if not secret:
        return True  # Skip verification if no secret configured
    expected = hmac.new(secret.encode(), payload_bytes, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The default handler logs the full incoming payload, which can capture sensitive webhook contents such as user data, tokens, URLs, or operational metadata. If logs are broadly accessible or retained, this creates secondary exposure of data supplied by external systems.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest description says the skill triggers on generic words like "webhook," "endpoint," and "signature" without narrowing context or providing exclusion conditions. These terms are broad enough to appear in many unrelated conversations, which could cause unintended invocation of the skill.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The inline comment says 'Configure your secrets here (or via env vars)', which endorses embedding secrets in the source file. Later, the deployment checklist explicitly says to never hardcode the webhook secret, so the guidance and implementation comments conflict on a security-relevant behavior.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
98% confidence
Finding

The 'skip verification' behavior is effectively an autonomous trust decision that disables the primary security gate when configuration is incomplete. In a webhook automation skill, that is especially dangerous because unauthenticated external input can directly drive automated processing and follow-on actions.

Content

Scanner excerpt · skill.md (reported line 47)May include surrounding context.

md
def verify_signature(payload_bytes: bytes, signature: str, secret: str = WEBHOOK_SECRET) -> bool:
    """Verify HMAC-SHA256 signature from provider."""
    if not secret:
        return True  # Skip verification if no secret configured
    expected = hmac.new(secret.encode(), payload_bytes, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The retry example persists full failed webhook event contents to disk without warning or minimization. Webhook payloads frequently contain personal data, billing information, repository metadata, or tokens, so indiscriminate persistence can create a secondary data exposure risk through local file access, backups, or logs.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · skill.md (reported line 369)May include surrounding context.

bash
# Send a test payload
curl -X POST http://localhost:8443/webhook \
  -H "Content-Type: application/json" \
  -H "X-GitHub-Event: push" \
  -d '{"repository": {"full_name": "test/repo"}, "ref": "refs/heads/main", "commits": [{"message": "test"}]}'

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · skill.md (reported line 376)May include surrounding context.

md
# Test with signature (requires secret configured)
SIGNATURE=$(echo -n '{"test": true}' | openssl dgst -sha256 -hmac "your-secret" | sed 's/^.* //')
curl -X POST http://localhost:8443/webhook \
  -H "Content-Type: application/json" \
  -H "X-Hub-Signature-256: sha256=$SIGNATURE" \
  -d '{"test": true}'