Security answers
This page answers the questions a security reviewer asks about a Managoat instance. Each answer names the mechanism, and each limit sits next to the answer it limits. The concept pages linked from each answer have the detail.
Where credentials live, and who can read them
An Environment holds a machine's variables, and
a Vault holds a small set of overrides for a run. Both
keep secrets apart from plain env_vars. Managoat encrypts each secret with
AES-256-GCM under a random key of the owner's account. It wraps that key under
the instance's MASTER_SECRETS_KEY, which is never stored in the database.
Read Where a secret comes from for the whole chain.
Secret values are write-only. A list returns keys and timestamps, and no endpoint returns a value.
The limits.
- Plain
env_varsare stored and returned as written. Put a credential insecrets, never inenv_vars. - An operator who holds both the master key and the database can decrypt any account's secrets. Encryption at rest protects a stolen database or backup. It does not protect you from the person who runs the instance.
When an account is deleted, its key goes with it, so what stays in the database cannot be decrypted. That does not reach a backup taken before the deletion. That backup holds both the ciphertext and the wrapped key until your backup retention removes it. Erase an account has the detail.
Whether the model sees a secret
No mechanism puts a secret into the prompt, the system prompt or the model's
context. Secrets merge into the sandbox's process environment when the sandbox
starts. They reach an agent's MCP server configuration through ${VAR}
substitution. That substitution happens on the server, and applies only to
mcp_servers.
The limit. The code in the sandbox can read its own environment, and that is why the value is there. If an agent prints a value, the value is now in its context. The next answer covers what happens to that output.
What ends up in the conversation's log
Managoat stores everything a sandbox writes to stdout or stderr as the
conversation's log events, and streams it live. Before it stores or streams a
line, it replaces each registered secret value with [REDACTED]. Managoat
registers these values for every conversation:
- the environment and vault secrets;
- the inference credential;
- the callback token;
- the values the broker holds.
Managoat also catches a value that output splits across two chunks, and the JSON-escaped form of each value. Hop 5 describes the scrubber.
The limits.
- The match is on bytes. A value the agent re-encodes in another way (base64 or URL encoding, for example) is not caught.
- A value shorter than 8 bytes is not redacted. That floor keeps short
non-secrets like
trueor a port number readable. Do not choose a short password.
Whether one account can reach another's data
Every user-facing query is scoped by the owner's account. The few internal
reads that are not scoped carry an _unsafe_ prefix, and a Credo check
requires an ownership note at each call. One search finds them all. A
cross-tenant isolation suite in CI asserts that another account's resources
answer 404.
A sandbox belongs to one conversation, and Managoat destroys it when that conversation ends, unless the caller launched conversations into one workspace, whose machine they share. No conversation can attach to another's sandbox by its id. Read One machine per conversation.
Whether one conversation can reach another's
Each sandbox gets its own API key, MANAGOAT_TOKEN. It reaches the
conversation it belongs to and the conversations that one started, at any
depth. Any other conversation of the account, the one that started it and
its other children included, answers 404 as another account's would. The key
cannot list or search the account's conversations or follow the account
event stream. Outside its tree it reads only agents, environments, vaults and
the names of their secrets, the inputs to start a conversation. Every other
route refuses it, including any route a later release adds, unless that
route is added to its allowlist. Read
What a sandbox credential can do.
The limits.
- An agent can still read every agent's configuration and the names of every secret, and start conversations of any agent, which spends your inference credentials.
- A full-scope key that you put in an environment or vault reaches the whole account, from any sandbox that gets it.
sandbox_api_access: "none"gives a sandbox no key at all.- None of this separates conversations in one workspace. They share one disk and one user, so each can read the others' files, config homes, process environments and keys, and plant hooks or instructions the others then follow. What one sandbox key reaches there is in effect what all of theirs reach. A conversation launched without a workspace keeps every guarantee above. A sandbox key cannot create or use a workspace, so an agent cannot put itself beside another conversation.
What is recorded when something changes, and for how long
Managoat writes the audit event where the change happens, not where the request
arrived. So the console, the API and a background worker all follow one rule.
An update records the names of the fields that changed, never their values. A
secret event records the secret's key. An inference credential event records
the provider. Read your own trail at GET /api/audit. Filter it by action
prefix, by resource type, or by a since and until window.
A daily job deletes rows older than these windows:
| What | Kept |
|---|---|
| Conversation log events | 90 days |
| Audit events | 365 days |
| Usage events | 400 days |
| Revoked API keys | 30 days |
Admin actions (admin.* audit events) |
No limit. The record of what admins did is never pruned. |
The limit. The windows are not environment variables. To change one, set
config :fountain, :retention_days in your own build. A value of nil turns
pruning off for that table.
Whether an agent can do something nobody approved
Set an agent's permission policy to ask. The agent's tool call then waits for
a human before it runs. If nobody answers in 5 minutes, Managoat refuses it.
Read Before a tool runs.
A stronger pattern is to put the final gate where the agent cannot reach it, such as a protected branch that needs another account's review. Then refusing the change is somebody else's job, not the model's.
The limits.
askis not the default.- It does not cover every action. The runtime decides which tool calls it asks Managoat about, and a policy answers only those.
Whether an agent can start agents without end
An agent can start another agent, and the one it starts can do the same from
inside its own sandbox. Managoat counts how deep each chain goes and refuses a
start past CONVERSATION_MAX_DEPTH (default 5) with
422 conversation_depth_exceeded, naming the depth. The count follows the
sandbox's own token, so an agent cannot reset it by leaving out the parent
header. Read Spawn depth.
The limits.
- Nothing caps fan-out. An agent can start many conversations one level down,
and each of those can do the same until the ceiling. The fleet ceiling
(
SANDBOX_FLEET_CEILING) bounds how many sandboxes run at once across the deployment. - A sandbox that holds the owner's own full-scope key, from an environment or a vault, and starts a conversation with it without the parent header is counted as a start from outside, at depth 0.
Whether a credential can stay out of the sandbox
The egress broker keeps a bound credential out of the sandbox. The sandbox holds a placeholder, and the broker attaches the real value on the way out.
The limits.
- It applies to every conversation on every deployment: Managoat does not boot without it. It covers only secrets that have a binding. The built-in bindings cover the GitHub token and the inference keys.
- A secret with no binding still enters the sandbox as plain text.
Read Bindings.
Whether any of this can stay inside our network
Partly. On an instance of your own, the control plane, the transcripts, the audit trail and the encrypted secrets live in your deployment and your Postgres.
The limit. Sandboxes run at a hosted provider, Sprites or E2B. Managoat no longer runs agents on hardware you own. So the repositories and credentials a run uses reach that provider. Read Self-host Managoat.
What it does not have
- No organizations and no seats. An account is one person. A team either shares one account or runs an instance of its own.
- No SAML or OIDC single sign-on. Sign-in is a password, or the optional GitHub login.
- No independent penetration test. The security work is what is in the repository, and you can read all of it.