Security

Where it runs and who can reach it

Counterfirm is two WordPress plugins. If you self host, it runs on your hosting, inside your WordPress, behind whatever perimeter you already have, and nobody else operates it. This page says exactly what the software does, and then says plainly what it has not been certified for.

What happens to your filesInstalling it yourself

No SOC 2. No penetration test yet. The section near the bottom of this page explains both.

Where it runs

Ordinary WordPress, on hosting you choose

The application is a plugin. It installs into a WordPress site, creates its own database tables with the standard table prefix, and serves its interface from one page and one REST namespace. The website you are reading is the second plugin and is independent of the first.

Self hosted, that means your security boundary is your WordPress install: your host, your TLS certificate, your admin accounts, your backups, your update policy. Nothing phones home. There is no licence check, no telemetry, and no outbound connection at all unless you configure a language model key, which is optional and covered on its own page.

Hosted by the operator of this site, it is the same two plugins on ordinary managed hosting. That is an honest description of the architecture rather than a marketing one, and it is the reason the section on what is not claimed exists.

  • Standard WordPress roles and capabilities, not a parallel permission system
  • One REST namespace, with a permission callback on every route
  • Its own tables, so uninstalling can remove them cleanly if you ask it to
  • No outbound network calls unless a language model key is configured
Your companythe copy of it that you are allowed to break

What the code does

Six things you can go and verify in the source

Each of these is a specific behaviour in a specific file rather than a posture. If you self host, you can read all of it.

Files are never in the media library

Uploads are moved into a counterfirm folder inside the WordPress uploads directory, one subfolder per twin, with a randomised filename prefix. They never become attachments, so they never get an attachment page, never appear in the media grid, and never get handed to an image resizer.

The folder carries deny rules

On creation the folder gets an .htaccess containing a deny rule and an index.php that outputs nothing, so directory listing and direct fetching are both blocked on Apache. On nginx the deny rule is not read, and blocking that path is a server configuration step. The installation page says so.

Every twin has one owner

Twins, sources, chunks, facts, assumptions, scenarios, runs and questions all carry the twin id, and every read or write resolves the twin first and checks that the caller owns it or holds an active seat from the owner. There is no cross account query anywhere in the application.

Plan gating on every route

The REST namespace has one member permission callback used by every route except the public boot call. It returns 401 if you are not signed in and 402 with the plan message if you have no plan. Route by route, the twin is then resolved and a 403 returned if it is not yours.

Seats are explicit rows

Sharing access is a row in a seats table with an owner, a user and a status. Access follows that row and is revoked by changing it. A colleague with a seat inherits the plan of the owner who granted it, which is why seat counts are part of the plan limits.

Deleting deletes

Deleting a source unlinks the file from disk and removes its chunks and its facts in the same operation. This is not a soft delete or a flag. The file handling page walks through it.

Access control

What each answer looks like when the check fails

SituationWhat the API returnsWhere the check lives
Not signed in401, sign in firstThe member permission callback, on every route but the public boot call
Signed in with no plan402, with the configured message about picking a planThe same callback, via the access resolver
Signed in, asking for somebody else's twin403, not yoursResolved per route before any read or write
Twin does not exist404Resolved per route
Over the twin limit for your plan403, naming the limitChecked on create, against the plan limits table
Over the file limit for that twin403, naming the limitChecked on upload
Over the monthly run or sweep allowanceRefused, with the allowance namedMetered per user per calendar month
Administrator of the siteTreated as enterprise and able to touch any twinBy design, because an administrator can read the database anyway

That last row is worth reading twice. Anybody with administrator access to the WordPress install can reach everything in it. That is true of every WordPress plugin, and it is why who has admin on the site is the real access control decision.

Keys and secrets

Where a language model key is kept, if there is one

A site wide key lives in the plugin settings option. A member key, if bring your own key is enabled, lives in that user's meta. Both are stored the way WordPress stores options and meta, which is in the database in plain text. The plugin does not add encryption on top, and saying otherwise would be inventing a control that is not there.

What follows from that is practical. Give the key the smallest scope and the lowest spend cap the provider offers. Rotate it on the provider side when somebody leaves. Do not put a production key into a staging site. And remember that with no key at all the product still works, because the simulation never calls a model.

Where a language model is used

Monthly revenuedocumentGross margindocumentPrice elasticitydefaultMonthly churndocumentCompetitor reactiondefaultContracted revenuedefaultLargest customer sharederivedCost per headderived

What is not claimed

This is new software written by one person. The following certifications and assurances do not exist, and no page on this site implies they do.

No SOC 2, no ISO 27001, no HIPAA, no FedRAMP

There has been no audit of any kind. There is no Type I and no Type II report, no statement of applicability, no auditor. If your procurement process requires one of these, this product does not pass it today, and the correct answer to that question in a questionnaire is no.

No penetration test yet

No third party has been paid to attack this software. There is no report to share and no remediation log, because there has been no test. The code follows the ordinary WordPress practices for this kind of plugin, which is not the same thing as having been tested by somebody trying to break it.

No bug bounty, no formal vulnerability disclosure programme

If you find something, the contact page reaches a person directly and a security report gets answered the same day. That is a commitment about responsiveness, not a programme with a policy and a payout table.

No encryption added by the plugin

Files sit on disk as ordinary files. Rows sit in MySQL as ordinary rows. Disk encryption, database encryption and transport security are all properties of the hosting you chose, not things this plugin provides. On managed hosting they are usually present. Check rather than assume.

No uptime commitment while this is new

There is no service level agreement and no published availability target. The terms say the same thing in the same words.

What would change all of this

In order of what is worth doing first: a penetration test by an outside firm with the report summarised on this page, including what was found; a written incident response process with a named responder and a notification window; then, only when there are customers whose procurement genuinely requires it, the long and expensive work of a SOC 2 Type II. Each of those will appear on the changelog with a date when it is real, and not before.

If you self host

Five things worth doing on the day you install it

Serve the site over HTTPS and nothing else

Force it at the server. A checkout on plain HTTP is refused by gateways and a login on plain HTTP is a gift to anybody on the same network.

Block the uploads folder at the web server

The plugin writes a deny rule that Apache honours. If you run nginx, add a location rule denying the counterfirm folder inside uploads, then try to fetch one of your own files over the web and confirm you cannot.

Count your administrators

Administrator access to the WordPress install is administrator access to every twin on it. That is the control that actually matters. Keep the list short and use two factor authentication on all of them.

Decide what happens on uninstall before you need to know

There is a setting that controls whether uninstalling removes the tables and the files. It ships switched off, so data survives an accidental deactivation. If you need removal to be complete, switch it on deliberately.

Back up the uploads folder, not just the database

The facts and the ledger are in MySQL, the original files are on disk. A database only backup restores a twin whose sources cannot be re-parsed.

Keep the language model key out of staging

Copying production to staging copies the options table. Clear the key in the copy, or use a separate key with a low cap for anything that is not production.

Read the file handling page before you upload anything

It walks the whole path: what is stored, what a chunk is, what the quote is for, what deletion actually removes, and the one condition under which anything leaves your server.

What happens to your filesAsk a specific question