Connect by JBRH Open Connect

Prompt injection arrives as ordinary business mail

Everything Connect reads — an enquiry, a WhatsApp message, a transcribed sentence, an uploaded file — is data. Containment is not a filter that recognises attacks; it is that every action worth attacking is gated where the text cannot reach it: autonomy modes, one send boundary that reports nothing as sent without provider evidence, an assistant whose rights are narrower than its user's, and a compliance check no model output clears.

Status
Available What this means
Audience
both, developer
Last verified
Product version
6.3.2

What the attack actually looks like#

It does not arrive labelled. A business agent reads text written by strangers all day, and the injection that matters is an ordinary enquiry with one extra sentence in it — in the signature block, inside a quoted reply, in an HTML comment, in the text layer of an attached quotation, or spoken aloud on a call where there is no formatting to hide in at all.

Where the text arrivesWhat it would try to obtainWhat stands in the way
Email body or quoted threadAn outbound message to a third partyoutbound.py is the one send boundary; autonomy decides whether any outbound goes without a person
Signature or HTML commentA changed standing instructiondirectives() reads the tag list only, so prose in a message body is never a directive
An uploaded documentFile reading turned into file writingReading and writing are separate tools; a change is a new version with provenance and audit
A spoken sentence on a callA price, discount or promisesafe_sales refuses commercial terms Knowledge does not support and escalates
A message from a suppressed addressIts own suppression liftedSuppression, unsubscribe and do-not-contact are checked in one place, and the Assistant cannot clear a do-not-contact entry at all

The boundary sits where the authority is, not where the text is#

Filtering incoming text for attack phrases is a losing position: the attacker writes the next sentence, and the defender has to have anticipated it. The workable position is that reading a sentence and being permitted to act on it are different things, decided in different places, by code that never sees the sentence.

  1. Decide what may happen without a person, per channel and per scope. autonomy.py holds four modes — off, draft_only, ask_before_send, autonomous — over four scopes, narrowest first: contact, endpoint, channel, workspace.

    Result A message that persuades the model to write something still meets a rule deciding whether anything leaves. Under ask_before_send the persuasive reply becomes a queue item with the message that prompted it sitting above it.

  2. Send through one boundary, and report nothing as sent without the provider's own acknowledgement.

    Result There is no second path a tool call can take, and no state where something is believed to have gone out because the model said so.

  3. Give the agent narrower rights than the person it works for. The Assistant has no pricing authority and cannot clear a do-not-contact entry.

    Result The most valuable outcomes for an attacker are not in the tool set at all, which is a stronger guarantee than a refusal the model has to remember to make.

  4. Check the model's picture of the screen against the database before trusting it.

    Result Context handed to the model is treated as a claim about state, not as state. What the Assistant acts on is what the record says.

Things received text is never allowed to do#

  • Become a standing instruction or a memory directive — those are tagged rows a person creates, and directives() reads only tags.
  • Clear a suppression, an unsubscribe or a do-not-contact entry.
  • Set a price, a discount, an SLA or a warranty that Knowledge does not support.
  • Move an outbound message past the autonomy rule in force for its channel and scope.
  • Reach another workspace: isolation is enforced three times, in the customer_safe allowlist, in the SQLAlchemy workspace kernel and again in PostgreSQL row-level security.
  • Supply a person's name. identity.PROTECTED_MARKERS, name_rejected and clean_name guard every door a name passes through, and a form of address is never a name.
  • Load remote content into a reader's browser. mail_render.py sanitises the HTML and remote images are proxied, so opening a message does not report the open back to its sender.
  • Expand on being opened: DOCTYPE and ENTITY declarations and zip bombs are refused by the file service rather than parsed.

Contained, merely detected, or unmeasured#

Two of these are genuinely different guarantees and the distinction is worth keeping. An action outside the tool set cannot be taken. A rule that lives in a prompt can be broken and then noticed.

Prevented
Anything requiring authority the agent does not hold: pricing, clearing a do-not-contact entry, sending past an autonomy rule, reading another workspace.
Detected after the fact
Commercial claims on the realtime voice path. The rule can only live in the instructions there, so price_spoken counts the lines the voice priced, through the same expression the carrier path checks each sentence with.
Unmeasured
Resistance rate. There is no injection benchmark, no red-team score and no measured attempt count for this system: UNKNOWN. What is documented is the shape of the boundary, which is testable by reading the tool set rather than by trusting a number.

Questions#

Could a customer email make Connect send something to a third party?

Not by asking. Any outbound crosses outbound.py under the autonomy rule for that channel and scope; under ask_before_send it becomes an item in Needs You with the message that prompted it, and under draft_only it is written and nothing is even asked. Where a workspace has chosen autonomous, the compliance check on suppression and do-not-contact still runs first.

Does Connect scan incoming messages for injection attempts?

The design does not rest on recognising them. Mail is sanitised for rendering and files are refused for structural attacks, but the sentence itself is treated as data and the actions it might want are gated elsewhere. A page claiming a detection rate for this would be claiming a measurement that does not exist.

Is an attachment safer than an email body?

Neither is trusted. The file service refuses DOCTYPE and ENTITY declarations and zip bombs before reading anything, then reads the formats it handles with the standard library. Once the text is out, it has exactly the standing of a message body: something to read, not something to obey.