Key vault
An agent doing real work needs credentials: cloning a private repository, connecting an MCP server, logging into a machine and deploying. Until now the only way to hand them over was to paste them into the chat, which means the token travels to the AI provider and stays in your history.
The Vault breaks that deal. Secrets are stored encrypted inside the project and the agent never works with the value, only with a reference: «secreto:azure/pat/lord-denihol». The app swaps that reference for the real value as it launches the process, already out of the model’s reach.
The promise
Section titled “The promise”A key is never chat text, prompt text, or tool output.
In practice that is three guarantees:
- The agent asks for permission, not for the value. When it needs a credential it asks whether you authorise using
mantis/prod/contextcode; it never receives the string. - Nothing that comes back from a tool gives it away. Whatever a command prints, whatever a file read or an MCP server returns, goes through a filter before it reaches the model: the value is replaced by its reference. That is what covers the classic
echo $TOKEN,env,cat .envandgit remote -vwith the token in the URL. - The value only expands where it may be needed. Commands, MCP servers marked as trusted, and
ssh/scp. Never when writing files.
It is an architectural guarantee, not a matter of discipline: decryption happens in the app’s core (Rust), and neither the interface nor the model ever sees the value.
Creating the vault
Section titled “Creating the vault”The Vault icon sits in the sidebar, right next to Skills & MCP. The vault is per project, so open the folder you work in first.
-
Click the Vault icon.
If the project has no vault yet, the Create the project Vault screen appears.
-
Choose the master password.
You type it twice in a secure field (black dots, outside the chat) and it must be at least 8 characters: that key encrypts every secret in the project.
-
Store it in your password manager.
The master password cannot be recovered. The app says so when you create the vault, and the only backup is the emergency export.
-
Click Create vault.
The vault is created and open. The panel header always shows whether it is Open or Closed, which source it comes from, and whether the master password is held in memory (master in memory).
Where each thing lives
Section titled “Where each thing lives”| What | Where | Committed to the repo? |
|---|---|---|
| The key derived from the master password | System keychain (Credential Manager, Keychain, Secret Service), one entry per repository | No |
| The encrypted values | .context/secretos/equipo.enc (AES-256-GCM) | Yes, it is committed |
| The metadata | .context/secretos/equipo.enc.meta (plain text: id, type, description, service, user, dates, strength) | Yes |
The .meta file never holds values. It exists for two reasons: listing the panel without opening the vault, and keeping Git diffs readable (you can see who added or rotated a secret without seeing anything secret).
Deriving the master password (Argon2id) happens once per session; the derived key stays in memory and is wiped when the app closes. If the system keychain is unavailable, the master password lives only in that session and the panel says so with “master in memory”.
Saving a secret
Section titled “Saving a secret”With the vault open, the panel’s toolbar offers New secret, New server, Import .env and Export.
In New secret you fill in the id in three parts — system (azure-devops, mantis…), environment (prod, pat…) and user — and the panel shows you the resulting id: azure-devops/pat/lord-denihol. That id is the name the agent will use when asking for authorisation, so make it readable.
The types are password, token, SSH key, OAuth, environment variable and server.
The value is not typed into an ordinary field: you click Choose secure value… and the secure field opens — the app’s own box, value hidden, no autocomplete, no trip through the chat. What you type goes straight into the encrypted core.
The strength badge
Section titled “The strength badge”Passwords are evaluated as you save them (including a server’s password). It is not a character counter: it measures how guessable the key is — dictionaries, proper names, words from your company or project, dates, keyboard runs, substitutions like @ for a — and the result shows in the list:
| Badge | What it means |
|---|---|
| Very weak (red) | Broken in minutes. You can still save it, but the panel will not pretend otherwise. |
| Weak (orange) | Does not hold up for long. Change it when you can. |
| Strong (green) | No recognisable patterns and long enough. |
| Not evaluated | Tokens, SSH keys and the like: strength is not measured there (the service generates them, and what matters is the format and the expiry). |
Context counts: if the service is a public URL, or the user has high privileges (sa, administrator), the acceptable minimum goes up and a middling password raises a rotation warning.
A real example: Ds910913@ looks respectable — uppercase, digits, a symbol — and comes out orange or red. It is a pair of initials, a date and a trailing symbol: nine characters in a shape every dictionary knows by heart. The app does not forbid you from saving it; it tells you the truth about it.
Server entries
Section titled “Server entries”Keeping the IP in one place and the password in another helps nobody, so New server groups everything you need to log in:
-
Connection details.
Host / IP, Port (22 by default) and User. These are not secrets: they live in plain text in the
.metafile, because the agent needs them to build the command. -
Authentication.
Password, SSH key or OS agent. The password, the private key and its passphrase all go through the secure field; the key is pasted into a box that never shows it in full.
-
Save server.
-
Test connection.
The app runs a real
sshand reports only Connection successful or Could not connect with the reason (authentication rejected, host unreachable, timed out). The private key goes through a temporary file with restricted permissions that is deleted when the process ends.
If that machine is already in your remote connections panel, there is no need to type anything twice: from the connection picker, Save to the Vault opens this form with the host, port and user already filled in. Only the secret is missing, and it comes in through the secure field.
How the agent uses it
Section titled “How the agent uses it”The agent can list the vault (ids and metadata, never values) and ask to use a secret. When it does, an Approval required card reaches you with the id and the purpose it wrote itself (“to clone the organisation’s repository”), and three ways out:
- Allow — good for this one use.
- Always allow — creates a project rule. It does not hand over the whole vault: it is remembered by system pattern (
mantis/*, derived frommantis/prod/contextcode), it expires after 30 days, and you can revoke it from the panel whenever you like. The card itself tells you what you are about to grant before you click. - Deny — the agent goes without the credential and has to carry on without it.
If you authorise it, the value is decrypted inside the app and injected into the process, not into the text. The agent writes the reference into the command and the app substitutes it at launch:
git clone https://oauth2:«secreto:azure/pat/lord-denihol»@dev.azure.com/org/proj/_git/repossh «secreto:servidor/prod/mi-server» ./deploy.shIn the second case the reference becomes user@host and the password or key is injected on its own.
Where it expands and where it does not
Section titled “Where it expands and where it does not”| Place | Expanded? |
|---|---|
| Command arguments and environment variables | Yes |
ssh, scp, sftp, rsync built by the app | Yes |
| MCP servers marked trusted in this project | Yes, in their environment |
| Third-party MCP servers that are not marked | No |
| File writes (Write/Edit) | Never, not even into a .env |
That last row is deliberate: if the agent could “hand” the key to a file in the repository, it could then read it back itself and the whole promise would collapse.
Redacting tool output
Section titled “Redacting tool output”Injecting through an environment variable is not enough, because the value comes back in the output. Everything an agent receives from a tool — console output and errors, the contents of files it reads, MCP results — goes through the filter before reaching the model:
| The agent runs | What the model reads |
|---|---|
echo $TOKEN | «secreto:azure/pat/lord-denihol» |
env | the token’s line, with the reference in its place |
cat .env | every recognised value, substituted |
git remote -v | the URL with the reference where the token was |
The base64, URL-encoded and JSON-escaped forms of the value are redacted too, because that is how servers return them (Authorization: Basic …, https://user:pass@…, {"token":"…"}).
Two details matter:
- The filter only covers the secrets in use in this session (the ones you authorised this turn, or the ones an active rule covers), not the whole vault at all times.
- Values shorter than 8 characters are not redacted. The app warns you when you save one.
While authorised secrets exist, the composer shows an indicator: “N secrets in use this turn”, which opens the panel in one click.
When a secret shows up where it should not
Section titled “When a secret shows up where it should not”In the chat, as you type
Section titled “In the chat, as you type”The composer analyses what you type, before it is sent. It recognises Azure DevOps and GitHub tokens, OpenAI, Anthropic and Google keys, AWS keys, JWTs, PEM private keys and the usual password= / token:; and beyond the patterns it measures entropy to catch tokens that match no known shape. Git hashes, UUIDs and placeholders such as Password=<password> are not flagged.
On a match a banner appears, and it does not block sending:
This looks like a credential. It will be sent to the AI provider and kept in the history.
- Save to vault and send reference (the default): the credential goes into the vault and the text that travels to the provider carries the reference instead. Your own message shows the redacted version.
- Send anyway: it goes out as it is, on your own responsibility.
The X only dismisses the notice for that text.
In a file you open or save
Section titled “In a file you open or save”When you open a file, the editor scans its first lines and marks the ones that look like they hold secrets. If there are any, a banner appears:
This file contains N possible secret(s).
Save to the vault moves them into the encrypted vault… and the file is left untouched. The app confirms it: “The file was NOT modified: «secreto:id» references are never written to files.” That is not an oversight; it is the rule above.
The same offer comes back when you save a tab containing secrets, once per file and session.
The panel, tab by tab
Section titled “The panel, tab by tab”Secrets
Section titled “Secrets”The list shows Id, Type, Strength, Expires and Last used, with filters by system and by environment. Open one and you get:
- Show — asks for the master password again (Confirm your identity) and reveals the value, which hides itself after 30 seconds.
- Copy — copies without the value passing through the interface, and warns you: “Copied. The clipboard will clear itself in 30 seconds.”
- Validate — returns the strength (0-4) with its reasons, and if the secret has ever gone through a chat in the clear it says so plainly: treat it as compromised and rotate it soon.
- Delete — removes it from the encrypted vault, with a confirmation; the agent can no longer use it.
- Rotate — it is there, disabled, with the note “Guided rotation: coming soon”.
Permissions
Section titled “Permissions”Every Always allow you granted, with its pattern, its scope and its expiry; expired ones are marked as such and authorise nothing. Revoke puts things back where they started: the agent will ask for authorisation every time again.
Audit log
Section titled “Audit log”A local table with the date, the secret, who authorised it and the purpose of each use, plus deletions. It lives in the app’s encrypted database and never stores values: it is there to tell you what was used and why, not to recover anything.
Importing a .env
Section titled “Importing a .env”You paste the file’s contents, the wizard recognises the KEY=value pairs, lists them, and you untick the ones that should stay out. It is the fast lane for a project that already kept its keys in plain text.
Emergency export
Section titled “Emergency export”Generates an encrypted file with the whole vault, protected by its own passphrase (not the master password: you can use a different one). It is your only backup if you lose the master password, so keep it off this machine. It is written into the app’s data folder, never inside the repository.
Working as a team
Section titled “Working as a team”- One master password per repository, shared through the team’s password manager. Context Code never distributes master passwords and never sends them anywhere.
- When you clone a repository that already carries
equipo.enc, the panel detects it and asks for the master password; once open, it is tied to your keychain and is not requested again every session. - Everyone uses their own account on the services. The service account (
ContextCode, or whatever you call it) is for the agents. That is what makes the audit log worth reading. - The plain-text
.metameans vault changes get reviewed like any other diff: you can see that someone addedmantis/prod/contextcodeon Tuesday, without seeing the value.
Coming next
Section titled “Coming next”Three pieces are designed but not in the app yet:
- Guided rotation: open the service’s own guide (Azure DevOps, GitHub, Google, Mantis), type the new key into the secure field and replace the old one in a single step. The Rotate button is already in the detail view, disabled.
- Manual vault importer: bring in, in one go, the key sheet the team used to keep by hand.
- Member offboarding wizard: rotate the master password and walk through the secrets that need changing, one after another.