{"containers":{"cna":{"affected":[{"collectionURL":"https://repo.hex.pm","cpes":["cpe:2.3:a:elixir-plug:plug:*:*:*:*:*:*:*:*"],"defaultStatus":"unaffected","modules":["'Elixir.Plug.Crypto'"],"packageName":"plug","packageURL":"pkg:hex/plug","product":"plug","programFiles":["lib/plug/crypto.ex"],"programRoutines":[{"name":"'Elixir.Plug.Crypto':secure_compare/2"},{"name":"'Elixir.Plug.Crypto':masked_compare/3"}],"repo":"https://github.com/elixir-plug/plug","vendor":"elixir-plug","versions":[{"lessThan":"1.7.0","status":"affected","version":"1.6.3","versionType":"semver"}]},{"collectionURL":"https://github.com","cpes":["cpe:2.3:a:elixir-plug:plug:*:*:*:*:*:*:*:*"],"defaultStatus":"unaffected","modules":["'Elixir.Plug.Crypto'"],"packageName":"elixir-plug/plug","packageURL":"pkg:github/elixir-plug/plug","product":"plug","programFiles":["lib/plug/crypto.ex"],"programRoutines":[{"name":"'Elixir.Plug.Crypto':secure_compare/2"},{"name":"'Elixir.Plug.Crypto':masked_compare/3"}],"repo":"https://github.com/elixir-plug/plug","vendor":"elixir-plug","versions":[{"lessThan":"*","status":"affected","version":"56000c33d8a0a2fbac491f8be06b9c5112bc2276","versionType":"git"}]},{"collectionURL":"https://repo.hex.pm","cpes":["cpe:2.3:a:elixir-plug:plug_crypto:*:*:*:*:*:*:*:*"],"defaultStatus":"unaffected","modules":["'Elixir.Plug.Crypto'","'Elixir.Plug.Crypto.MessageVerifier'","'Elixir.Plug.Crypto.MessageEncryptor'"],"packageName":"plug_crypto","packageURL":"pkg:hex/plug_crypto","product":"plug_crypto","programFiles":["lib/plug/crypto.ex","lib/plug/crypto/message_verifier.ex","lib/plug/crypto/message_encryptor.ex"],"programRoutines":[{"name":"'Elixir.Plug.Crypto':verify/4"},{"name":"'Elixir.Plug.Crypto':decrypt/4"},{"name":"'Elixir.Plug.Crypto':secure_compare/2"},{"name":"'Elixir.Plug.Crypto':masked_compare/3"},{"name":"'Elixir.Plug.Crypto':mask/2"},{"name":"'Elixir.Plug.Crypto':sign/4"},{"name":"'Elixir.Plug.Crypto':encrypt/4"},{"name":"'Elixir.Plug.Crypto.MessageVerifier':sign/3"},{"name":"'Elixir.Plug.Crypto.MessageVerifier':verify/2"},{"name":"'Elixir.Plug.Crypto.MessageEncryptor':encrypt/4"},{"name":"'Elixir.Plug.Crypto.MessageEncryptor':decrypt/4"}],"repo":"https://github.com/elixir-plug/plug_crypto","vendor":"elixir-plug","versions":[{"lessThan":"2.2.1","status":"affected","version":"1.0.0","versionType":"semver"}]},{"collectionURL":"https://github.com","cpes":["cpe:2.3:a:elixir-plug:plug_crypto:*:*:*:*:*:*:*:*"],"defaultStatus":"unaffected","modules":["'Elixir.Plug.Crypto'","'Elixir.Plug.Crypto.MessageVerifier'","'Elixir.Plug.Crypto.MessageEncryptor'"],"packageName":"elixir-plug/plug_crypto","packageURL":"pkg:github/elixir-plug/plug_crypto","product":"plug_crypto","programFiles":["lib/plug/crypto.ex","lib/plug/crypto/message_verifier.ex","lib/plug/crypto/message_encryptor.ex"],"programRoutines":[{"name":"'Elixir.Plug.Crypto':verify/4"},{"name":"'Elixir.Plug.Crypto':decrypt/4"},{"name":"'Elixir.Plug.Crypto':secure_compare/2"},{"name":"'Elixir.Plug.Crypto':masked_compare/3"},{"name":"'Elixir.Plug.Crypto':mask/2"},{"name":"'Elixir.Plug.Crypto':sign/4"},{"name":"'Elixir.Plug.Crypto':encrypt/4"},{"name":"'Elixir.Plug.Crypto.MessageVerifier':sign/3"},{"name":"'Elixir.Plug.Crypto.MessageVerifier':verify/2"},{"name":"'Elixir.Plug.Crypto.MessageEncryptor':encrypt/4"},{"name":"'Elixir.Plug.Crypto.MessageEncryptor':decrypt/4"}],"repo":"https://github.com/elixir-plug/plug_crypto","vendor":"elixir-plug","versions":[{"lessThan":"981887493dada0fce7003c61861c00dfbb0fc8c5","status":"affected","version":"2d078b7b8264c565f0ff71235c515f4f46882dcf","versionType":"git"}]}],"configurations":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>Request-data reachability needs an application that passes unvalidated request data into <code>Plug.Crypto</code>. Either a controller or plug that hands <code>params[\"token\"]</code> to <code>Phoenix.Token.verify/4</code> or <code>Phoenix.Token.decrypt/4</code>, where <code>Plug.Parsers</code> yields a list for <code>token[]=x</code> in a query string and an integer or a map for a JSON body, or a call to <code>Plug.Crypto.secure_compare/2</code> on a value that can be nil, such as <code>List.first(get_req_header(conn, \"x-api-key\"))</code> compared against a configured secret. Disclosure of the leaked value additionally needs read access to the application logs or to an error tracker that receives <code>crash_reason</code> metadata. The remaining affected functions require direct misuse by application code and have no request-data trigger.</p>"},{"base64":false,"type":"text/markdown","value":"Request-data reachability needs an application that passes unvalidated request data into `Plug.Crypto`. Either a controller or plug that hands `params[\"token\"]` to `Phoenix.Token.verify/4` or `Phoenix.Token.decrypt/4`, where `Plug.Parsers` yields a list for `token[]=x` in a query string and an integer or a map for a JSON body, or a call to `Plug.Crypto.secure_compare/2` on a value that can be nil, such as `List.first(get_req_header(conn, \"x-api-key\"))` compared against a configured secret. Disclosure of the leaked value additionally needs read access to the application logs or to an error tracker that receives `crash_reason` metadata. The remaining affected functions require direct misuse by application code and have no request-data trigger."}],"value":"Request-data reachability needs an application that passes unvalidated request data into Plug.Crypto. Either a controller or plug that hands params[\"token\"] to Phoenix.Token.verify/4 or Phoenix.Token.decrypt/4, where Plug.Parsers yields a list for token[]=x in a query string and an integer or a map for a JSON body, or a call to Plug.Crypto.secure_compare/2 on a value that can be nil, such as List.first(get_req_header(conn, \"x-api-key\")) compared against a configured secret. Disclosure of the leaked value additionally needs read access to the application logs or to an error tracker that receives crash_reason metadata. The remaining affected functions require direct misuse by application code and have no request-data trigger."}],"cpeApplicability":[{"nodes":[{"cpeMatch":[{"criteria":"cpe:2.3:a:elixir-plug:plug:*:*:*:*:*:*:*:*","versionEndExcluding":"1.7.0","versionStartIncluding":"1.6.3","vulnerable":true},{"criteria":"cpe:2.3:a:elixir-plug:plug_crypto:*:*:*:*:*:*:*:*","versionEndExcluding":"2.2.1","versionStartIncluding":"1.0.0","vulnerable":true}],"negate":false,"operator":"OR"}],"operator":"AND"}],"credits":[{"lang":"en","type":"reporter","value":"Neil Berkman"},{"lang":"en","type":"finder","value":"Neil Berkman"},{"lang":"en","type":"remediation developer","value":"Neil Berkman"},{"lang":"en","type":"coordinator","value":"Jonatan Männchen / EEF"},{"lang":"en","type":"remediation verifier","value":"José Valim"}],"dateAssigned":"2026-10-22T09:25:19.000Z","descriptions":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>Externally-Generated Error Message Containing Sensitive Information vulnerability in <code>plug_crypto</code> in elixir-plug plug_crypto allows an unauthenticated remote attacker to expose the secret key base through application logs via a wrong-typed argument. Every public function in <code>Plug.Crypto</code> validates argument types in a function-head guard. A failed guard makes the Erlang VM raise <code>FunctionClauseError</code> with the actual argument values in the top stack frame, so the secret key base, a derived key, or the value an API key is compared against lands in that frame. The library's <code>rescue</code> blocks and <code>Plug.Crypto.prune_args_from_stacktrace/1</code> only reach exceptions raised inside a function body, so they never see a guard failure. Plug.Cowboy and Bandit write the frame to the error log and attach it as <code>crash_reason</code> Logger metadata, which error trackers forward.</p>\n<p>Two entry points take request data. <code>Plug.Crypto.verify/4</code> exposes the secret key base when the token is neither a binary nor nil, which <code>Phoenix.Token.verify/4</code> reaches because it forwards the token unguarded. <code>Plug.Crypto.secure_compare/2</code> exposes the other operand when one side is not a binary, as happens when a missing request header yields nil. Reading the leaked value needs separate access to the logs or the error tracker, so the request alone does not disclose the secret to the attacker.</p>\n<p>This issue affects plug: from 1.6.3 before 1.7.0; plug_crypto: from 1.0.0 before 2.2.1.</p>"},{"base64":false,"type":"text/markdown","value":"Externally-Generated Error Message Containing Sensitive Information vulnerability in `plug_crypto` in elixir-plug plug_crypto allows an unauthenticated remote attacker to expose the secret key base through application logs via a wrong-typed argument. Every public function in `Plug.Crypto` validates argument types in a function-head guard. A failed guard makes the Erlang VM raise `FunctionClauseError` with the actual argument values in the top stack frame, so the secret key base, a derived key, or the value an API key is compared against lands in that frame. The library's `rescue` blocks and `Plug.Crypto.prune_args_from_stacktrace/1` only reach exceptions raised inside a function body, so they never see a guard failure. Plug.Cowboy and Bandit write the frame to the error log and attach it as `crash_reason` Logger metadata, which error trackers forward.\n\nTwo entry points take request data. `Plug.Crypto.verify/4` exposes the secret key base when the token is neither a binary nor nil, which `Phoenix.Token.verify/4` reaches because it forwards the token unguarded. `Plug.Crypto.secure_compare/2` exposes the other operand when one side is not a binary, as happens when a missing request header yields nil. Reading the leaked value needs separate access to the logs or the error tracker, so the request alone does not disclose the secret to the attacker.\n\nThis issue affects plug: from 1.6.3 before 1.7.0; plug_crypto: from 1.0.0 before 2.2.1."}],"value":"Externally-Generated Error Message Containing Sensitive Information vulnerability in plug_crypto in elixir-plug plug_crypto allows an unauthenticated remote attacker to expose the secret key base through application logs via a wrong-typed argument. Every public function in Plug.Crypto validates argument types in a function-head guard. A failed guard makes the Erlang VM raise FunctionClauseError with the actual argument values in the top stack frame, so the secret key base, a derived key, or the value an API key is compared against lands in that frame. The library's rescue blocks and Plug.Crypto.prune_args_from_stacktrace/1 only reach exceptions raised inside a function body, so they never see a guard failure. Plug.Cowboy and Bandit write the frame to the error log and attach it as crash_reason Logger metadata, which error trackers forward.\n\nTwo entry points take request data. Plug.Crypto.verify/4 exposes the secret key base when the token is neither a binary nor nil, which Phoenix.Token.verify/4 reaches because it forwards the token unguarded. Plug.Crypto.secure_compare/2 exposes the other operand when one side is not a binary, as happens when a missing request header yields nil. Reading the leaked value needs separate access to the logs or the error tracker, so the request alone does not disclose the secret to the attacker.\n\nThis issue affects plug: from 1.6.3 before 1.7.0; plug_crypto: from 1.0.0 before 2.2.1."}],"impacts":[{"capecId":"CAPEC-54","descriptions":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>An unauthenticated attacker sends one request with a wrong-typed parameter, or omits a header, and the secret key base or the compared secret is written into the application log and into <code>crash_reason</code> metadata forwarded to error trackers. Anyone who can read those logs then holds a key sufficient to mint session cookies and tokens under any salt.</p>"},{"base64":false,"type":"text/markdown","value":"An unauthenticated attacker sends one request with a wrong-typed parameter, or omits a header, and the secret key base or the compared secret is written into the application log and into `crash_reason` metadata forwarded to error trackers. Anyone who can read those logs then holds a key sufficient to mint session cookies and tokens under any salt."}],"value":"An unauthenticated attacker sends one request with a wrong-typed parameter, or omits a header, and the secret key base or the compared secret is written into the application log and into crash_reason metadata forwarded to error trackers. Anyone who can read those logs then holds a key sufficient to mint session cookies and tokens under any salt."}]}],"metrics":[{"cvssV4_0":{"Automatable":"NOT_DEFINED","Recovery":"NOT_DEFINED","Safety":"NOT_DEFINED","attackComplexity":"LOW","attackRequirements":"NONE","attackVector":"NETWORK","baseScore":5.1,"baseSeverity":"MEDIUM","privilegesRequired":"HIGH","providerUrgency":"NOT_DEFINED","subAvailabilityImpact":"NONE","subConfidentialityImpact":"NONE","subIntegrityImpact":"NONE","userInteraction":"NONE","valueDensity":"NOT_DEFINED","vectorString":"CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","version":"4.0","vulnAvailabilityImpact":"NONE","vulnConfidentialityImpact":"LOW","vulnIntegrityImpact":"NONE","vulnerabilityResponseEffort":"NOT_DEFINED"},"format":"CVSS","scenarios":[{"lang":"en","value":"GENERAL"}]}],"problemTypes":[{"descriptions":[{"cweId":"CWE-532","description":"CWE-532 Insertion of Sensitive Information into Log File","lang":"en","type":"CWE"}]}],"providerMetadata":{"dateUpdated":"2026-10-23T09:56:58.885Z","orgId":"6b3ad84c-e1a6-4bf7-a703-f496b71e49db","shortName":"EEF"},"references":[{"name":"Secret key base exposed in stack trace arguments on wrong-typed Plug.Crypto arguments","tags":["vendor-advisory","related"],"url":"https://github.com/elixir-plug/plug/security/advisories/GHSA-ffw9-w85g-f2vh"},{"name":"EEF CNA record for CVE-2026-105963","tags":["related"],"url":"https://cna.erlef.org/cves/CVE-2026-105963.html"},{"name":"OSV record EEF-CVE-2026-105963","tags":["related"],"url":"https://osv.dev/vulnerability/EEF-CVE-2026-105963"},{"name":"Introducing commit 56000c3 in elixir-plug/plug","tags":["related"],"url":"https://github.com/elixir-plug/plug/commit/56000c33d8a0a2fbac491f8be06b9c5112bc2276"},{"name":"Fix commit aeaa90e in elixir-plug/plug","tags":["patch"],"url":"https://github.com/elixir-plug/plug/commit/aeaa90e76b73ea90c3ecca9aae16fce71bd2a8d4"},{"name":"Introducing commit 2d078b7 in elixir-plug/plug_crypto","tags":["related"],"url":"https://github.com/elixir-plug/plug_crypto/commit/2d078b7b8264c565f0ff71235c515f4f46882dcf"},{"name":"Fix commit 9818874 in elixir-plug/plug_crypto","tags":["patch"],"url":"https://github.com/elixir-plug/plug_crypto/commit/981887493dada0fce7003c61861c00dfbb0fc8c5"}],"source":{"discovery":"EXTERNAL"},"title":"Secret key base exposed in stack trace arguments on wrong-typed Plug.Crypto arguments","workarounds":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>Check argument types in the calling code before entering <code>Plug.Crypto</code>, so no head guard ever fails. Pass a token to <code>Phoenix.Token.verify/4</code>, <code>Phoenix.Token.decrypt/4</code>, <code>Plug.Crypto.verify/4</code> or <code>Plug.Crypto.decrypt/4</code> only when <code>is_binary(token)</code> holds, and treat anything else as an invalid token. Call <code>Plug.Crypto.secure_compare/2</code> only when both operands are binaries, treating a missing header or parameter as a failed comparison rather than passing nil.</p>\n<p>Where the leaked value matters more than the crash, configuring the error tracker to drop stack-frame arguments also keeps the secret out of the forwarded report. For Sentry this means not sending <code>vars</code> with the crash report. This narrows exposure but leaves the value in the local error log.</p>"},{"base64":false,"type":"text/markdown","value":"Check argument types in the calling code before entering `Plug.Crypto`, so no head guard ever fails. Pass a token to `Phoenix.Token.verify/4`, `Phoenix.Token.decrypt/4`, `Plug.Crypto.verify/4` or `Plug.Crypto.decrypt/4` only when `is_binary(token)` holds, and treat anything else as an invalid token. Call `Plug.Crypto.secure_compare/2` only when both operands are binaries, treating a missing header or parameter as a failed comparison rather than passing nil.\n\nWhere the leaked value matters more than the crash, configuring the error tracker to drop stack-frame arguments also keeps the secret out of the forwarded report. For Sentry this means not sending `vars` with the crash report. This narrows exposure but leaves the value in the local error log."}],"value":"Check argument types in the calling code before entering Plug.Crypto, so no head guard ever fails. Pass a token to Phoenix.Token.verify/4, Phoenix.Token.decrypt/4, Plug.Crypto.verify/4 or Plug.Crypto.decrypt/4 only when is_binary(token) holds, and treat anything else as an invalid token. Call Plug.Crypto.secure_compare/2 only when both operands are binaries, treating a missing header or parameter as a failed comparison rather than passing nil.\n\nWhere the leaked value matters more than the crash, configuring the error tracker to drop stack-frame arguments also keeps the secret out of the forwarded report. For Sentry this means not sending vars with the crash report. This narrows exposure but leaves the value in the local error log."}],"x_proofOfConcept":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<ol>\n<li>Run an application that depends on <code>plug_crypto</code> and exposes a route calling <code>Phoenix.Token.verify(secret_key_base, \"user auth\", conn.params[\"token\"])</code>, plus a route calling <code>Plug.Crypto.secure_compare(List.first(get_req_header(conn, \"x-api-key\")), api_key)</code>.</li>\n<li>Send <code>GET /verify?token[]=x</code>. <code>Plug.Parsers</code> yields a list for the bracket syntax, so the token argument is a list.</li>\n<li>Observe the 500 response and read the error log. The top frame prints <code>Plug.Crypto.verify(\"SECRET_KEY_BASE_...\", \"user auth\", [\"x\"], [])</code> with the secret key base in full, and the same frame is attached as <code>crash_reason</code> Logger metadata.</li>\n<li>Send <code>GET /api</code> with no <code>x-api-key</code> header. The header lookup yields nil, and the frame prints <code>Plug.Crypto.secure_compare(nil, \"&lt;configured API key&gt;\")</code>.</li>\n<li>A JSON body of <code>{\"token\": 123}</code> to the first route gives the same result with an integer argument.</li>\n</ol>"},{"base64":false,"type":"text/markdown","value":"1. Run an application that depends on `plug_crypto` and exposes a route calling `Phoenix.Token.verify(secret_key_base, \"user auth\", conn.params[\"token\"])`, plus a route calling `Plug.Crypto.secure_compare(List.first(get_req_header(conn, \"x-api-key\")), api_key)`.\n2. Send `GET /verify?token[]=x`. `Plug.Parsers` yields a list for the bracket syntax, so the token argument is a list.\n3. Observe the 500 response and read the error log. The top frame prints `Plug.Crypto.verify(\"SECRET_KEY_BASE_...\", \"user auth\", [\"x\"], [])` with the secret key base in full, and the same frame is attached as `crash_reason` Logger metadata.\n4. Send `GET /api` with no `x-api-key` header. The header lookup yields nil, and the frame prints `Plug.Crypto.secure_compare(nil, \"<configured API key>\")`.\n5. A JSON body of `{\"token\": 123}` to the first route gives the same result with an integer argument."}],"value":"* Run an application that depends on plug_crypto and exposes a route calling Phoenix.Token.verify(secret_key_base, \"user auth\", conn.params[\"token\"]), plus a route calling Plug.Crypto.secure_compare(List.first(get_req_header(conn, \"x-api-key\")), api_key).\n* Send GET /verify?token[]=x. Plug.Parsers yields a list for the bracket syntax, so the token argument is a list.\n* Observe the 500 response and read the error log. The top frame prints Plug.Crypto.verify(\"SECRET_KEY_BASE_...\", \"user auth\", [\"x\"], []) with the secret key base in full, and the same frame is attached as crash_reason Logger metadata.\n* Send GET /api with no x-api-key header. The header lookup yields nil, and the frame prints Plug.Crypto.secure_compare(nil, \"<configured API key>\").\n* A JSON body of {\"token\": 123} to the first route gives the same result with an integer argument."}],"x_technicalAnalysis":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p><strong>1. Type checks in function-head guards.</strong> Every public function in <code>Plug.Crypto</code> validates its arguments in a head guard, for example <code>secure_compare/2</code> with <code>when is_binary(left) and is_binary(right)</code> and <code>verify/4</code> with <code>when is_binary(key_base) and is_binary(salt) and is_binary(token)</code>.</p>\n<p><strong>2. The VM builds the error, not the library.</strong> When no clause matches, the Erlang VM raises <code>FunctionClauseError</code> and the top stack frame holds the actual argument values. The library's <code>rescue ... reraise</code> blocks and <code>Plug.Crypto.prune_args_from_stacktrace/1</code> only apply to exceptions raised inside a function body, so a guard failure never reaches them. In <code>plug_crypto</code> the same commit that added <code>prune_args_from_stacktrace/1</code> also added these guards.</p>\n<p><strong>3. Which value leaks.</strong> <code>verify/4</code> and <code>sign/4</code> put the secret key base in the frame. <code>decrypt/4</code> has no <code>is_binary(token)</code> guard on its main clause, so the key is derived first and the failing guard belongs to <code>MessageEncryptor.decrypt/4</code>, placing the derived 32-byte key in the frame. <code>secure_compare/2</code> places the other operand in the frame, which is the server-side secret when a request value is nil.</p>\n<p><strong>4. The frame reaches logs and error trackers.</strong> Plug.Cowboy and Bandit write the formatted frame, arguments included, to the error log and attach it as <code>crash_reason</code> Logger metadata. Error trackers forward it; Sentry's handler sends crash reports by default and serializes frame arguments as <code>vars</code>.</p>"},{"base64":false,"type":"text/markdown","value":"**1. Type checks in function-head guards.** Every public function in `Plug.Crypto` validates its arguments in a head guard, for example `secure_compare/2` with `when is_binary(left) and is_binary(right)` and `verify/4` with `when is_binary(key_base) and is_binary(salt) and is_binary(token)`.\n\n**2. The VM builds the error, not the library.** When no clause matches, the Erlang VM raises `FunctionClauseError` and the top stack frame holds the actual argument values. The library's `rescue ... reraise` blocks and `Plug.Crypto.prune_args_from_stacktrace/1` only apply to exceptions raised inside a function body, so a guard failure never reaches them. In `plug_crypto` the same commit that added `prune_args_from_stacktrace/1` also added these guards.\n\n**3. Which value leaks.** `verify/4` and `sign/4` put the secret key base in the frame. `decrypt/4` has no `is_binary(token)` guard on its main clause, so the key is derived first and the failing guard belongs to `MessageEncryptor.decrypt/4`, placing the derived 32-byte key in the frame. `secure_compare/2` places the other operand in the frame, which is the server-side secret when a request value is nil.\n\n**4. The frame reaches logs and error trackers.** Plug.Cowboy and Bandit write the formatted frame, arguments included, to the error log and attach it as `crash_reason` Logger metadata. Error trackers forward it; Sentry's handler sends crash reports by default and serializes frame arguments as `vars`."}],"value":"1. Type checks in function-head guards. Every public function in Plug.Crypto validates its arguments in a head guard, for example secure_compare/2 with when is_binary(left) and is_binary(right) and verify/4 with when is_binary(key_base) and is_binary(salt) and is_binary(token).\n\n2. The VM builds the error, not the library. When no clause matches, the Erlang VM raises FunctionClauseError and the top stack frame holds the actual argument values. The library's rescue ... reraise blocks and Plug.Crypto.prune_args_from_stacktrace/1 only apply to exceptions raised inside a function body, so a guard failure never reaches them. In plug_crypto the same commit that added prune_args_from_stacktrace/1 also added these guards.\n\n3. Which value leaks. verify/4 and sign/4 put the secret key base in the frame. decrypt/4 has no is_binary(token) guard on its main clause, so the key is derived first and the failing guard belongs to MessageEncryptor.decrypt/4, placing the derived 32-byte key in the frame. secure_compare/2 places the other operand in the frame, which is the server-side secret when a request value is nil.\n\n4. The frame reaches logs and error trackers. Plug.Cowboy and Bandit write the formatted frame, arguments included, to the error log and attach it as crash_reason Logger metadata. Error trackers forward it; Sentry's handler sends crash reports by default and serializes frame arguments as vars."}]}},"cveMetadata":{"assignerOrgId":"6b3ad84c-e1a6-4bf7-a703-f496b71e49db","assignerShortName":"EEF","cveId":"CVE-2026-105963","datePublished":"2026-10-23T09:56:58.885Z","dateReserved":"2026-10-06T10:30:01.805Z","dateUpdated":"2026-10-23T09:56:58.885Z","state":"PUBLISHED"},"dataType":"CVE_RECORD","dataVersion":"5.2"}