Skip to content

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.

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 .env and git remote -v with 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.

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.

  1. Click the Vault icon.

    If the project has no vault yet, the Create the project Vault screen appears.

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

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

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

WhatWhereCommitted to the repo?
The key derived from the master passwordSystem keychain (Credential Manager, Keychain, Secret Service), one entry per repositoryNo
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”.

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.

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:

BadgeWhat 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 evaluatedTokens, 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.

Keeping the IP in one place and the password in another helps nobody, so New server groups everything you need to log in:

  1. Connection details.

    Host / IP, Port (22 by default) and User. These are not secrets: they live in plain text in the .meta file, because the agent needs them to build the command.

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

  3. Save server.

  4. Test connection.

    The app runs a real ssh and 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.

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 from mantis/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:

Ventana de terminal
git clone https://oauth2:«secreto:azure/pat/lord-denihol»@dev.azure.com/org/proj/_git/repo
ssh «secreto:servidor/prod/mi-server» ./deploy.sh

In the second case the reference becomes user@host and the password or key is injected on its own.

PlaceExpanded?
Command arguments and environment variablesYes
ssh, scp, sftp, rsync built by the appYes
MCP servers marked trusted in this projectYes, in their environment
Third-party MCP servers that are not markedNo
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.

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 runsWhat the model reads
echo $TOKEN«secreto:azure/pat/lord-denihol»
envthe token’s line, with the reference in its place
cat .envevery recognised value, substituted
git remote -vthe 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”

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.

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

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.

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.

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.

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.

  • 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 .meta means vault changes get reviewed like any other diff: you can see that someone added mantis/prod/contextcode on Tuesday, without seeing the value.

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.