
Security model
Written for the person reviewing it. Not for the buyer.
One organization reading another's numbers is the worst failure this platform could have. Everything below is arranged around preventing that, and the rules are structural, so breaking one should require deleting code rather than forgetting a check.
Three things a reviewer usually has to dig for are on this page rather than in an appendix: where the credential to your database lives, what happens when a document contains instructions, and what we would run differently if you asked. What we hold is on data handling, and who else touches it is on sub-processors.
The model is not the thing your reviewer is worried about. They are worried about who it is standing in for when it reads.
This page states the model, and then shows where each rule is enforced.
If you are compiling a vendor assessment, the enforcement column is the one worth reading twice. Every rule here is held by code rather than by process, which is the difference between a control and an intention.
The rules
Thirteen non-negotiables.
| # | Rule | How it is enforced |
|---|---|---|
| 1 | Only the guard binds a project | Not a tool argument, not a URL, not a token claim. One middleware resolves it from a live grant and stamps it on the request. |
| 2 | No scope, no connection | The component that reads holds no connection string. It asks for one on every open, so a tool with no bound project cannot open anything. |
| 3 | Anything touching data is request-scoped | A long-lived object could outlive one request and be handed to the next. None exists on that path. |
| 4 | Caches are keyed by project and re-assert on read | A cache hit that belongs to another project is discarded rather than returned. |
| 5 | Deny by default | Empty skill list grants nothing. Unknown profile expands to nothing. Unrecognised tool is assumed to need a database. |
| 6 | Customer organizations cannot cross organizations | Enforced where grants are created and again where they are used. |
| 7 | Token audiences are bound to the connector host | A token for one client's address cannot be replayed against another's. |
| 8 | Fail closed on every unknown | No email, no grant, no project, ambiguous project, unreachable control plane: all refuse. Outside development the service refuses to start at all without auth and a control plane. |
| 9 | Every narrowing is an intersection | Four gates, each a pure function that can only remove. No layer can grant what another refused. |
| 10 | A client's contributions run only on its own project | Tools and everything else a pack contributes, checked at creation and again at dispatch. |
| 11 | Refuse, never guess, when a target is ambiguous | Two candidates and no default is a refusal with the list. |
| 12 | Tenant-authored text is data, never instruction | Arrives after every decision and after the read, capped, labelled untrusted, and unable to reach another project's session. |
| 13 | A refusal never teaches the caller about another client | Not whether a project exists, not who owns a tool, not another organization's identifiers. |
These are covered by a test suite that runs offline with no database and no network, plus a set against a real SQL Server. A change that weakens one is treated as a security change and gets an adversarial review rather than a normal one.
Isolation
Isolation is the grant. Not the deployment.
One service serves every client, and a request reaches exactly the one project the caller holds a live grant on. That is a deliberate choice with a real trade-off, so here is the trade-off.
| Target | Held by this person | Outcome | What the caller is told |
|---|---|---|---|
| planning-test | a live grant | bound | the environment it answered from, by name |
| planning-prod | no grant | refused | no_grant_on_project |
| anything owned by another organization | not reachable at all | refused | no_grant_on_project |
Why not one deployment per client
It was seriously considered and it stays available. It was not chosen because N deployments per engine change is exactly the per-client maintenance cost that makes small clients uneconomic, and unmaintained instances are their own security problem.
If your review requires process isolation
The code supports it unchanged: a second instance of the same service with only your projects reachable. Ask, and it is a deployment decision rather than a redesign. We would rather have that conversation than pretend the shared model suits everybody.
Secrets
The credential that reaches your database.
Usually the third question in a review and rarely answered on a website, so it is answered here. Every project has exactly one connection secret, held in a managed key vault, and nothing about it is shared with any other project.
| Question | Answer |
|---|---|
| Where does it livethe secret itself | A managed key vault, one secret per project, fetched by the running service under a managed identity. Not in a configuration file, not in an environment variable on the app, and not in the repository. |
| Who can read iton our side | The connector process, at the moment it opens a connection for a bound project. The administration console is a separate assembly that references the platform alone: it has no data-plane role on the vault and no path to a client database at all. That is a property of the project file and it is asserted by a test, not a convention somebody keeps. |
| How is it scopedat the moment of a read | The component that reads holds no connection string of its own. It asks for the project in scope on every open, so a tool with no bound project cannot open any connection at all. That is rule 2 above, and it is the reason a missing grant fails as a refusal rather than as a query against the wrong database. |
| Rotating itand revoking us | A vault operation on your schedule, effective on the next connection. If you want us out entirely, remove the credential your side: there is no cached copy to expire and no standing access to withdraw separately. |
| In transitand at rest | TLS on every hop that leaves a machine, including the database connection. The control plane is an encrypted managed database. Neither of these is unusual and neither is a differentiator, which is why they are a row rather than a section. |
What this does not cover. A credential we hold for a system that cannot tell your people apart is a different question, and it is answered per system on whose permissions apply rather than generalised here.
Prompt injection
What happens when the data contains instructions.
The question every reviewer now asks about anything with a model in the room, and the honest answer starts with what we cannot do: we cannot stop a model being persuaded by text it reads. Nobody can, and a vendor claiming otherwise is describing a filter rather than a guarantee.
What is structural here is the ordering. Every decision that could widen access is made before any text is read, so persuasive text arrives too late to change what may be reached.
-
01
The tool list is fixed before anything is read
The grant and the four narrowing gates settle which tools exist for this person on this environment before a single row or document comes back. A document cannot talk its way into a tool she does not hold, because the list was closed before it was opened.
-
02
Your own written rules arrive last, and cannot widen anything
Knowledge is approved by a named person, and it is carried and labelled as untrusted data rather than as instruction. It arrives after every access decision and after the read, it is capped and sanitised, and four independent checks stop it reaching another project's session.
-
03
So the worst case is a wrong answer, not a wider one
That is a real cost and we are not dressing it up: a misleading document can produce a misleading answer, and this platform does not make the model right. It cannot produce an answer built from data the person was never permitted to reach, and the attempt is recorded either way.
-
04
We deliberately do not filter for injection phrasing
It is unwinnable, and a filter that appears to work is worse than none, because everything downstream then assumes the text is safe. We would rather the ordering carry the guarantee and the text be treated as hostile throughout.
This is isolation rule 12 above, stated the way a reviewer asks it.
Identity
Who you are, and what may reach what, are answered by different systems.
| who is this | verified work account |
| are they still here | your joiner and leaver process |
| the password | never leaves you |
| groups and roles | stay in your directory |
| what may they reach | one grant, per project |
| what did they reach | the audit trail |
| directory held | none |
| revoking access | effective on the next call |
Keeping that split is what makes revocation a database edit that takes effect on the next call, and it means we hold no directory, no passwords and no group memberships. The broker that federates sign-in holds OAuth machinery only: it knows nothing about clients, projects or entitlements.
Reporting
If you find something, tell us.
The fastest route to a fix is a direct one, and a reviewer who has found something real should not have to hunt for an address.
Security reports go to info@proceptio.com with security in the subject line, and the reporting instructions are on the contact page. Every report is acknowledged within one business day and the reporter is told what happened to it.