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.
| Guardrail | The rule |
|---|---|
| Compliance layering | No 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 design | Every 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 documentation | Every 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.
| Standard | Status | Basis |
|---|---|---|
| OWASP Top 10 | Partial | Static 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. |
| GDPR | Partial | No 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 / CPRA | Not applicable | Determination 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. |
| CRA | Partial | Phase 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 AA | Partial | AA 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
requirelists 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
| Clause | Status | Concrete mechanism (cited) |
|---|---|---|
| (1) Umbrella, appropriate cybersecurity based on risks | Partial | Permanent 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 vulnerabilities | Partial | PHPStan 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 + resettable | Partial | Safe 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 updates | Met | WordPress 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 access | Partial | WordPress 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 data | Partial | No 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, configuration | Partial | All 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 minimisation | Partial | Visitor 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 mitigation | Not applicable | Cairnstone 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 services | Met | Zero 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 interfaces | Partial | Single 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) | Partial | Strict 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 / monitoring | Partial | The 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 settings | Partial | uninstall.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
| Clause | Status | Concrete mechanism (cited) |
|---|---|---|
| (1) SBOM in a commonly used machine-readable format | Met (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 delay | Met | Ecosystem 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 reviews | Partial | PHPStan 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 vulnerabilities | Partial | CVD 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 policy | Met | Live 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 information | Met | Monitored security@gillish.com mailbox + security.txt Contact line + the public CVD policy, three discoverable paths to a working contact. |
| (7) Secure update distribution | Met | WordPress 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 period | Partial, declared support period set in Phase 3 | CVD 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.