captcha-sdk 0.0.1

Unified server-side CAPTCHA verification for Rust
Documentation
# Security and serialization

> **TARGET architecture — implementation pending.** These requirements must be
> enforced and tested before the target API is considered complete.

## Sensitive input

`CaptchaToken` has private storage, redacted `Debug`, and implements neither
`Serialize` nor `Deserialize`. Simple required provider secrets are accepted by
the provider builder and immediately moved into private, redacted,
non-serializable credential storage. No universal public secret type or public
secret getter exists. Public credential types are reserved for structured auth,
such as reCAPTCHA Enterprise API-key or service-account alternatives, and must
also redact sensitive values.

The authoritative storage, best-effort final-drop zeroization, structured-auth,
custom-transport trust, and rotation rules are in
[Credential lifecycle](credentials.md). A custom
[`HttpTransport`](http-transport.md) is privileged: it necessarily sees
serialized credential and token bytes, and the SDK cannot prevent a malicious
implementation from retaining them.

`VerificationRequest` is also non-serializable because it carries a token.
Errors use bounded, normalized context rather than embedding request bodies,
provider responses, URLs with credentials, or arbitrary transport messages.
Tracing records `ProviderId`, timing, result class, and safe normalized codes,
never a token, secret, raw request, or raw response.

Public error values are not serializable or deserializable. The separate,
serialization-only `CaptchaErrorCode` provides stable safe codes without error
messages, URLs, payloads, sources, tokens, or arbitrary provider strings. See
the [error taxonomy](errors.md).

## Output serialization

Normalized outcomes, `Assessment`, `ProviderEvidence`, `Capabilities`, and
their non-secret component enums support `Serialize` for audit events and API
responses. Their schema contains typed fields only—no raw JSON, generic
metadata, or credential-adjacent provider payloads.

`Deserialize` is avoided for authorization-bearing types. Where operational
tooling needs deserialization of evidence or assessments, the result is
explicitly an untrusted data/record type that cannot become authorization
proof. Deserialization never creates a value equivalent to a fresh successful
verification.

There is no public constructor for `VerifiedCaptcha` or any similar proof that
would let callers mint authorization. If such a proof is introduced internally,
only a live `Assessment` with `Decision::Allow` can create it, and it must not be
deserializable.

## Fail-closed rules

- Unsupported policy requirements fail verifier construction.
- Required supported evidence missing from an accepted response denies policy.
- Malformed or ambiguous provider responses produce
  `CaptchaError::Verification(VerificationError::Protocol { .. })`.
- Provider rejection is never retried and never converted into acceptance.
- Ambiguous attempt failure reports `TokenState::PossiblyConsumed`; transient
  status alone does not permit token replay.
- The same token is never sent to a fallback provider.
- Endpoint override cannot be enabled accidentally in the normal API.
- Remote IP forwarding is explicit and follows application privacy/proxy
  policy; the SDK does not infer it from untrusted headers.

## Data handling

Applications should retain only evidence needed for fraud analysis or audit,
apply a documented retention period, and consider hostname, remote IP,
challenge time, action, and score potentially sensitive. Serialized assessments
are observations, not reusable authorization credentials.

Provider adapters constrain response sizes, validate field formats and score
ranges, and avoid reflecting provider strings directly into logs. Unknown
provider rejection codes may be retained only as bounded, sanitized codes in a
typed `UnknownCode { provider_code }` variant; the code is bounded and sanitized
and does not open a generic metadata channel.

`AttemptOptions`, `RetryPolicy`, `TokenState`, internal idempotency keys, and
transport request/response values are not authorization proofs. Generic public
request options never expose idempotency keys. See
[Attempts and safe retries](attempts-and-retries.md).