Authentication
If we already know the user may use a tool (SAP, HubSpot, Google, …), we ask HashiCorp Vault for credentials, Vault returns them, and our code connects to the tool and gathers the data. Vault never returns HubSpot/SAP business records. The agent never sees the secrets.
Nono and pi-authentication sit beside that loop. They do not fetch Vault secrets.
Longer write-up: HashiCorp Vault proposal. Sandbox detail: Agent sandboxing.
Implemented as this Vault path (VAULT_OAUTH_BACKEND=hashicorp). Google and HubSpot Connect on Tools remain as a manual path; chat JIT is the primary path via needs_connection.
Three kinds of “auth” (do not mix them)
| Name | Proves | Held by | Used for |
|---|---|---|---|
| User login | This browser is Alice | Browser cookie | Opening the app, sending a chat turn |
Pi-authentication (PI_USER_TOKEN) | This sandbox is Alice’s box, not Bob’s | Landlocked pi | Django internal APIs that must run as Alice (Memory, reports, the Enterprise Database) |
| Vault vendor secrets | How to log into SAP / HubSpot / Google | Django only | Our code connecting to the vendor |
| Nono | Not identity | Supervisor around pi | Lock the box (Landlock) and, today, inject a phantom vendor key so pi never sees the real token |
PI_USER_TOKEN is signed with PI_USER_TOKEN_SECRET (see chat/pi_user_token.py,
chat/pi_sandbox_auth.py). It names one user id. The sandbox never gets
MCP_INTERNAL_SECRET (that would let it act as any user).
Role of nono
Nono is the in-box supervisor, not a login system and not a Vault client.
Docker container
nono (reads session.json, Landlock, optional vendor proxy)
└── pi (the CLI / agent — untrusted)
| Nono does | Nono does not |
|---|---|
Wrap pi (nono -s run --profile … -- pi …) | Authenticate the human |
| Landlock: which files, env vars, and hosts pi may touch | Call HashiCorp Vault |
Today: proxy Gmail/HubSpot — child sends phantom HUBSPOT_API_KEY, nono injects the real Bearer | Hold Vault AppRole, client_secret, SAP password |
| Enforce “this session may talk to HubSpot paths X Y Z” | Decide “is Alice allowed SAP?” (that is Django) |
In the target Vault loop, SAP (and ideally HubSpot/Google too) are called by Django. Nono then only sandboxes pi. Pi asks Django for data; Django already has the vendor result. Pi does not present Vault credentials to SAP.
Role of pi-authentication
Pi-authentication is how the box proves which user it is, once Django has already logged Alice in and started her sandbox.
| Pi-authentication is | Pi-authentication is not |
|---|---|
HMAC token: user_id + expiry + signature | A Vault token |
| Scoped to one user | Shared org secret (MCP_INTERNAL_SECRET) |
| How Django's internal APIs know which Alice | How we log into SAP |
| Safe to put in the sandbox | A license to read Bob’s HubSpot |
The vendor-data loop (Vault)
User allowed? → Django asks Vault for credentials → Vault returns secrets
→ our code connects to the vendor → data comes back to the chat
| Step | Who |
|---|---|
| 1. Login | Django session |
| 2. User asks for data | Chat → Django starts/uses the pi sandbox |
| 3. Access check | Django policy. If no, stop. No Vault. |
| 4. Credentials | Django ← Vault |
| 5. Connect | Django → SAP / HubSpot / Google |
| 6. Data | Vendor → Django → chat. Pi may display the data. Nono never saw the secret. |
Sequence (all three together)
Pi can still call Django's /api/internal/… routes with PI_USER_TOKEN in the same session.
That path never uses Vault. Gmail, Sheets, and HubSpot do not use it either: those go through
nono's phantom-key proxy. There is no MCP server in the sandbox.
Decision flow
What Vault returns vs what the vendor returns
- Vault: passwords, client id/secret, tokens, certs → Django.
- Vendor: records → Django.
- Pi-authentication: “this box is Alice” → Django internal APIs.
- Nono: containment (+ today’s Gmail/HubSpot phantom-key proxy).
- User / model: business data only.
What never happens
- Calling Vault before we know the user is allowed
- Vault queried for “SAP customers”
- Vault secrets in pi, in chat, or in nono’s child env
- Nono or pi calling Vault
PI_USER_TOKENused as a SAP/HubSpot passwordMCP_INTERNAL_SECRETgiven to the sandbox
FAQ
“The credentials are shared inside the pi session.”
Vault credentials stay in Django. Pi may see business data and PI_USER_TOKEN (who the
box is). Those are not SAP passwords.
Today’s Gmail/HubSpot path is weaker: a short-lived access token sits on the nono supervisor
in the same container; Landlocked pi only has a phantom *_API_KEY. SAP should not copy that.
“The pi CLI is treated as a separate service.”
Pi is not a microservice and not a Vault client. It is the process nono starts in the sandbox.
Separate services: Django, Vault, Postgres. Pi-authentication only answers “which user does
this box represent?”