Security and compliance

What Cairnstone does about GDPR, CRA, OWASP and WCAG.

This page states the real posture, not an aspiration. Every line is either source-cited or marked "not assessed". The honest ladder is met, partial, not assessed, not applicable. A generalised "fully compliant" claim is a legal liability, so it is never written here. This is a working document and it changes as work lands.

Permanent guardrails#

These are doctrine, not per-task decisions. They bind every session and every tool, so the posture cannot drift. They are written as what not to do, because on a WordPress plugin the common compliance reflexes are the dangerous ones.

GuardrailThe rule
Compliance layeringNo server-level configuration (strict CSP, TLS headers) injected from plugin code; it breaks core and Gutenberg and is the wrong layer. No .env in a WordPress plugin; configuration is wp-config.php, options, or approved constants. WordPress core owns password hashing; a plugin never shadows the password store.
Secure by designEvery change keeps static analysis at its strictest level with zero errors and conforms to the WordPress-Extra lint ruleset. Sanitisation and nonce plus capability checks are mandatory at every entry point. No tree-wide automated fixes; compliance is verified surgically, per unit. No hardcoded secrets.
Honest documentationEvery GDPR, CCPA, CRA, or accessibility claim is defensible, source-cited, and mapped to a real status. Fabricated or generalised compliance statements are never written.

Compliance status#

"Partial" means a real baseline is enforced but a formal end-to-end audit has not been done. "Not assessed" means no claim is made yet. Nothing here is marked fully met, because nothing has been independently audited.

StandardStatusBasis
OWASP Top 10PartialStatic analysis at the strictest level, zero errors; WordPress-Extra lint; every superglobal read funnelled through a typed accessor; capability checks, escaping discipline, and input sanitisation are wp.org submission-gate hard requirements. No third-party penetration test.
GDPRPartialNo telemetry without explicit opt-in. Visitor country, when used, is provided by Gillish Node and read by Cairnstone through a server-side bridge; Cairnstone itself stores no location database, makes no external requests, and retains no raw visitor IPs. Conditional Content processes visitor signals to choose a variant. The served-variant analytics log and its retention are not yet formally assessed for data minimisation.
CCPA / CPRANot applicableDetermination 2026-05-16 (maintainer). Cairnstone does not sell personal information (no transfer of visitor data to a third party for monetary or other valuable consideration) and does not share it for cross-context behavioral advertising. All visitor-signal processing is first-party, on the site owner's own server, to choose which block variant renders; nothing is transmitted to Gillish (no telemetry without explicit opt-in), and there is no ad-tech integration or cross-site profiling. The consumer "Do Not Sell/Share My Personal Information" opt-out obligation is therefore not triggered by Cairnstone. This covers Cairnstone itself; a site owner that is independently a covered business remains responsible for its own overall CCPA/CPRA posture. A functional determination from documented data behaviour, not legal advice.
CRAPartialPhase 2 gap audit complete 2026-05-20 (see §Annex I gap audit): per-clause status with cited mechanisms is now public. Most rows are partial because the cite-your-mechanism discipline does not allow status inflation where a formal per-unit audit has not been done, the doctrine working, not a defect. Build gates and the decision-log lifecycle are enforced; runtime dependencies are zero (Composer require lists only PHP ≥ 8.2; GeoIP delegation to Node in v2.2.71/72 removed the previously vendored MaxMind reader). CycloneDX 1.5 SBOM landed (build/sbom-2.2.79.cdx.json; excluded from the distributable zip). Phase 3, declared support period, EU DoC, CE marking, Annex VII technical documentation, is gated to product launch.
EAA / WCAG 2.2 AAPartialAA is the stated bar. Shipped library calls-to-action carry 44px tap targets and a visible focus indicator. The state switcher ships radiogroup semantics with the gated-control reason associated to the control. No full third-party WCAG audit; per-surface testing is incomplete.

Data handling#

  • Visitor signals. Country, device, browser, language, login state, UTM tags, visit count, and local hour are used to decide which block variant a visitor sees. Country, when used, is provided by Gillish Node and read through a server-side bridge; Cairnstone stores no location database and retains no raw visitor IPs.
  • Analytics. A schema-versioned table logs which variant was served per Conditional Content block, for the analytics screen. Retention and data-minimisation review is listed under "not yet assessed".
  • No phone-home. No activation telemetry and no anonymous usage statistics unless the site owner actively turns them on.
  • Documentation site. This site self-hosts its webfont for GDPR; there is no third-party font CDN call.

Security measures#

  • Static analysis at the strictest level, zero errors, after every change; new errors are regressions, not new debt.
  • Lint: the WordPress-Extra ruleset; new violations are not added to touched files.
  • Input: superglobal reads go through a typed accessor; sanitisation and nonce plus capability checks are submission-gate hard requirements.
  • Output: every output goes through the appropriate escaping helper; exceptions require a comment explaining why the value is already trusted.
  • Headers and TLS: deliberately not handled by the plugin. This is correct, not a gap; the plugin layer is the wrong place for site-wide security headers.
  • Secrets: none hardcoded in the plugin codebase.

Accessibility#

Conformance target is WCAG 2.2 AA. Enforced uniformly on shipped surfaces: 44px minimum tap targets and a visible focus indicator on every call-to-action; suppressing the focus outline without a replacement is prohibited. The state switcher ships as a radiogroup with named segments, and a gated control announces why it is disabled through a description associated to the control, not an orphaned note. Verification is by exercising the real editing surface in a browser, since a passing test does not prove a surface is usable. Open: no full third-party audit; per-surface formal testing is incomplete.

Dependencies and SBOM#

  • Zero runtime third-party surface. Composer require lists only the PHP platform (≥ 8.2); the previously-vendored MaxMind GeoLite2 reader was removed in v2.2.71 / v2.2.72 (2026-05-19) when GeoIP was delegated entirely to Gillish Node. Development tooling never ships in the build.
  • License audit. Every shipped third-party asset must be GPLv2-or-later compatible; new dependencies are checked before shipping.
  • SBOM: implemented (Phase 2, 2026-05-20). CycloneDX 1.5 JSON at build/sbom-2.2.79.cdx.json; regenerates on version bump; excluded from the distributable zip. Near-empty by honest design, Cairnstone's runtime third-party surface is genuinely just the PHP platform. An automated dependency-vulnerability audit / scanning pipeline remains pending; tracked under "Not yet assessed".

Annex I gap audit (Phase 2)#

Phase 2 of the Core-owned CRA program: per-clause mapping of every Annex I requirement to Cairnstone's concrete mechanism, with a mandatory citation per row. Completed 2026-05-20 against the clause structure verified the same day from the consolidated OJ text (Gillish Core plans/CRA-PHASE2-PLAN.md §3.1). The status ladder is met / partial / not applicable / not assessed. No row may read met without an exercisable cited mechanism, partial rows reflect that a real baseline exists but a formal per-unit audit has not been done, which is the cite-your-source discipline the project enforces, not a defect.

Annex I Part I: cybersecurity requirements relating to product properties

ClauseStatusConcrete mechanism (cited)
(1) Umbrella, appropriate cybersecurity based on risksPartialPermanent guardrails (§Permanent guardrails above) bind every change; wp.org submission gate enforces hard requirements (plans/WP-ORG-SUBMISSION-GATE.md); no formal documented risk assessment per CRA Art. 13 yet, Phase 3 deliverable.
(2)(a) No known exploitable vulnerabilitiesPartialPHPStan level 9 + WordPress-Extra phpcs + PHPCompatibility, 0-error invariants; Plugin Check release gate. No third-party penetration test; no CVE recorded against Cairnstone.
(2)(b) Secure-by-default + resettablePartialSafe defaults are wp.org-gate-mandated (Plugin Check rejects unsafe defaults); resettable state via uninstall.php (drops plugin-owned tables, removes gillish_canvas_ options, gc_ transients, capabilities). No per-block formal review of default values conducted.
(2)(c) Vulnerabilities addressable via security updatesMetWordPress core's signed update channel (readme.txt Stable tag mechanism, wp.org distribution); studio's controlled Pro update path uses the same transport guarantees.
(2)(d) Protection from unauthorised accessPartialWordPress capability checks (current_user_can) and nonces on every write path, wp.org-gate-mandated. Single hook registry in src/Plugin.php. REST routes (Conditional Content country + visitor-state, set-value, import/export) each carry a capability permission_callback (manage_options for import/export, edit_others_posts for set-value, logged-in + rate limit for visitor-state writes); cookie-auth requests carry the core wp_rest nonce. The one deliberately public route is the read-only country lookup (a single ISO code, no PII, no state change). No per-entry-point formal audit.
(2)(e) Confidentiality of stored / transmitted / processed dataPartialNo raw visitor IP storage; country resolution delegated entirely to Gillish Node since v2.2.71/72 (2026-05-19) via the gillish_node_resolve_country_code filter (no Cloudflare-header path in Cairnstone); Cairnstone ships no location database of its own and makes no external network calls at runtime. The gillish_canvas_events analytics log has not been formally PII-assessed.
(2)(f) Integrity of data, commands, programs, configurationPartialAll superglobal / attribute / option reads funnel through src/Shared/Attr.php (typed-narrow accessors); outputs escaped via the appropriate esc_* helper at the boundary; nonces on every write. declare(strict_types=1) in every PHP file. No per-call-site formal audit.
(2)(g) Data minimisationPartialVisitor signals (Conditional Content) bounded to what is needed to choose a variant; no raw IP retained; no telemetry without explicit opt-in. The gillish_canvas_events table's retention and PII-minimisation review is open.
(2)(h) Availability + DoS mitigationNot applicableCairnstone is a WordPress plugin; availability sits in the site owner's hosting / WP-core layer. Cairnstone introduces no external network calls at render-time, no expensive server processing on visitor requests, and no new DoS vectors.
(2)(i) Minimise negative impact on other servicesMetZero frontend JS / CSS by default; runtime makes no external HTTP requests (GeoIP delegation to Node in v2.2.71/72 removed the only external-resource path that had ever existed). Cairnstone's runtime is process-local on the WP host.
(2)(j) Limit attack surfaces, incl. external interfacesPartialSingle grep-able hook registry in src/Plugin.php; REST routes each carry an explicit permission_callback (capability-gated writes plus one deliberately public, read-only country lookup); admin pages and AJAX handlers funnel through capability- and nonce-gated entry points (wp.org gate). No formal external-interface inventory yet, Phase 3 deliverable.
(2)(k) Reduce impact of incidents (exploitation mitigation)PartialStrict types per file; typed-narrow superglobal reads via Shared\Attr; PHPStan level 9 catches whole classes of latent type-confusion bugs; WordPress / host layer carries process-level mitigations. No formal exploit-mitigation features beyond established WP best-practice.
(2)(l) Security-related information recording / monitoringPartialThe gillish_canvas_events table records variant-served events (functional / analytics, not a dedicated security audit log; the table keeps its original name by design, a rename would orphan existing data); authentication-event logging is inherited from WordPress core. Cairnstone does not implement an additional security-event log.
(2)(m) Secure permanent removal of data and settingsPartialuninstall.php provides full destructive cleanup but is opt-in: gated on gillish_canvas_keep_data_on_uninstall='no' because the default is to preserve analytics history. The user can securely remove on a permanent basis; clearer UI surfacing of the opt-in would close this row to met.

Annex I Part II: vulnerability-handling requirements

ClauseStatusConcrete mechanism (cited)
(1) SBOM in a commonly used machine-readable formatMet (this commit)CycloneDX 1.5 JSON at build/sbom-2.2.79.cdx.json; near-empty by honest design (zero Composer runtime deps; no vendored runtime third-party post-GeoIP-delegation); excluded from the distributable zip via make-zip.ps1; regenerates on version bump.
(2) Address vulnerabilities without delayMetEcosystem CVD intake operational; security@gillish.com watched via Thunderbird IMAP (2026-05-19); standard wp.org update channel for rapid distribution.
(3) Effective and regular tests and reviewsPartialPHPStan level 9 + WordPress-Extra phpcs + PHPCompatibility + PHPUnit; Plugin Check release gate; runs after every PHP edit batch. Automated dependency-CVE audit, in-session pre-push gate: the agent runs composer audit --format=plain against the resolved lockfile before every git push origin main on Core / Cairnstone / Node / MCP; an open Packagist advisory holds the push until resolved. The gate moved from a GitHub-Actions workflow to an in-session pre-push step on 2026-05-24 alongside the broader auto-deploy retirement; same zero-tolerance threshold throughout. Feed and Mesh inherit when their toolchains stand up. No formal regular cadence for human-driven code review beyond per-change.
(4) Public disclosure of fixed vulnerabilitiesPartialCVD policy commits to structured public advisories (readme.txt changelog + docs site); format committed, channel live. No vulnerabilities have been fixed and disclosed yet, first execution awaits the first real report.
(5) Coordinated vulnerability disclosure policyMetLive at html.gillish.com/mesh/security/#cvd; canonical source gillish-core/plans/CRA-CVD-POLICY.md; security.txt published at /.well-known/security.txt (RFC 9116).
(6) Facilitate sharing of vulnerability informationMetMonitored security@gillish.com mailbox + security.txt Contact line + the public CVD policy, three discoverable paths to a working contact.
(7) Secure update distributionMetWordPress core's signed update channel (wp.org Stable tag); Pro tier via the studio's controlled update path with the same transport guarantees.
(8) Free + timely security updates within declared support periodPartial, declared support period set in Phase 3CVD policy commits to free security updates within the support period; the support-period number itself is a Phase 3 deliverable. Proposed default 5 years from a version's release. Row moves to met when the support period lands in readme.txt and is declared in the EU Declaration of Conformity.

Summary. Part I, 14 clauses: 2 met, 11 partial, 1 not applicable. Part II, 8 clauses: 5 met, 3 partial. The partial rows concentrate on items that require either formal per-unit audits (the discipline does not let "we mostly do this" read as met) or Phase 3 deliverables (declared support period, formal risk assessment, exploitation-mitigation statement). These are scheduled, not gaps in conduct.

Not yet assessed#

The honest gaps, listed so a reviewer can see exactly what is open:

  • Conditional Content and analytics-log data-flow and retention review for data minimisation.
  • An automated dependency-vulnerability audit / scanning pipeline. SBOM generation landed in Phase 2 (2026-05-20); the automated-audit side remains pending.
  • Full third-party WCAG 2.2 AA audit across every surface.
  • A formal secure-development-lifecycle statement for CRA beyond the build gates already in place.

Input on any of this goes back to the maintainer directly. The honest gap a reviewer can name is the most valuable thing on this page.