ovos codesafe works around the clock, end to end: it detects the attacks on your site and the errors in your code, shields against the attack pattern while the fix ships, and proves the fix held. Built for a time when AI writes the exploits.
This plugin connects a WordPress site to codesafe. It judges each request against codesafe's Shield rules before your plugins run, and reports what the site sees: attack probes and failed logins, files nobody shipped, the plugins it runs, and the errors in its code. You get your own codesafe instance — run for you by ovos, or set up on your own infrastructure for larger organisations. The plugin is the client, and it is free — GPL-2.0, no telemetry of its own. Talk to us about getting an instance, or try the live demo first.
Roughly half of what a public WordPress site reports is not a bug — it is scanners looking for a way in. codesafe counts those apart from real errors, folds them into attack waves, matches your installed plugins against the vulnerability feeds so "vulnerable and being probed" is something you see rather than guess, and, for a CVE with no patch out yet, lets this plugin shield the site against the exploit's request shape until the update lands.
- Requirements · Quick start · Recommended settings · APCu
- Settings reference · What leaves the site · Updates · Troubleshooting
- What codesafe does with it · Advanced · For developers · Talk to us
- The long version of every feature: docs/FEATURES.md
- WordPress 6.0+ and PHP 8.3+ — the
Requires PHPheader keeps older hosts from activating it. - An ovos codesafe instance reachable from this site (HTTPS). One instance serves all your sites and services.
- APCu — optional, but worth having. Traffic rollups need it; nothing else does. See APCu.
-
Install. Download
ovos-codesafe.zipand upload it under Plugins → Add New Plugin → Upload Plugin, or:wp plugin install https://github.com/ovos/codesafe-client-wordpress/releases/latest/download/ovos-codesafe.zip --activate
The plugin is not on wordpress.org; it updates itself from this repository afterwards (Updates).
-
Have the project in codesafe. On the PROJECTS tab, add a project for this site if it has none. For browser errors, switch JS errors on for the project.
-
Connect. In wp-admin go to Settings → ovos codesafe, type your codesafe address under Connect to codesafe and press Connect. codesafe asks you to sign in and pick this site's project — any operator may — and adds this site's origin to the project's allowed JS origins; the browser comes back with the URL and both keys saved, Enabled ticked and a test error sent. The keys never travel through the browser: the site trades a one-time code for them, server to server, with a secret that never left it. Connect needs a codesafe that has the connect page; on an older one, or if you prefer, paste the project's keys into the fields below the button and tick Enabled, the master switch.
-
Test. The page says what codesafe answered to the test error, and the error is in the grid within a second; Send test error sends another.
-
Read through the switches (below). They all start on — each one is a feature of codesafe, described where you would switch it off — and codesafe's project switches decide what it takes. Browser errors and DOM snapshots start once the JS key is set.
Prefer code? Each setting has a wp-config.php constant that wins over the page and locks the field — with CODESAFE_URL or CODESAFE_API_KEY defined, the connection is the server's and the page offers no Connect:
wp config set CODESAFE_ENABLED true --raw
wp config set CODESAFE_URL https://codesafe.example
wp config set CODESAFE_API_KEY 'the project api_key'
wp config set CODESAFE_JS_KEY 'the project js_key'Everything starts on; codesafe decides. Every feature on the page is on by default (only Enabled, the master switch, and Also load in wp-admin start off), and every one that sends something has a second half on the codesafe project: the 404, SECURITY and ROLLUPS switches, CVE for the inventory, INTEGRITY for the integrity scan and the executed-file watch, AUTO-UPDATE, JS errors with the origins allowlist, and the Shield. codesafe takes only what the project has on, so the boxes here say what this site can send, and a box you untick (or a constant set to false) is the site's own no. Read through them once: each describes a feature of codesafe.
Always on, no box to tick. The software inventory and the Shield run whenever the plugin is connected: codesafe drafts the Shield's rules from the inventory's CVE findings, publishes them to this site automatically, and decides which may block — the project's Shield switch (block, observe or off) and each rule's mode, with every rule's side effects shown and a switch to turn one rule off for this project alone. The server keeps its own say through constants (see Advanced).
The defaults, and when to change them. None of these blocks anything; only auto-update changes the site, and only once codesafe asks.
| Setting | Default | Change it when |
|---|---|---|
| Enabled | off | tick it once the URL and the key are in — the master switch |
| Log level | 4 — warning |
3 — error for a noisy legacy site, 5 — notice when you want more |
| Report 404s | on | never, as a rule — scanner and broken-link traffic, counted apart from errors and never turned into issues, is the probe signal |
| Traffic rollups | on | never — it counts only where the APCu line says available, and codesafe takes it only with the project's ROLLUPS switch on. A project with it off refuses; the site then sends nothing for an hour, asks again, and says so under the box |
| Security events | on | never, as a rule — failed logins, the login that succeeded after failures, rejected nonces, forbidden REST calls, sensitive admin changes; usernames masked, a per-minute budget per kind (a flood is summarised, admin changes never held back) |
| Auto-update on codesafe's word | on | nothing happens until codesafe names a plugin, a theme or WordPress itself: one that is vulnerable and probed while the project's AUTO-UPDATE switch is on, or one an operator picks with TURN ON on the project's SOFTWARE list — then WordPress' own auto-update is switched on for it, the one virtual patch WordPress supports natively; an operator's TURN OFF there switches it off again (that entry alone; for WordPress itself it only withdraws codesafe's override). For WordPress itself that is minor releases only, even where WP_AUTO_UPDATE_CORE is false (untick the box to stop that; never a major release, never past DISALLOW_FILE_MODS, AUTOMATIC_UPDATER_DISABLED or a version-controlled site). On a site with a release pipeline untick it, or leave the project's switch off and pick nothing |
| Integrity scan | on, weekly | a read-only walk for files nobody shipped and the site's hardening posture, shown on this page; codesafe takes the report with the project's INTEGRITY switch on. Press Scan now once right after installing |
| Report JavaScript errors | on, with the JS key | window errors, unhandled rejections, failed fetch/XHR calls from the browser |
| Trace correlation | on | off only if a firewall rejects the extra header — a failed browser request and the PHP error behind it share one trace id |
| Request body | structure |
off keeps every body on the site, and a failed POST cannot be replayed |
| DOM snapshot | on | off where pages carry personal data you do not want in codesafe: a snapshot carries the page's text (What leaves the site) |
| Inline styles into snapshots | on | off where upload size matters — up to 400 KB of CSS per snapshot, so it renders styled |
| Also load in wp-admin and on the login page | off | on when admin-side JavaScript errors matter to you (noisier) |
Turn on when it applies.
| Setting | When | Why |
|---|---|---|
Shield coverage FULL (auto_prepend_file) |
when you can add one line to the PHP configuration — the page prints it | codesafe shows SHIELD FULL instead of SHIELD BASIC: the Shield judges every anonymous request before WordPress exists, direct hits on plugin files included, and the Executed-file watch (on by default) starts recording the PHP files nobody shipped the moment they run — see Advanced |
| Release label | when you deploy with a version or a commit | every report carries it, and codesafe compares a release against the one before |
| Environment | on staging and test sites | staging shows as a badge beside the project name; blank sends WordPress' own WP_ENVIRONMENT_TYPE |
| Tags | when one instance serves many tenants or regions | one filter per tag in codesafe |
| Trusted proxy header and ranges | behind Cloudflare, a load balancer or another proxy | reports, rate rules and offender scores name the visitor instead of the proxy |
Leave off unless you know why: the Shield's CODESAFE_SHIELD_KILL constant, which switches the whole Shield off with no network — for a host that must never call home, or the moment a rule misbehaves and codesafe cannot be reached.
APCu is PHP's shared memory between requests. On this page it matters for one switch:
- Traffic rollups need it. The per-minute counters accumulate in APCu and one request per minute ships them. Without APCu the switch collects nothing and sends nothing — deliberately silent, because a host without shared memory could only produce wrong numbers. The Shield's per-rule hit counters ride the same memory, so they are missing too.
- Everything else works without it. Errors, security events, the inventory, the integrity scan and the Shield's matching itself — the Shield keeps its rules in a file under
wp-content/ovos-codesafe/(orCODESAFE_STORE_DIR) when there is no APCu. Only the Shield's rate rules need it too: without APCu they are skipped.
How to tell: the settings page says so on the APCu line right above Traffic rollups — available or NOT available on this server's PHP. Ask your host to enable the apcu extension for the web server's PHP (mod_php or PHP-FPM); on your own server that is apt install php-apcu or the equivalent, then a restart of PHP. The command line's php -m does not count: CLI PHP usually has APCu off even when the web server has it on.
Every value lives under Settings → ovos codesafe, or as a constant in wp-config.php — a defined constant wins and locks the field.
| Setting | Constant | Default | |
|---|---|---|---|
| Enabled | CODESAFE_ENABLED |
false |
master switch for PHP and browser reporting |
| codesafe URL | CODESAFE_URL |
— | the codesafe instance's base URL, e.g. https://codesafe.example; http(s) only — the form saves any other scheme as no URL and the plugin's own connections speak nothing else; a private or loopback address works |
| API key | CODESAFE_API_KEY |
— | the project's secret api_key (PHP errors) |
| Log level | CODESAFE_LOG_LEVEL |
4 |
send errors with syslog priority ≤ this (0 emergency … 7 debug) |
| Report 404s | CODESAFE_REPORT_404 |
true |
front-end not-found requests, REST routes and admin-ajax actions this site does not have, as access events — rate-limited, static assets ignored, never issues |
| Traffic rollups | CODESAFE_ROLLUPS |
true |
anonymous per-minute request counters and duration histograms by the page type WordPress resolved — REST per registered namespace, admin-ajax per registered action — and logged-in state; needs APCu and the project's rollups switch |
| Security events | CODESAFE_SECURITY_EVENTS |
true |
refused actions and sensitive admin changes as security events; a per-minute budget per kind, the excess summarised |
| — | CODESAFE_INVENTORY |
true |
no settings box: installed plugin/theme/core versions, daily and on change; false keeps them on the site; needs the project's CVE switch (on by default) |
| Auto-update on codesafe's word | CODESAFE_AUTO_UPDATE_VULNERABLE |
true |
WordPress' own auto-update switched on for what codesafe names: a plugin or WordPress itself that is vulnerable and probed (with the project's Auto-update switch), or a plugin, a theme or WordPress' minor releases an operator picks on the project's SOFTWARE list; for WordPress itself minor releases only, over WP_AUTO_UPDATE_CORE false while this is on; an operator's TURN OFF switches it off again (for WordPress itself: codesafe's override withdrawn, WordPress' own setting untouched); needs the inventory |
| — | CODESAFE_SHIELD_DETECT |
true |
no settings box: pull this site's exploit rules (match, rate and virtual-patch rules) and codesafe's threat addresses every five minutes, and judge every request — an anonymous one before the other plugins load, a signed-in one there by the blocks that refuse whoever asks and whole at plugins_loaded; false stops the judging; fails open |
| — | CODESAFE_SHIELD_ENFORCE |
true |
no settings box: 403 on a request a blocking rule or a threat address matches — which rules block is codesafe's word; false stops all blocking on this site; inert without detection; read per request |
| — | CODESAFE_SHIELD_KILL |
false |
constant only: the whole Shield off, no network; codesafe shows the project's Shield as switched off by the server |
| Integrity scan | CODESAFE_SCAN |
true |
the background read-only walk for files nobody shipped; Scan now works without it |
| Scan interval | CODESAFE_SCAN_INTERVAL |
7 |
days between background passes (1 or 7) |
| Executed-file watch | CODESAFE_ENTRY_WATCH |
true |
record every PHP file the site runs that WordPress did not ship as an entry point; records only while the auto_prepend_file layer is active (Advanced); on multisite the main site's |
| Release label | CODESAFE_RELEASE |
— | deploy label on every report, announced to codesafe once when it changes |
| Environment | CODESAFE_ENVIRONMENT |
— | deployment stage; blank sends WP_ENVIRONMENT_TYPE |
| Tags | CODESAFE_TAGS |
— | comma list on every report (shop, eu, tenant:acme); lowercase, up to ten |
| Report JavaScript errors | CODESAFE_JS_ENABLED |
true |
loads the bundled browser client on the front end |
| JS key | CODESAFE_JS_KEY |
— | the project's public js_key (browser errors) |
| Trace correlation | CODESAFE_JS_TRACE |
true |
W3C traceparent header on the page's same-origin fetch/XHR calls |
| Request body | CODESAFE_REQUEST_BODY |
structure |
off — none; structure — parsed, credentials dropped, 8 KB |
| DOM snapshot | CODESAFE_SNAPSHOT |
true |
DOM snapshot with the first error per page load: inputs and scripts stripped, data-codesafe-mask areas blanked, the page's text included |
| Inline styles into snapshots | CODESAFE_SNAPSHOT_STYLES |
true |
embed the page's CSS so snapshots render styled |
| Also load in wp-admin and on the login page | CODESAFE_JS_ADMIN |
false |
also report browser errors from wp-admin and the login page |
| Trusted proxy header | CODESAFE_TRUSTED_PROXY_HEADER |
— | behind a proxy/CDN only: the header naming the visitor (CF-Connecting-IP, X-Forwarded-For); read only from the ranges below |
| Trusted proxy ranges | CODESAFE_TRUSTED_PROXIES |
— | the proxies' CIDRs or addresses, comma-separated; cloudflare = Cloudflare's published ranges (bundled). Empty: REMOTE_ADDR is the visitor. A range wider than /8 (IPv4) or /16 (IPv6), 0.0.0.0/0 and ::/0 among them, is refused; one inside fc00::/7 or fe80::/10 passes |
| — | CODESAFE_STORE_DIR |
— | constant only: an absolute directory outside the document root for the Shield's and the watch's store (recommended on nginx/IIS); the prepend stub stays in wp-content/ovos-codesafe/ |
Example wp-config.php block:
define('CODESAFE_ENABLED', true);
define('CODESAFE_URL', 'https://codesafe.example');
define('CODESAFE_API_KEY', '...');
define('CODESAFE_JS_KEY', '...');
define('CODESAFE_RELEASE', '2026.09.24');
define('CODESAFE_ENVIRONMENT', 'production');Every report is reduced in the plugin before it is sent; codesafe redacts again on its side as a backstop.
- Errors travel with their request context: the URL, method, headers and variables, the logged-in user id, WordPress version, active theme, and which plugin or theme the failing file belongs to. Credentials are dropped by field name wherever they stand — passwords in every spelling, tokens, keys, cookies, authorization headers, CSRF nonces, card numbers and security codes, account numbers and IBANs, and in the request's query and post the reset link's
key,code,otp,sigand their kin. The request body is parsed and cleaned the same way (structure), or not sent at all (off). E-mail addresses and usernames travel as written — in the variables, the body, the URL and referrer, the extra data and a WP-CLI command's arguments: codesafe masks them on arrival and keeps the original encrypted for an audited reveal and a replay. Masked here, they could never be revealed. - Security events carry the refused action's kind, the masked username or the option's name, never a value. The username is the one identity masked here: inside a sentence (
login failed for m***i*) codesafe could not recognise it, and the account travels beside it as its user id. A login typed as an e-mail address travels as written, for codesafe to mask and keep. - Traffic rollups are counts under names WordPress defines — never a URL, an address, a user agent or a cookie.
- The inventory is the list of what is installed with versions, the newest version WordPress's own update check knows for each and for core, whether each one auto-updates, where each one's updates come from (wordpress.org, or the host of its own update server) and whether an offered update has no download (a licence not active, commonly), whether the site can auto-update at all, and core's own update policy — minor and major on or off and what decided it,
WP_AUTO_UPDATE_COREas defined, what blocks it, the failed update WordPress parked (its code, never its paths) and the minor release on offer — never paths, options or user data. - A switch-on codesafe asked for is reported as a security event naming why:
auto-update enabled for akismet (requested by an operator in codesafe),auto-update enabled for WordPress core, minor releases (vulnerable and probed, per codesafe) — WP_AUTO_UPDATE_CORE is false on this site, overridden for minor releases only. - The integrity scan sends paths, sizes and dates — never a file's content — and changes nothing on the site.
- The Shield sends the matched fragment of a flagged request, capped at 200 bytes — none where a secret could be it (any cookie, a header or field named like a secret), and scrubbed of secrets as an error message is — and reads the request body only while a body rule is live.
- DOM snapshots (on by default) carry the page as the visitor saw it, with the first browser error per page load: input values and scripts are stripped in the browser, areas marked
data-codesafe-maskare blanked, and the page's text goes as it is — a customer's account page with it. Untick DOM snapshot where that is more than you want in codesafe.
The full account, per feature, is in docs/FEATURES.md.
The plugin updates itself from this repository through WordPress' own update flow: new versions appear under Dashboard → Updates and install like any directory plugin, one click or unattended (Enable auto-updates on the Plugins screen, or wp plugin auto-updates enable ovos-codesafe). Every release is signed, and the plugin verifies the signature before WordPress installs a byte; a package that does not verify is refused, a release without a signature is not offered. Details, and how to pin a version, in docs/FEATURES.md.
- "codesafe unreachable — check the URL." The site could not reach the instance: a typo in the URL, a firewall between the host and codesafe, or a self-signed certificate — see Advanced for the TLS switch.
- "codesafe rejected the key." The API key is not the project's api_key, or belongs to another project. Copy it again from the project in codesafe.
- Browser errors do not arrive. Report JavaScript errors on, the JS key set, JS errors switched on for the project, and this site's exact origin (
https://www.example.com, no path) in the project's JS origins. - Traffic rollups show nothing. The APCu line above the switch says NOT available, or the project's rollups switch in codesafe is off — the page then says so under the box, and the site asks again every hour. Cached pages served before WordPress boots are never counted, by design.
- No CVE findings. The inventory needs the project's CVE switch in codesafe (on by default) and no
CODESAFE_INVENTORYset tofalse; the first report goes out at the first request after the plugin is connected, and the matching runs nightly. - Shield status says "no ruleset yet". The first request after saving pulls it; "off — …" names what switched it off. "No ruleset in use — the stored one is not sealed under this site's key" follows an update of the plugin or a new API key, and clears with the next pull that codesafe answers. A rule exists only while codesafe has an open CVE finding for this site, so an empty ruleset on a fully updated site is the normal state.
- A blocking rule refuses a real visitor. Switch the rule off for this project in codesafe (SECURITY → SHIELD → OFF HERE) or demote it — the site drops it with its next report to codesafe, at the latest at its next five-minute pull; the INBOX will already carry its alarm. On the server,
CODESAFE_SHIELD_ENFORCEset tofalsestops all blocking at the very next request, andCODESAFE_SHIELD_KILLthe whole Shield.
Try the live demo → — a public instance filled with synthetic errors, no login. codesafe's screens are on the product page. One codesafe for everything you run:
- Issues, not noise — the same error a thousand times is one row with a count and an open → resolved → regressed lifecycle; a failed browser request and the PHP error behind it share one trace id.
- OPS — the INBOX, one list of what needs a person now, across issues, attack campaigns, probed CVE findings, files nobody shipped, late monitors, releases and the Shield's alarms, most urgent first, each with its one ready action.
- Security — probes folded into waves and cross-site campaigns, an address reputation, a blocklist export for your edge, CVE findings with a PROBED count, and the Shield's rules — drafted automatically from the CVE's fix and tested against real traffic, published to the sites that carry the finding, each rule's side effects on show and a switch to turn it off for one project.
- Releases and performance — a release compared against the one before it; response-time trends from the rollups.
- Alerting — throttled mail and chat per project; policies that notify, ticket or assign by themselves.
- Ask your AI — point Claude or any MCP client at codesafe, or hit
◇ AI EXPLAINon a row for a root-cause read.
Your own instance, not a shared tenancy — run for you by ovos in Vienna, or on your own infrastructure when the data has to stay there. More on the product page.
A self-signed certificate on the instance (intranet setups) — TLS verification of the ingest call is on by default; disable it from an mu-plugin or your theme:
add_filter('ovos_codesafe_sslverify', '__return_false');The integrity scan's checksum lists come from wordpress.org over TLS; behind a proxy that re-signs traffic, that verification has its own switch: add_filter('ovos_codesafe_sslverify_wporg', '__return_false');
Manual captures from theme or plugin code — safe to call unconditionally, no-ops while the plugin is disabled:
ovos_codesafe()->captureException($e, ['orderId' => 7]);
ovos_codesafe()->captureMessage('checkout step skipped', 4); // priority 4 = warningThe Shield before the other plugins (always on). While the Shield is on, the plugin keeps a must-use plugin, wp-content/mu-plugins/ovos-codesafe-shield.php, which judges every request before any other plugin's code runs: an anonymous one whole, a signed-in one — whose user only WordPress can name — by the blocks that refuse whoever asks (match rules, virtual patches for anyone); the rest of a signed-in request is judged at plugins_loaded, with its user. It goes when the Shield is switched off, the plugin deactivated or uninstalled.
The Shield before WordPress (optional, auto_prepend_file). The plugins_loaded hook never sees a direct request to a plugin file, and a block there has already paid for the WordPress bootstrap. The settings page prints one line for your PHP configuration that points PHP's prepend at a stub the plugin writes into wp-content/ovos-codesafe/:
; .user.ini or php.ini (PHP-FPM, CGI)
auto_prepend_file = "/var/www/site/wp-content/ovos-codesafe/codesafe-shield.php"# .htaccess (Apache with mod_php)
php_value auto_prepend_file "/var/www/site/wp-content/ovos-codesafe/codesafe-shield.php"With it, every anonymous request is judged before WordPress exists. The settings page says ACTIVE once PHP reports the directive. Requests that are not plainly anonymous are judged in two steps:
- A login cookie or Basic credentials. A request with a login cookie to a file WordPress runs through (
index.php,wp-login.php, wp-admin's own pages …), or a REST request with Basic credentials (an application password, which WordPress checks only later), arrives with its caller unknown — anyone can send a cookie by that name. A plugin's own files run beforeplugins_loaded, so the blocks that refuse whoever asks (a match rule, a virtual patch for anyone) refuse it here. Everything else waits forplugins_loaded, which judges the request whole, with its user. - A token or XML-RPC. A REST request with a token another plugin reads (Bearer, JWT, OAuth) and an XML-RPC call meet the rules for anyone here. The rules for anonymous visitors or a capability wait until WordPress has signed the caller in.
- Pages that load no plugins. WordPress's installer pages (
wp-admin/install.php,upgrade.php,setup-config.php) andwp-activate.phpare judged whole here, cookie or not, as is a direct hit on a plugin's file. - Update directories. Before any rule, the layer refuses every PHP file run from WordPress's update working directories (
wp-content/upgrade/,upgrade-temp-backup/); the updater never requests them over HTTP.
What the layer leaves on your server, and what it never touches:
- It fails open. Any failure lets the request through. The stub sits outside the plugin directory and includes the plugin only while it is there, so removing the plugin never leaves PHP with a missing prepend file.
- The earlier stub. The stub's name says what it is, as Wordfence's
wordfence-waf.phpdoes. A directive that still names the earlier stub,wp-content/ovos-codesafe/prepend.php, keeps working: the plugin keeps that file current and never deletes it by itself, and the settings page asks once for the new line. Remove the old file once no directory's PHP configuration names it. - Your server configuration. The plugin never edits it. The only
.htaccessfiles it writes outside its store deny the two update directories while WordPress updates (WordPress 6.4 and later, which have thepre_unzip_filehook), with the layer or without it. nginx, IIS and Caddy ignore them; OpenLiteSpeed reads a new.htaccessonly after a restart unless its auto-load is on. - The store. Every file in it begins with a PHP
exitand the ruleset's file name is random, so a server that ignores.htaccess(nginx, IIS) serves nothing from it. The settings page warns there, andCODESAFE_STORE_DIRmoves the data out of the document root. - Deactivating and uninstalling. Deactivating switches the layer off through its consent file. Uninstalling removes the store but leaves the stubs in place, doing nothing, while PHP names either of them or the configuration cannot be read — another directory's
php.inior.user.inimay name the other. Remove theauto_prepend_filelines, then the directory. - Multisite. The Shield, the executed-file watch and the trusted proxy are one for every site of a network, in one store, so a super admin sets the watch and the proxy on the main site.
Details in docs/FEATURES.md.
The executed-file watch rides the same layer. It is on by default while the layer is active; the Executed-file watch box or CODESAFE_ENTRY_WATCH switches it off. Every PHP file the site runs that WordPress did not ship as an entry point is recorded the moment it runs (size, md5, the request's address) and checked on the next request against what wordpress.org shipped. A file nobody shipped becomes an integrity finding in codesafe, with one security event (file_executed) for the address that ran it. Details in docs/FEATURES.md.
Pinning or rolling back to one version, for a fleet:
wp plugin install https://github.com/ovos/codesafe-client-wordpress/releases/download/v1.0.0/ovos-codesafe.zip --forceFrom git — clone into wp-content/plugins/ovos-codesafe (the directory name matters), activate, update with git pull, and leave auto-updates off for that copy: an update from wp-admin replaces the whole directory with the release zip.
- The repository root is the plugin directory — junction or symlink it into
wp-content/plugins/ovos-codesafe. assets/codesafe-client.jsis a byte-identical copy of codesafe's browser client,project/public/js/codesafe-client.js(intentionally ES5), andsrc/Shield/Kernel.phpandsrc/Shield/Wordpress.phpof its request-side kernel, the platform-neutral core and WordPress's module; all are synced from the codesafe repository, never edited here.Adapter.phpandPrepend.phprequire the module right after the core, by path, and hand it to every kernel and Facts they build.Tests\Shieldholds both kernel files to php-library's copies (and to codesafe'sclient-php/Shield.phpandclient-php/Shield/Wordpress.phpwhen a checkout is beside this one), checks that both layers read the module, and smokes a judge without WordPress.- Tests run on php-library's harness, the way codesafe's do, and on every push (
.github/workflows/tests.yml, PHP 8.3–8.5):cd ci && composer install && cp .env.tests .env && php cli.php tests run.ci/is a test-only application andtests/holds the classes — the Updater's signed-update path, the Shield kernel and the prepend layer, andTests\Sanitizer, which drives a WooCommerce checkout through the plugin's real request path so no password, cookie or nonce leaves the site raw. Neither directory ships in the release zip. - Two classes skip themselves unless pointed somewhere:
Tests\SanitizerStoredruns the same checkout through codesafe's own scrubbing (OVOS_CONSOLE_PATH, or../consolebeside this repository), andTests\WordPressLivedrives the signed update on a real WordPress with this plugin active (OVOS_WP_PATH). - Releasing:
docs/RELEASE.md— the four version spots, the changelog, onev*tag; the workflow verifies, builds, signs and publishes.
codesafe is built and run by ovos in Vienna. We use it on our own client sites every day, which is why it is shaped the way it is.
- Questions about the plugin, or a bug in it — open an issue.
- Want codesafe for your sites? Write to office@ovos.at. You get your own instance: we run it for you, or for larger organisations we set one up on your own infrastructure. Happy to just answer whether it fits what you have.
- Try it first — the live demo needs no login.
This plugin is GPL-2.0-or-later and always will be — it is a WordPress plugin and a client, and you can read, fork and audit every line of what runs on your site.
The ovos codesafe it talks to is a commercial product — your own instance, run for you by ovos or set up on your infrastructure. It is not covered by this licence.

