Skip to content

Security: cboxdk/license

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Please report security vulnerabilities through GitHub Private Vulnerability Reporting on this repository:

Security tab → Report a vulnerability

This opens a private advisory visible only to the maintainers. Please do not open a public issue for a suspected vulnerability, and please do not disclose it publicly until a fix is available.

Include, as far as you can: the affected version, a description of the issue, and a minimal reproduction (ideally a failing Pest test that mints a keypair with Cbox\License\Support\Ed25519KeyPair::generate() and drives the issuer/verifier, so no secrets or infrastructure are needed).

This is a best-effort open-source project. We will acknowledge reports and work a fix as promptly as we reasonably can, but we do not commit to a fixed response or remediation SLA, we do not operate a security@ mailbox or a PGP key, and we are not a CVE Numbering Authority. GitHub's advisory workflow is the single supported channel.

Supported versions

This library follows semantic versioning. Until a 1.0.0 release, security fixes land on the latest 0.x line; there is no back-porting to earlier 0.x releases.

Security posture

The signing and verification path is the security-sensitive part of this package, so its posture is stated explicitly and honestly.

Vetted crypto, pinned algorithm

  • Signing and verification wrap firebase/php-jwt over libsodium's Ed25519. There is no hand-rolled signature code.
  • The algorithm is pinned to EdDSA on every decode path (new Key($publicKey, 'EdDSA') for both licenses and revocation lists). Because only one algorithm is accepted, alg:none tokens and HS256/EdDSA confusion (re-signing with the public key as an HMAC secret) are rejected at decode.
  • Verification is fully offline — a local signature check against a bundled public key, with no network dependency.

Deny-by-default verification

Ed25519LicenseVerifier::verify() is a total function that never throws; every outcome that is not a provably valid, in-window, correctly-bound, non-revoked license is denied with a typed LicenseStatus and an operator-visible reason. Malformed claims are caught internally and surface as Malformed, never as an exception to the caller.

Revocation freshness: fail-open by default, fail-closed on demand

Revocation lists are signed and verified with the same pinned algorithm. The list carries a signed issuedAt, monotonic sequence, and optional expiresAt, so a stale or replayed snapshot cannot be forged without the private key.

By default RevocationList::fromSigned() is fail-open: an unusable (missing, malformed, or tampered) list yields null and is treated as "no usable list", so a broken feed never denies service to a legitimately licensed deployment. Where revocation is security-critical, pass a RevocationPolicy (failClosed(...) / requirePresence()) into the VerificationContext: the verifier then treats a stale, replayed, expired, or absent list as Revoked instead of trusting it. Freshness is judged against the caller-injected now, never a library wall clock.

Residual limitation: this is tamper-evident offline revocation with a freshness window, not a live check. An attacker who can BOTH withhold newer lists AND control the clock the verifier is handed can still stall revocation up to the window you allow. Choose maxAgeSeconds accordingly and drive now from a trusted source.

Honest scope: tamper-evident, not tamper-proof

This library provides a cryptographically sound, offline-verifiable licensing signal. It is not DRM and cannot be: an operator in full control of their own runtime can always modify or bypass the check. The signature guarantees a license was issued by the holder of the private key and has not been altered; it cannot guarantee that a self-hosted binary actually honours the result. Protect the issuer private key accordingly — it is the root of trust for every deployment.

There aren't any published security advisories