From b8e52930ab4a765ce96e8653b11aa8d5515c096e Mon Sep 17 00:00:00 2001 From: LeTraceurSnork Date: Wed, 11 Jun 2025 21:15:55 +0500 Subject: [PATCH 1/7] New PSR for standartizing CAPTCHA (CaptchaInterface) --- captcha.md | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 captcha.md diff --git a/captcha.md b/captcha.md new file mode 100644 index 000000000..5e8ece9e3 --- /dev/null +++ b/captcha.md @@ -0,0 +1,106 @@ +CaptchaInterface for various Captcha services +============================================= + +This document describes common interfaces to query data. These data can be from different sources, from in-memory data to databases, as well as filesystem files. + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be +interpreted as described in [RFC 2119][]. + +The final implementations MAY decorate the objects with more +functionality than the one proposed but they MUST implement the indicated +interfaces/functionality first. + +[RFC 2119]: http://tools.ietf.org/html/rfc2119 + +## 1. Specification + +### 1.1 Definitions + +* **CAPTCHA** - A Completely Automated Public Turing test to tell Computers and Humans Apart. A challenge-response test designed to distinguish human users from automated bots. +* **Provider** - A service that implements CAPTCHA functionality by: + * Generating challenges to distinguish humans from automated systems + * Validating user responses to these challenges + * Assessing interaction risk levels +* **Client Token** - A string value generated by the **Provider**'s client-side implementation, representing a single challenge attempt. + * MAY have **Provider**-specific format and expiration +* **Verification** - The process of validating a **Client Token** against the **Provider**'s service. MUST be performed server-side. MAY include client IP validation if required by **Provider**. MAY involve additional risk analysis (e.g., behavioral **SCORING**) +* **Successful Verification** - A determination that: + * The **Client Token** is valid and unexpired + * The challenge was completed satisfactorily + * (When applicable) **SCORING** meets implementation thresholds +* **Failed Verification** Occurs when: + * The token is invalid/expired + * The challenge response was incorrect + * The risk score indicates automated behavior + * Network/configuration errors prevent validation +* **SCORING** - An OPTIONAL provider-specific value indicating confidence in the human/bot determination: + * SHOULD be represented as float between 0.0 (likely bot) and 1.0 (likely human). + * MAY be represented as vice-versa (0.0 = no risk, 1.0 = maximum risk). + * MAY, but SHOULD NOT be represented in other than float formats, e.g. 0 - 100 (as percentages) + * SHOULD be accessible through CaptchaResponseInterface extensions + * Thresholds for success/failure are implementation-defined + * Threshold SHOULD be configurable via CaptchaInterface extensions + +### 1.2 Goal +This specification establishes a standardized interface for CAPTCHA implementations in PHP with the primary objective of **enabling immediate, low-effort substitution of CAPTCHA providers during critical vendor lock-in scenarios**. Specifically addressing real-world crises where: +- Providers discontinue services or change pricing models abruptly +- Security vulnerabilities require emergency provider migration +- Sudden service bans + +The interface shall achieve: +1. **Zero-Refactor Replacement**: Allow switching providers (e.g., Google reCAPTCHA -> hCaptcha -> Cloudflare Turnstile) through configuration/vendor changes only +2. **DI Container Readiness**: Enable dependency injection of any compliant implementation without call-site modifications +3. **Vendor Crisis Resilience**: Mitigate business continuity risks during: + - Access restrictions + - Provider API shutdowns + - Compliance requirement changes +4. **Cost Containment**: Eliminate: + - System-wide code refactoring during migrations + - Parallel implementation maintenance + - Provider-specific testing overhead + +## 2. Interfaces + +### 2.1 CaptchaInterface + +```php +namespace Psr\Captcha; + +/** + * Interface of Captcha service itself. + * MUST decide whether user passed the Captcha or not and return corresponding response. + * SHOULD contain method to configure SCORING threshold (if applicable by PROVIDER) + */ +interface CaptchaInterface +{ + /** + * Verifies client token and decides whether verification was successful or not (is user a bot or not). + * + * @return CaptchaResponseInterface + */ + public function verify(string $token): CaptchaResponseInterface; +} +``` + +### 2.2 CaptchaResponseInterface + +```php +namespace Psr\Captcha; + +/** + * Interface of the object that CaptchaInterface obliged to return on ::verify() method. + * MUST contain enough information to consistently say whether user succesfully passed Captcha or not. + * SHOULD contain actual user's SCORING + * MAY contain additional information (e.g., gathered from it's captcha-vendor service's verification endpoint) (i.e. message, errors, etc.) + */ +interface CaptchaResponseInterface +{ + /** + * Return true/false depends on whether verification was successful or not (is user a bot or not). + * + * @return bool + */ + public function isSuccess(): bool; +} +``` From e04dd052cc37fef4a1887f0eb230524b3240a427 Mon Sep 17 00:00:00 2001 From: LeTraceurSnork Date: Thu, 12 Jun 2025 01:59:50 +0500 Subject: [PATCH 2/7] feat: added CaptchaException --- captcha.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/captcha.md b/captcha.md index 5e8ece9e3..bfd130fc2 100644 --- a/captcha.md +++ b/captcha.md @@ -71,6 +71,7 @@ namespace Psr\Captcha; * Interface of Captcha service itself. * MUST decide whether user passed the Captcha or not and return corresponding response. * SHOULD contain method to configure SCORING threshold (if applicable by PROVIDER) + * SHOULD throw a CaptchaException as soon as possible if appears any non-user related error that prevents correct Captcha solving (e.g. network problems, incorrect secret token, e.g.) */ interface CaptchaInterface { @@ -78,6 +79,7 @@ interface CaptchaInterface * Verifies client token and decides whether verification was successful or not (is user a bot or not). * * @return CaptchaResponseInterface + * @throws CaptchaException if Captcha cannot be validated (e.g. due to network problems, incorrect secret token, etc.) */ public function verify(string $token): CaptchaResponseInterface; } @@ -104,3 +106,16 @@ interface CaptchaResponseInterface public function isSuccess(): bool; } ``` + +### 2.3 CaptchaException + +```php +namespace Psr\Captcha; + +/** + * MUST be thrown from CaptchaInterface methods if Captcha test itself cannot be passed due to any reason that is not user-related - network problems, incorrect secret token, unable to parse request-response, etc. + */ +interface CaptchaException extends \RuntimeException +{ +} +``` From 9d91b87a0239a05f3bd1812bb6f0d5f428dca632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=A1=D0=B0=D0=BB=D0=B8=D0=B3?= =?UTF-8?q?=D0=B6=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Tue, 17 Jun 2025 15:01:14 +0500 Subject: [PATCH 3/7] fix: directory path --- captcha.md => proposed/captcha.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename captcha.md => proposed/captcha.md (100%) diff --git a/captcha.md b/proposed/captcha.md similarity index 100% rename from captcha.md rename to proposed/captcha.md From 9bfbb82adfd81b9e8228a7a4b12f8bc86fdf755f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=A1=D0=B0=D0=BB=D0=B8=D0=B3?= =?UTF-8?q?=D0=B6=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Wed, 18 Jun 2025 19:27:27 +0500 Subject: [PATCH 4/7] feat: captcha-meta presented --- proposed/captcha-meta.md | 77 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 proposed/captcha-meta.md diff --git a/proposed/captcha-meta.md b/proposed/captcha-meta.md new file mode 100644 index 000000000..0e1424c70 --- /dev/null +++ b/proposed/captcha-meta.md @@ -0,0 +1,77 @@ +CAPTCHA Meta Document +===================== + +## 1. Summary + +The `CaptchaInterface` defines a standard, implementation-agnostic way to verify whether a user is human or automated, using vendor-provided challenge-response systems. It enables applications and frameworks to substitute CAPTCHA vendors transparently and with minimal effort, especially in crisis scenarios such as API bans, forced migrations, or vendor lock-in. + +The interfaces are intentionally minimal: implementors MAY extend for vendor-specific data, but consumers MUST depend only on functionality guaranteed by the PSR, allowing maximum interoperability and rapid reaction to a changing security landscape, while they MAY depend on functionality, provided by specific implementations, but only if they know for sure this functionality exists (via instanceof/typehinting). + +## 2. Why Bother? + +There are currently several major CAPTCHA providers (such as Google reCAPTCHA, hCaptcha, Yandex SmartCaptcha, Cloudflare Turnstile, and others), as well as growing open-source and self-hosted alternatives. Many PHP libraries and frameworks ship with their own interfaces or concrete adapters for these services, leading to fragmentation and poor interoperability between codebases. + +When a sudden vendor policy change, API discontinuation, cost escalation, or regulatory issue occurs, projects are forced to urgently change CAPTCHA vendors. In practice this results in application-wide refactoring, duplicated logic, vendor-specific workarounds, and the risk of business downtime or security lapses due to a breaking change in a critical security layer. + +By providing a PSR for CAPTCHA, we enable PHP applications and frameworks to depend on a single interface, making it trivial to swap underlying implementations with minimal code change — often only at the DI configuration or service wiring level. This dramatically reduces migration risk and business impact during vendor crisis situations, promotes interoperability, and lessens the chance of vendor lock-in. + +Pros: +* A universal, vendor-agnostic interface for CAPTCHA verification and response; +* Rapid provider swap in emergency situations (API shutdowns, region bans, cost changes); +* Less duplicated code and easier maintenance of libraries and frameworks; +* Consistent exception handling and response format for all vendors; +* Promotes extensible, flexible code design. + +Cons: +* Slight abstraction overhead compared to using a single provider’s SDK directly; +* Vendor-specific features that are not part of the interface may require interface extension or custom code; + +## 3. Design Decisions + +### 3.1 Response Interface vs Boolean + +During code-review a question have been asked: `why CaptchaInterface::verify() returns CaptchaResponseInterface rather than a simple bool`. While the fundamental purpose of a CAPTCHA is a binary distinction (human vs bot), in practice, most modern CAPTCHA providers expose rich additional data, such as "score" for confidence (see: Google reCAPTCHA v3, hCaptcha), messages, challenge metadata, or error diagnostics. + +By requiring an interface that supplies at minimum an `isSuccess()`: bool method, this PSR permits codebases to enjoy both vendor-agnostic consumption and the option to access extra data by typehinting implementation extensions. This enables: + +* Access to provider-specific scoring/attributes without PSR changes. +* Future-proofing as vendors add richer response objects. +* Compatibility with dependency injection and type-centric application design. +* Shallow-to-deep migration (codebases MAY initially only call `isSuccess()`, later adapt to leverage e.g. `getScore()` if desired). +* Returning only a boolean would foreclose all extensibility. + +### 3.2 Exception Interface + +Instead of prescribing a concrete exception class, this PSR defines a CaptchaException interface, explicitly extending \RuntimeException. This follows the precedent set by PSR-18 and others, allowing vendor libraries to inject their own exception type hierarchies under a unified ancestor. + +### 3.3 Scoring + +The increasing adoption of "scoring" instead of simple pass/fail, particularly for invisible-style CAPTCHAs, required that the PSR not constrain implementations to simply returning booleans. Implementations MAY extend the CaptchaResponseInterface to expose a score or provider raw data, but code depending strictly on the PSR MAY always use the `isSuccess()` boolean. + +This pattern supports both simple and advanced applications (where scoring thresholds might be required by regulators or business rules) and ensures implementations MAY expose the full richness of their backend APIs while remaining PSR-compliant. + +### 3.4 Zero-Refactor Provider Swap + +The overarching design goal is to allow replacing any \Psr\Captcha\CaptchaInterface implementation (e.g. Google, hCaptcha, Yandex, Cloudflare Turnstile, etc) via configuration or dependency injection, without touching application code. This minimizes risk in migration and critical incident response, addressing: + +* sudden API or ToS changes by vendors, +* vendor-bans or unexpected region lockouts, +* cost spikes, +* rapid requirement changes (e.g. accessibility mandates). + +### 3.5 Error Isolation + +CaptchaException MUST be used for errors external to the user; e.g. misconfiguration, lost connectivity to the vendor, failed API authentication, or response parsing errors. +That exception MUST NOT be thrown if CAPTCHA token was actually validated, no matter the result - thus, unsuccessful validation (CAPTCHA provider said that user is a bot) MUST NOT throw an exception as this is not an exceptional case, instead `CaptchaInterface::isSuccess()` MUST return false +This ensures frontend code able to clearly distinguish between "user failed the challenge" and "site is misconfigured/problematic". +User errors (wrong, missing, or expired CAPTCHA tokens) are always indicated via a negative result from isSuccess(). + +## 4. People + +### 4.1 Editor(s) + +* [Ilya Saligzhanov](https://github.com/LeTraceurSnork) + +### 4.2 Sponsor(s) + +* _Vacant_ From 506a0aae41bd7fcf0aea8471956ee3c279db3bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=A1=D0=B0=D0=BB=D0=B8=D0=B3?= =?UTF-8?q?=D0=B6=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Thu, 19 Jun 2025 19:38:38 +0500 Subject: [PATCH 5/7] fix: PHPDoc --- proposed/captcha-meta.md | 6 +++--- proposed/captcha.md | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/proposed/captcha-meta.md b/proposed/captcha-meta.md index 0e1424c70..644ee76ce 100644 --- a/proposed/captcha-meta.md +++ b/proposed/captcha-meta.md @@ -3,7 +3,7 @@ CAPTCHA Meta Document ## 1. Summary -The `CaptchaInterface` defines a standard, implementation-agnostic way to verify whether a user is human or automated, using vendor-provided challenge-response systems. It enables applications and frameworks to substitute CAPTCHA vendors transparently and with minimal effort, especially in crisis scenarios such as API bans, forced migrations, or vendor lock-in. +The `CaptchaVerifierInterface` defines a standard, implementation-agnostic way to verify whether a user is human or automated, using vendor-provided challenge-response systems. It enables applications and frameworks to substitute CAPTCHA vendors transparently and with minimal effort, especially in crisis scenarios such as API bans, forced migrations, or vendor lock-in. The interfaces are intentionally minimal: implementors MAY extend for vendor-specific data, but consumers MUST depend only on functionality guaranteed by the PSR, allowing maximum interoperability and rapid reaction to a changing security landscape, while they MAY depend on functionality, provided by specific implementations, but only if they know for sure this functionality exists (via instanceof/typehinting). @@ -30,7 +30,7 @@ Cons: ### 3.1 Response Interface vs Boolean -During code-review a question have been asked: `why CaptchaInterface::verify() returns CaptchaResponseInterface rather than a simple bool`. While the fundamental purpose of a CAPTCHA is a binary distinction (human vs bot), in practice, most modern CAPTCHA providers expose rich additional data, such as "score" for confidence (see: Google reCAPTCHA v3, hCaptcha), messages, challenge metadata, or error diagnostics. +During code-review a question have been asked: `why CaptchaVerifierInterface::verify() returns CaptchaResponseInterface rather than a simple bool`. While the fundamental purpose of a CAPTCHA is a binary distinction (human vs bot), in practice, most modern CAPTCHA providers expose rich additional data, such as "score" for confidence (see: Google reCAPTCHA v3, hCaptcha), messages, challenge metadata, or error diagnostics. By requiring an interface that supplies at minimum an `isSuccess()`: bool method, this PSR permits codebases to enjoy both vendor-agnostic consumption and the option to access extra data by typehinting implementation extensions. This enables: @@ -62,7 +62,7 @@ The overarching design goal is to allow replacing any \Psr\Captcha\CaptchaInterf ### 3.5 Error Isolation CaptchaException MUST be used for errors external to the user; e.g. misconfiguration, lost connectivity to the vendor, failed API authentication, or response parsing errors. -That exception MUST NOT be thrown if CAPTCHA token was actually validated, no matter the result - thus, unsuccessful validation (CAPTCHA provider said that user is a bot) MUST NOT throw an exception as this is not an exceptional case, instead `CaptchaInterface::isSuccess()` MUST return false +That exception MUST NOT be thrown if CAPTCHA token was actually validated, no matter the result - thus, unsuccessful validation (CAPTCHA provider said that user is a bot) MUST NOT throw an exception as this is not an exceptional case, instead `CaptchaVerifierInterface::isSuccess()` MUST return false This ensures frontend code able to clearly distinguish between "user failed the challenge" and "site is misconfigured/problematic". User errors (wrong, missing, or expired CAPTCHA tokens) are always indicated via a negative result from isSuccess(). diff --git a/proposed/captcha.md b/proposed/captcha.md index bfd130fc2..8ce2902af 100644 --- a/proposed/captcha.md +++ b/proposed/captcha.md @@ -68,18 +68,20 @@ The interface shall achieve: namespace Psr\Captcha; /** - * Interface of Captcha service itself. + * Interface of Captcha verification service itself. * MUST decide whether user passed the Captcha or not and return corresponding response. * SHOULD contain method to configure SCORING threshold (if applicable by PROVIDER) * SHOULD throw a CaptchaException as soon as possible if appears any non-user related error that prevents correct Captcha solving (e.g. network problems, incorrect secret token, e.g.) */ -interface CaptchaInterface +interface CaptchaVerifierInterface { /** * Verifies client token and decides whether verification was successful or not (is user a bot or not). * + * @param string $token + * + * @throws CaptchaException if Captcha cannot be validated because of non-user problems (e.g. due to network problems, incorrect secret token, etc.) * @return CaptchaResponseInterface - * @throws CaptchaException if Captcha cannot be validated (e.g. due to network problems, incorrect secret token, etc.) */ public function verify(string $token): CaptchaResponseInterface; } @@ -91,15 +93,15 @@ interface CaptchaInterface namespace Psr\Captcha; /** - * Interface of the object that CaptchaInterface obliged to return on ::verify() method. - * MUST contain enough information to consistently say whether user succesfully passed Captcha or not. + * Interface of the object that CaptchaVerifierInterface MUST return on ::verify() method. + * MUST contain enough information to consistently say whether user successfully passed Captcha or not. * SHOULD contain actual user's SCORING * MAY contain additional information (e.g., gathered from it's captcha-vendor service's verification endpoint) (i.e. message, errors, etc.) */ interface CaptchaResponseInterface { /** - * Return true/false depends on whether verification was successful or not (is user a bot or not). + * MUST return true/false depends on whether verification was successful or not (is user a bot or not). * * @return bool */ @@ -113,9 +115,10 @@ interface CaptchaResponseInterface namespace Psr\Captcha; /** - * MUST be thrown from CaptchaInterface methods if Captcha test itself cannot be passed due to any reason that is not user-related - network problems, incorrect secret token, unable to parse request-response, etc. + * MUST be thrown from CaptchaVerifierInterface methods if Captcha test itself cannot be passed due to any reason that is not user-related - network problems, incorrect secret token, unable to parse request-response, etc. + * MUST NOT be thrown if CAPTCHA was actually performed validation - even if it failed - instead CaptchaVerifierInterface MUST return CaptchaResponseInterface which ::isSuccess() method MUST return false */ -interface CaptchaException extends \RuntimeException +class CaptchaException extends \RuntimeException { } ``` From 8ef133d0a36e1406ba7ec3601d3fbb2f539cdfee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=A1=D0=B0=D0=BB=D0=B8=D0=B3?= =?UTF-8?q?=D0=B6=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Thu, 19 Jun 2025 20:49:30 +0500 Subject: [PATCH 6/7] fix: naming --- proposed/captcha-meta.md | 2 +- proposed/captcha.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/proposed/captcha-meta.md b/proposed/captcha-meta.md index 644ee76ce..032f2b0a3 100644 --- a/proposed/captcha-meta.md +++ b/proposed/captcha-meta.md @@ -52,7 +52,7 @@ This pattern supports both simple and advanced applications (where scoring thres ### 3.4 Zero-Refactor Provider Swap -The overarching design goal is to allow replacing any \Psr\Captcha\CaptchaInterface implementation (e.g. Google, hCaptcha, Yandex, Cloudflare Turnstile, etc) via configuration or dependency injection, without touching application code. This minimizes risk in migration and critical incident response, addressing: +The overarching design goal is to allow replacing any \Psr\Captcha\CaptchaVerifierInterface implementation (e.g. Google, hCaptcha, Yandex, Cloudflare Turnstile, etc) via configuration or dependency injection, without touching application code. This minimizes risk in migration and critical incident response, addressing: * sudden API or ToS changes by vendors, * vendor-bans or unexpected region lockouts, diff --git a/proposed/captcha.md b/proposed/captcha.md index 8ce2902af..12a3699e2 100644 --- a/proposed/captcha.md +++ b/proposed/captcha.md @@ -1,5 +1,5 @@ -CaptchaInterface for various Captcha services -============================================= +CaptchaVerifierInterface for various Captcha services +===================================================== This document describes common interfaces to query data. These data can be from different sources, from in-memory data to databases, as well as filesystem files. @@ -40,7 +40,7 @@ interfaces/functionality first. * MAY, but SHOULD NOT be represented in other than float formats, e.g. 0 - 100 (as percentages) * SHOULD be accessible through CaptchaResponseInterface extensions * Thresholds for success/failure are implementation-defined - * Threshold SHOULD be configurable via CaptchaInterface extensions + * Threshold SHOULD be configurable via CaptchaVerifierInterface extensions ### 1.2 Goal This specification establishes a standardized interface for CAPTCHA implementations in PHP with the primary objective of **enabling immediate, low-effort substitution of CAPTCHA providers during critical vendor lock-in scenarios**. Specifically addressing real-world crises where: @@ -62,7 +62,7 @@ The interface shall achieve: ## 2. Interfaces -### 2.1 CaptchaInterface +### 2.1 CaptchaVerifierInterface ```php namespace Psr\Captcha; From 4c89592eb10c458c15e9b6653fa7cbbb68a66dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=A1=D0=B0=D0=BB=D0=B8=D0=B3?= =?UTF-8?q?=D0=B6=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Fri, 11 Jul 2025 17:32:03 +0500 Subject: [PATCH 7/7] feat: added WG member --- proposed/captcha-meta.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/proposed/captcha-meta.md b/proposed/captcha-meta.md index 032f2b0a3..c7ee06b6c 100644 --- a/proposed/captcha-meta.md +++ b/proposed/captcha-meta.md @@ -70,8 +70,11 @@ User errors (wrong, missing, or expired CAPTCHA tokens) are always indicated via ### 4.1 Editor(s) +### 4.2 Working Group members + * [Ilya Saligzhanov](https://github.com/LeTraceurSnork) +* [Alexander Makarov](https://github.com/samdark) -### 4.2 Sponsor(s) +### 4.3 Sponsor(s) * _Vacant_