T09 · Insecure Skill Coding Practices
- Location
scripts/webhook_server.py:19- Finding
Webhook authentication fails open when the application secret is absent
- Content
View full analysis
Vulnerability Details
File Location:
scripts/webhook_server.py:19-22, with network exposure atscripts/webhook_server.py:104
Vulnerability Type: Fail-open webhook authentication
Risk Level: HighVulnerable Code
python def verify_signature(timestamp, nonce, signature): if not APP_SECRET: return True string = f"{timestamp}{nonce}{APP_SECRET}" expected = hashlib.sha1(string.encode()).hexdigest() return hmac.compare_digest(expected, signature)The service is also bound to every available network interface:
python server = HTTPServer(('', PORT), Handler)Technical Analysis
The signature-verification function explicitly accepts every request when
LARK_APP_SECRETis unset or empty. This is a fail-open authentication design: a missing security control silently disables authentication rather than preventing the service from starting.The server binds to all interfaces rather than loopback, so the unauthenticated endpoint may be reachable by other local-network hosts, containers, or the public Internet if the port is forwarded or exposed through the tunnel recommended by the Skill documentation.
An attacker does not need to know the Lark application secret. They can omit all signature headers and submit a syntactically valid callback body. The request will pass
verify_signatureand be processed as if it were authentic.Attack Path
- An operator starts
scripts/webhook_server.pywithout settingLARK_APP_SECRET. - The server listens on all interfaces on port
19800by default. - An attacker identifies the exposed webhook endpoint.
- The attacker sends a forged POST request without valid Lark signature headers, for example an
event_callbackcontaining an attacker-controlled text message. verify_signaturereturnsTruesolely becauseAPP_SECRETis empty.- The handler accepts the forged event and writes attacker-con ...[truncated 928 chars]
- An operator starts
- Remediation
View remediation
Remediation Suggestions
- Refuse to start the webhook service unless
LARK_APP_SECRETis present and non-empty. - Change
verify_signatureso missing configuration or missing signature headers always returnsFalse. - Bind to
127.0.0.1by default and require an explicit configuration option to listen on public interfaces. - Validate the request timestamp against a short acceptance window and track nonces or event identifiers to prevent replay attacks.
- Continue using constant-time comparison for signatures.
- Set a strict maximum request-body size before reading
Content-Length. - Place the service behind a TLS-terminating reverse proxy with request limits when public exposure is required.
- Add automated tests confirming that missing secrets, missing headers, invalid signatures, stale timestamps, and replayed events are rejected.
A fail-closed pattern should be used:
python def verify_signature(timestamp, nonce, signature): if not APP_SECRET or not timestamp or not nonce or not signature: return False string = f"{timestamp}{nonce}{APP_SECRET}" expected = hashlib.sha1(string.encode()).hexdigest() return hmac.compare_digest(expected, signature) def main(): if not APP_SECRET: raise RuntimeError("LARK_APP_SECRET must be configured") server = HTTPServer(("127.0.0.1", PORT), Handler) server.serve_forever()- Refuse to start the webhook service unless
