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.

#RuleHow it is enforced
1Only the guard binds a projectNot a tool argument, not a URL, not a token claim. One middleware resolves it from a live grant and stamps it on the request.
2No scope, no connectionThe component that reads holds no connection string. It asks for one on every open, so a tool with no bound project cannot open anything.
3Anything touching data is request-scopedA long-lived object could outlive one request and be handed to the next. None exists on that path.
4Caches are keyed by project and re-assert on readA cache hit that belongs to another project is discarded rather than returned.
5Deny by defaultEmpty skill list grants nothing. Unknown profile expands to nothing. Unrecognised tool is assumed to need a database.
6Customer organizations cannot cross organizationsEnforced where grants are created and again where they are used.
7Token audiences are bound to the connector hostA token for one client's address cannot be replayed against another's.
8Fail closed on every unknownNo 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.
9Every narrowing is an intersectionFour gates, each a pure function that can only remove. No layer can grant what another refused.
10A client's contributions run only on its own projectTools and everything else a pack contributes, checked at creation and again at dispatch.
11Refuse, never guess, when a target is ambiguousTwo candidates and no default is a refusal with the list.
12Tenant-authored text is data, never instructionArrives after every decision and after the read, capped, labelled untrusted, and unable to reach another project's session.
13A refusal never teaches the caller about another clientNot 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.

The last two rows say the same thing, and that is rule 13 doing its work. A refusal that said "that project belongs to someone else" would confirm the project exists and who has it. So the answer for a project you cannot reach is the answer for a project that was never there.

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.

QuestionAnswer
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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

Neither side can answer the other's question. The boundary in the middle is the design rather than a diagram of it: identity on one side, permission on the other, and one verified email crossing between them.

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.

Ready?

Where our own enforcement is the only one standing. Let's put it to work.

The next page publishes, per connected system, exactly whose permissions decide what an assistant may reach, so your reviewer can check the answer rather than take it on trust.

Why this exists: useful assistants connect an answer to an action, and an action to a completed job.