Server-side fixes
Fix API and validation bugs without a deploy. Run the sidecar next to your app, then add the Sarcio library for your language. On hosting that cannot run a service, PHP apps and WordPress sites can skip the sidecar.
1. Run the sidecar
The sidecar runs on the same host as your app and hands it approved fixes over a local socket. It only makes outbound HTTPS requests to control.sarcio.io. If it is down, your app runs its normal code.
Docker Compose
Add the service and share its socket directory with your app. Pin a version: there is no latest tag.
compose.yaml
services: sarcio-sidecar: image: ghcr.io/sarcioai/sarcio-sidecar:0.2.2 command: - "-socket" - "/run/sarcio/sarcio.sock" - "-control-plane" - "https://control.sarcio.io" - "-site" - "pk_your_site" environment: SARCIO_CONTROL_PLANE_KEY: sk_your_sidecar_key SARCIO_SHIM_TOKEN: an-app-token-you-choose SARCIO_SOCKET_MODE: "0660" # if your app runs as a different user volumes: - sarcio-run:/run/sarcio
your-app: volumes: - sarcio-run:/run/sarcio # the same socket directory
volumes: sarcio-run:Linux: install script, deb or rpm
The script picks the deb, rpm or binary for the machine and verifies it before installing. Read it first.
Shell
curl -fsSLO https://github.com/sarcioai/sarcio-sidecar-releases/releases/latest/download/install.shless install.shsh install.sh v0.2.2Set your keys in /etc/sarcio/sidecar.env:
/etc/sarcio/sidecar.env
SARCIO_CONTROL_PLANE=https://control.sarcio.ioSARCIO_SITE_KEY=pk_your_siteSARCIO_CONTROL_PLANE_KEY=sk_your_sidecar_keySARCIO_SHIM_TOKEN=an-app-token-you-chooseAdd your app's user (here www-data) to the sarcio group so it can reach the socket, then start the service:
Shell
sudo usermod -aG sarcio www-datasudo systemctl enable --now sarcio-sidecarPackages and binaries are also on the releases page.
Add the sidecar as a second container in the pod and share the socket over an emptyDir volume:
deployment.yaml (pod spec)
spec: volumes: - name: sarcio-run emptyDir: {} containers: - name: your-app volumeMounts: - { name: sarcio-run, mountPath: /run/sarcio } - name: sarcio-sidecar image: ghcr.io/sarcioai/sarcio-sidecar:0.2.2 args: - "-socket" - "/run/sarcio/sarcio.sock" - "-control-plane" - "https://control.sarcio.io" - "-site" - "pk_your_site" env: - { name: SARCIO_SOCKET_MODE, value: "0660" } envFrom: - secretRef: { name: sarcio-sidecar } # SARCIO_CONTROL_PLANE_KEY, SARCIO_SHIM_TOKEN volumeMounts: - { name: sarcio-run, mountPath: /run/sarcio }Each release's SHA256SUMS is signed with minisign. The install script checks it when minisign is installed. By hand:
Shell
minisign -Vm SHA256SUMS -P RWT/8gXBswXCZXMhfResaZIVGcwEjR/89MHEoYpo0C5ejHNUVTWiddoDsha256sum --check --ignore-missing SHA256SUMSThe container image is signed with cosign. Save this public key as sarcio-cosign.pub, verify, and pin the digest it prints rather than a tag:
sarcio-cosign.pub
-----BEGIN PUBLIC KEY-----MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0BZVOjeZFPL6TDrSNwB7nEwnencpxBbo2v/0aOF+3+jt9faYKQVP4I55/QSNvEKzEdIORD6Xr7R/HU3Q46LC3g==-----END PUBLIC KEY-----Shell
cosign verify --key sarcio-cosign.pub ghcr.io/sarcioai/sarcio-sidecar:0.2.22. Add the Sarcio library to your app
One small dependency from your language's public registry. It asks the local sidecar, per request, whether a fix applies. Use the same site key, socket path and app token you gave the sidecar. PHP can also run without the sidecar: see PHP without the sidecar.
Node.js
The Express adapter needs Express 5.
Shell
npm install @sarcio/middlewareExpress
Create one client per process and register it after your body parser:
server.ts
import { createProtocolShim, ProtocolShimClient } from '@sarcio/middleware';import express from 'express';
const sarcio = new ProtocolShimClient({ siteKey: 'pk_your_site', shimToken: process.env.SARCIO_SHIM_TOKEN, target: { socketPath: '/run/sarcio/sarcio.sock' }});await sarcio.start();
const app = express();app.use(express.json());app.use(createProtocolShim(sarcio)); // after your body parserA fix that relaxes a rule or changes a setting reaches your handler on req.sarcio.skipValidations and req.sarcio.config.
Create the client as above, then register one line:
Fastify
import { createProtocolFastifyHook } from '@sarcio/middleware';
app.addHook('preHandler', createProtocolFastifyHook(sarcio));Koa
import { createProtocolKoaMiddleware } from '@sarcio/middleware';
app.use(createProtocolKoaMiddleware(sarcio)); // after your body parserHono
import { createProtocolHonoMiddleware } from '@sarcio/middleware';
app.use('*', createProtocolHonoMiddleware(sarcio));node:http
import { createServer } from 'node:http';import { withProtocolSarcio } from '@sarcio/middleware';
createServer(withProtocolSarcio(sarcio, handler)).listen(3000);PHP
Shell
composer require sarcio/shim:^0.3Laravel
The service provider registers itself. Set the connection in .env:
.env
SARCIO_SIDECAR_DSN=unix:///run/sarcio/sarcio.sockSARCIO_SITE_KEY=pk_your_siteSARCIO_SHIM_TOKEN=an-app-token-you-chooseAdd the middleware to the global stack (Laravel 11 and later):
bootstrap/app.php
// bootstrap/app.php->withMiddleware(function (Middleware $middleware) { $middleware->append(\Sarcio\Shim\Laravel\SarcioMiddleware::class);})Your validation code reads what an approved fix relaxed:
PHP
$sarcio = $request->attributes->get('sarcio');
if (!in_array('referralCodeRequired', $sarcio['skipValidations'] ?? [], true)) { // enforce the rule as normal}Laravel 10: app/Http/Kernel.php
// app/Http/Kernel.phpprotected $middleware = [ // ... \Sarcio\Shim\Laravel\SarcioMiddleware::class,];Symfony: config/services.yaml
# config/services.yamlSarcio\Shim\Cache\ApcuRouteSetCache: ~
Sarcio\Shim\Shim: arguments: $dsn: '%env(SARCIO_SIDECAR_DSN)%' $siteKey: '%env(SARCIO_SITE_KEY)%' $cache: '@Sarcio\Shim\Cache\ApcuRouteSetCache' $framework: 'symfony' $shimToken: '%env(SARCIO_SHIM_TOKEN)%'
Sarcio\Shim\Symfony\SarcioKernelListener: arguments: $shim: '@Sarcio\Shim\Shim' tags: - { name: kernel.event_listener, event: kernel.request } - { name: kernel.event_listener, event: kernel.response }Plain PHP
$sarcio = new \Sarcio\Shim\Shim( dsn: 'unix:///run/sarcio/sarcio.sock', siteKey: 'pk_your_site', cache: new \Sarcio\Shim\Cache\ApcuRouteSetCache(), shimToken: getenv('SARCIO_SHIM_TOKEN') ?: '',);
// null means: run your normal code$fix = $sarcio->evaluate($method, $path, ['headers' => $headers, 'body' => $body]);On PHP-FPM, install the APCu extension so requests to unpatched routes skip the sidecar.
PHP without the sidecar
On shared or managed hosting where you cannot run a service, sarcio/shim 0.3 and later can fetch your site's signed server-side fixes from Sarcio itself, check each signature against your workspace's signing key, and decide every request inside PHP. Skip step 1 and give the library your workspace address, the site key and the site's sidecar key (sk_…) instead of a socket. It needs ext-sodium, which ships with PHP.
Plain PHP, without the sidecar
use Sarcio\Shim\Cache\ApcuRouteSetCache;use Sarcio\Shim\Shim;use Sarcio\Shim\Transport\InMemoryPatchCache;use Sarcio\Shim\Transport\PullTransport;
$transport = new PullTransport( 'https://acme.sarcio.io', 'pk_your_site', getenv('SARCIO_SIDECAR_KEY') ?: '', new InMemoryPatchCache(),);$sarcio = new Shim('', 'pk_your_site', new ApcuRouteSetCache(), transport: $transport);In Laravel or Symfony, register this Shim in the container in place of the default one. A newly approved fix takes effect within the refresh window (60 seconds by default) rather than in seconds, and everything else behaves the same: a fix that does not verify never applies, an expired fix stops on its own, and if Sarcio cannot be reached the last verified set keeps serving. Under PHP-FPM, keep the verified set in a store your workers share (implement the library's PatchCache over APCu or your framework's cache), so the fetch happens once per window rather than once per request. Keep the sidecar key out of your code, as you would any secret.
Java
Needs Java 21. The library is on Maven Central.
pom.xml
<dependency> <groupId>io.sarcio</groupId> <artifactId>shim</artifactId> <version>0.2.0</version></dependency>Spring Boot
The filter registers itself at the front of the chain. Configure it:
application.properties
sarcio.enabled=truesarcio.socket-path=/run/sarcio/sarcio.socksarcio.site-key=pk_your_sitesarcio.shim-token=${SARCIO_SHIM_TOKEN}Your handlers read what an approved fix relaxed. Set sarcio.enabled=false to switch the filter off.
Java
@SuppressWarnings("unchecked")var sarcio = (Map<String, Object>) request.getAttribute(SarcioFilter.ATTRIBUTE);var skip = (List<String>) sarcio.get("skipValidations");build.gradle.kts
dependencies { implementation("io.sarcio:shim:0.2.0")}Without Spring, register the filter yourself:
Plain servlets
var sarcio = SarcioShim.create( ProtocolClient.Target.unix("/run/sarcio/sarcio.sock"), "pk_your_site", "java", 25, System.getenv().getOrDefault("SARCIO_SHIM_TOKEN", ""));sarcio.start();
servletContext.addFilter("sarcio", new SarcioFilter(sarcio)) .addMappingForUrlPatterns(null, false, "/*");WordPress
One plugin covers front-end and server-side fixes and the in-admin assistant, on any host: shared, managed or your own. No script tag, no Composer, no service to run, and no fix from this plugin writes to your site's files: every fix is applied to the page or the request as it runs. Every change the assistant makes is an ordinary WordPress write made as the signed-in user; it never writes or runs code the model generated, it installs and updates plugins and themes only through WordPress's own updater, with checks and a way back, and only the separate Sarcio Advanced add-on lets it write the site's Additional CSS or apply signed PHP file fixes. Site owners: the plain-words walkthrough is Sarcio for WordPress; this section is the developer's reference.
- Install the Sarcio plugin from WordPress.org (Plugins → Add New Plugin, search for Sarcio) and activate it. It needs WordPress 6.0+ and PHP 8.1+.
- Open Sarcio in the admin menu and click Connect to Sarcio (or Create a workspace if you are new). Sign in, choose the site, and click Authorize: the plugin receives its keys itself, with nothing to copy. Reporting, front-end fixes and server-side fixes now work.
- Everyone else who reviews fixes opens Sarcio → Inbox and clicks Connect your Sarcio account once. WordPress then keeps them signed in to the Inbox.
Reports, drafted fixes and approvals live in Sarcio → Inbox, with a count on the menu and a Sarcio box on the WordPress Dashboard when something is waiting. The Inbox and Sarcio → Live fixes are WordPress screens: your server fetches this site's reports and fixes from Sarcio with the reviewer's own sign-in, and nothing from Sarcio is shown in a frame. Preview opens the fix on your live site for you alone, and approving asks for your Sarcio password or an authenticator code, passed through for that one request and never stored, before anything goes live. Turn off under Sarcio → Live fixes retires a live fix with the same password or code. Once a fix is live, Keep this fix saves it in your site's database: it keeps working even if the site is no longer connected to Sarcio, and Sarcio → Kept fixes lists it with turn off and remove.
Prefer to copy the keys yourself? Enter the site key under Settings → Sarcio, and the site's sidecar key (Sites → Manage → Sidecar keys) for server-side fixes; a newly approved fix is then live within the refresh interval (30 seconds by default).
Sarcio → Assistant is a chat that changes the site through a catalog of abilities the plugin ships (pages and posts, menus, a block theme's template parts, Global Styles, a classic theme's Customizer settings, style rules, WooCommerce settings, products and coupons, plugin activation and directory-only installs): the Sarcio service only ever answers with the name of an ability to run and its arguments, and the plugin runs it as the WordPress user under WordPress's own capability checks. Previewable writes are staged on a per-chat preview of the site that only that browser sees (an HttpOnly cookie scoped to the site, two hours at most) and applied on Apply changes; every applied write is journaled in the plugin's own {prefix}sarcio_changes table with an undo, and each person's conversations are kept in {prefix}sarcio_conversations. Both tables are dropped on uninstall. On WordPress 6.9 and later the same catalog registers with the Abilities API, so the MCP Adapter plugin exposes it to any MCP client under the same permissions.
A style rule is one selector, one property and one value, stored as data and printed through WordPress's own CSS filter (safecss_filter_attr), so the assistant never saves, suggests or stages free CSS text and the site styles refuse it too. The free Sarcio Advanced plugin adds a sarcio-advanced/css-update ability (formerly sarcio-advanced-styling/css-update) that writes the Additional CSS directly, for users with edit_css, previewed and undoable like every write. Across sites tasks are pulled only when an administrator ticks Receive changes your agency writes for several sites (SARCIO_BULK_ENABLED); it is off by default. A task, an undo included, is only ever prepared as a preview and saved under Sarcio → Requests for an administrator to apply as themselves. While it is prepared the run is limited to content, menus, media, products, comments, categories, SEO and design, with no plugin, theme, user or settings ability offered, and other plugins' abilities, MainWP's included, are left out.
A host you deploy yourself can pin any setting in wp-config.php instead, which locks that field in the admin:
wp-config.php
define('SARCIO_SITE_KEY', 'pk_your_site');define('SARCIO_SIDECAR_KEY', 'sk_your_site');Have a host that can run a service?
| Setting | What it does |
|---|---|
Reporter audience | Who sees the reporter: users who can edit_posts (default), logged_in or everyone. Fixes always apply to every visitor. |
SARCIO_SERVER_SCOPE | all (default), rest for the REST API only, or off for front-end fixes only. |
Sidecar key / SARCIO_SIDECAR_KEY | The site's sk_… key. Turns server-side fixes on without a sidecar; write-only in the admin. |
Refresh interval / SARCIO_PULL_REFRESH_SEC | How often the plugin checks for new or retired fixes without a sidecar. Default 30 seconds; 15 to 3600. |
Assistant abilities / SARCIO_ASSISTANT_DISABLED | Settings → Sarcio → Assistant lists every ability with a switch; the constant takes a comma-separated list of ability names to keep off, e.g. sarcio/plugin-install,sarcio/style-rule-set. A disabled ability is neither offered to Sarcio nor run. |
Receive changes your agency writes for several sites / SARCIO_BULK_ENABLED | Off by default. When on, the site checks Sarcio every five minutes for an Across sites change and prepares it as a preview under Sarcio → Requests for an administrator to apply. |
SARCIO_API_URL | For developers only: sends the plugin's API requests to another sarcio.io address over https, or to a local Sarcio stack (localhost, 127.0.0.1, *.sarcio.local). Anything else is ignored. The reporter script always loads from https://control.sarcio.io/sarcio.js. |
Sidecar DSN / SARCIO_SIDECAR_DSN | Optional. With a sidecar on the host, its socket, e.g. unix:///run/sarcio/sarcio.sock; fixes then arrive in seconds. |
A value set in wp-config.php shows read-only in the admin.
3. Check it works
The sidecar logs listening on unix /run/sarcio/sarcio.sock when it is up:
Shell
# Docker Composedocker compose logs sarcio-sidecar
# deb / rpmjournalctl -u sarcio-sidecar -n 20curl -s http://127.0.0.1:9090/healthzThen report a bug on a server-backed form and run your first fix. The fix applies on the next request after approval.
Rotating the sidecar key
Ready to try it on your own site?
Create a workspace, add a site, and follow these steps on your own app.
Create a workspace