CVE-2026-93710 Overview
CVE-2026-93710 is a business logic flaw in the Dancer2 Perl web framework affecting versions 2.0.0 through 2.1.x. When a before hook dies and the exception handler halts the response, the dispatcher still runs the refused route. The caller receives the refusal, but the route body executes and its writes persist. This breaks a common security pattern where developers rely on before hooks for access control checks.
Critical Impact
Access control checks implemented in Dancer2 before hooks can be bypassed, allowing route handlers to execute and perform side effects even when a hook explicitly refused the request.
Affected Products
- Dancer2 for Perl versions 2.0.0 through 2.1.x
- Applications using before hooks for authorization or input validation
- Fixed in Dancer2 version 2.2.0
Discovery Timeline
- 2026-09-22 - CVE-2026-93710 published to NVD
- 2026-09-22 - Public disclosure via Openwall OSS-Security list
- 2026-09-22 - Last updated in NVD database
Technical Details for CVE-2026-93710
Vulnerability Analysis
The vulnerability resides in the hook compilation logic within lib/Dancer2/Core/App.pm. When a hook dies during request processing, Dancer2 fires the core.app.hook_exception event and then invokes cleanup logic. Cleanup discards the request, response, and session objects that the dispatcher has not yet consumed.
The exception handler is expected to halt the response by calling the halt method or setting is_halted. The halt keyword unwinds through with_return before cleanup runs. However, when the exception handler itself halts, the cleanup path is not stopped in the affected flow. The dispatcher proceeds to execute the route body, and its output is written back to the client alongside the earlier refusal.
This behavior maps to [CWE-460] (Improper Cleanup on Thrown Exception). Applications that rely on before hooks for authentication or authorization decisions are the primary exposure surface.
Root Cause
The root cause is a state-management defect in compile_hooks. The wrapper that fires core.app.hook_exception does not correctly gate the subsequent cleanup when the exception handler itself halts the response. Additionally, to_app() invokes finish() each time it is called, and compile_hooks was re-wrapping already-wrapped hooks, causing a single dying hook to fire the exception event multiple times across successive calls.
Attack Vector
An unauthenticated remote attacker sends a request to a route protected by a before hook that dies to refuse access. The client receives a refusal response, but the route body still executes on the server. Any side effects such as database writes, file operations, or downstream API calls proceed. This is exploitable over the network without authentication or user interaction.
# Patch excerpt from lib/Dancer2/Core/App.pm
# Tracks wrappers produced by compile_hooks, keyed by address,
# so a later run recognises its own work and passes it through
# instead of wrapping it a second time.
+has _compiled_hooks => (
+ is => 'ro',
+ isa => HashRef,
+ default => sub { +{} },
+ init_arg => undef,
+);
Source: GitHub Patch Commit 8fd31a3
Detect Identity Attacks the Moment Authentication Fails
Singularity™ Identity surfaces credential misuse, lateral movement, and privilege escalation in real time, so you can contain the breach before it spreads.
Stop Identity AttacksDetection Methods for CVE-2026-93710
Indicators of Compromise
- Application logs showing route handlers executing after a before hook fired core.app.hook_exception
- Duplicate core.app.hook_exception events for a single request, indicating re-wrapped hooks from repeated to_app() calls
- Successful writes or state changes correlated with HTTP responses returning refusal status codes
Detection Strategies
- Audit the Dancer2 version in use with cpanm --info Dancer2 or by inspecting cpanfile.snapshot for versions between 2.0.0 and 2.1.x
- Instrument before hooks to log both refusal and subsequent handler entry, then alert when both occur for the same request identifier
- Review web server access logs for anomalous ordering: refused requests followed by evidence of downstream side effects
Monitoring Recommendations
- Ingest Perl application logs and web server logs into a centralized log platform and correlate hook-refusal events with backend writes
- Track core.app.hook_exception event frequency; a sudden spike may indicate active probing or the pre-patch double-firing behavior
- Monitor database and file-system change logs for writes originating from routes gated by before hooks
How to Mitigate CVE-2026-93710
Immediate Actions Required
- Upgrade Dancer2 to version 2.2.0 or later across all Perl application servers
- Inventory all before hooks used for authorization, authentication, or input validation and confirm each halts the response correctly
- Re-run application test suites with the upgraded framework to verify that refused requests no longer invoke route bodies
Patch Information
The fix is committed in Dancer2 upstream as commit 8fd31a32798f9aa25e4fccbd6c7cb9dd3a0c1029 and released in Dancer2 2.2.0. Details are published in the GitHub Security Advisory GHSA-v527-r4px-7vx7 and the MetaCPAN Release Changes for Dancer2-2.2.0. Additional context is in the Openwall OSS-Security Post.
Workarounds
- Move authorization checks out of before hooks and into route handler prologues, returning explicitly before performing any side effects
- In exception handlers, ensure halt is called and verify is_halted on the response object before returning
- Wrap sensitive route bodies with an explicit is_halted check to short-circuit execution if a prior hook refused the request
# Upgrade Dancer2 to the patched release
cpanm Dancer2@2.2.0
# Verify installed version
perl -MDancer2 -e 'print $Dancer2::VERSION, "\n"'
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

