Agent profiles configure secure, credential-restricted environments for running CLI tools and agents. Instead of exposing actual API keys to child processes, Stashbase provides short-lived placeholders. An HTTP proxy enforces access policies—deciding which hosts, HTTP methods, and URL paths may receive each credential.
Early access — local exposure reduction, not hostile-agent isolation. Agent profiles keep granted secrets out of child processes and inject them only through the HTTP proxy. They do not prevent a malicious same-user process from accessing broader Stashbase credentials or bypassing this workflow.
Local Agent Proxy sessions run a short-lived proxy on your machine. Secrets are resolved in an ephemeral process, and the child sees only opaque placeholders. Useful for local development and testing.
Remote Agent Proxy sessions (--remote flag) resolve secrets on Stashbase infrastructure (requires active plan). The CLI creates a scoped session token that exists only in memory and is revoked when the child exits. Personal credentials are fully private—never fetched, printed, or stored by the CLI. Perfect for CI/CD and shared environments.
Create a profile template:
stashbase agent init my-profileThis creates .stashbase/agents/my-profile.toml with placeholder sections. Edit it to configure your secrets and policies, then run:
stashbase agent run --profile my-profile -- <your-command>For a remote session:
stashbase agent run --remote --profile my-profile -- <your-command># .stashbase/agents/basic.toml
egress_hosts = ["api.github.com", "registry.npmjs.org"]
[secrets]
project = "my-project"
environment = "development"
[secrets.GITHUB_TOKEN]
env = "GH_TOKEN"
[[secrets.GITHUB_TOKEN.rules]]
effect = "allow"
hosts = ["api.github.com"]
methods = ["GET"]
paths = ["/user", "/repos/*"]The child process receives GH_TOKEN as a placeholder. When it makes a matching HTTP request (GET to /user or /repos/* on api.github.com), the proxy injects the real token. Any unmatched request is blocked.
Restrict what the child can read or write:
[filesystem]
deny_read = ["~/.ssh", "~/.aws"]
deny_write = ["~/.git"]Paths use explicit prefixes: ~ for home, relative paths for the current directory. A denied directory covers everything under it.
Entries can also be patterns:
[filesystem]
deny_read = [
"**/.env*", # glob: any .env file at any depth below the current directory
"~/.config/*/token", # `*` and `?` match within one path segment
"re:^~/.*\\.pem$", # regex, prefixed `re:`, matched against the absolute path
]- An entry containing
*or?is a glob.**matches any number of directories, and[a-z]/[!.]character classes work inside globs. Brackets on their own don't make an entry a glob, so a plain path like~/notes/[old]keeps its literal meaning. - An entry prefixed
re:is a regular expression. It must start with^/or^~/, and it may only use POSIX constructs: write[0-9]instead of\d, and group alternatives (^/(a|b)/...) instead of using a top-level|. path:forces a literal path, e.g.path:/data/what?.- Like a plain path, a pattern that matches a directory denies everything under it. This holds for regexes ending in
$too:re:^/srv/secrets$also denies/srv/secrets/key. - On macOS, the native backend matches patterns every time a file is accessed, so files the agent creates during the run are covered. Linux and the Docker backend expand patterns once, when the run starts; files created later aren't covered.
stashbase agent validatewarns about this, andstashbase agent explainlists each pattern with how it's matched. - Patterns aren't supported on Windows; validation fails.
By default, enforcement uses the platform-native mechanism (Seatbelt on macOS, systemd-run/bubblewrap on Linux). If Docker is available, prefer [sandbox] backend = "docker" instead — it's meaningfully stronger (allow-list filesystem access, a real network-layer firewall, and it works on Windows too, unlike the native backend).
See Sandboxing for the full picture: both backends, how the Docker backend's network firewall is enforced, custom images, git identity forwarding, login persistence, and Codex/Claude Code OAuth quirks.
Run the agent in its own git worktree (branch stashbase/<name>, inside the repo at .stashbase/worktrees/<name>) instead of your checkout, so several agents can work in one repository in parallel:
[workspace]
worktree = trueOr per run: stashbase agent run --profile coding --worktree -- claude (--worktree=false skips it when the profile turns it on). Works with the native backend and, on macOS/Linux, the Docker backend. Review and merge the agent's work from your checkout with stashbase agent worktrees list, merge <name> and clean — see Worktrees.
Set non-sensitive configuration for the agent with [config_env]. Values are stored in plaintext in the profile, and both sandbox backends apply them. The Docker backend otherwise starts the container with an empty environment. A value can be a string, a number or a boolean (PORT = 3000 and DEBUG = true work without quotes), and the agent always receives it as a string.
[config_env]
NODE_ENV = "development"
RUST_LOG = "info"
DATABASE_URL = "postgres://app@localhost:5432/app"-
[config_env]values take precedence over variables inherited from your shell. They can't replace anything the sandbox sets: proxy and CA variables,HOME,TMPDIR,FORCE_COLORand anything starting withSTASHBASE_are rejected.PATH,LD_*andDYLD_*are rejected too, because they control which programs and libraries start the sandbox. -
Each
NAME=valuecan be up to 64 KiB, and the whole table up to 256 KiB. That keeps[config_env]well within what the operating system accepts when starting the agent. Values can't contain NUL bytes. Keep larger content in a file in the workspace. -
A name that a
[secrets]or[personal_credentials]binding already provides (itsenvname, or a renamedfromsource) is rejected. -
Credentials don't belong here. Stashbase checks every value: a credential-like name (
*TOKEN*,*PASSWORD*,*API_KEY*, …), a known token format, a URL containing a password, or a long random-looking string triggers a warning inagent validate.agent runthen asks for confirmation before starting, and fails when it can't ask (non-interactive or--silent). Flagged values are never printed:agent profiles showdisplays them as<hidden: looks like a credential>. -
If a flagged value really isn't secret, acknowledge it so the check skips it:
[config_env] BUILD_HASH = { value = "9f86d081884c7d659a2feaa0c55ad015", not_secret = true }
To set or override a variable for one run without editing the profile, pass --config-env NAME=VALUE (repeatable):
stashbase agent run --profile coding --config-env NODE_ENV=test --config-env PORT=4000 -- claudeFlag values go through the same checks as the profile's, including the credential check: a value that looks like a credential asks for confirmation, or fails the run when it can't ask.
The audit log's session_started event lists the names of the variables set for the run (config_env_names), never their values.
A complete example profile is in examples/config-env.toml. It includes commented-out lines that show the warning and the validation errors.
By default, the proxy denies all connections. Allow specific destinations:
egress_hosts = ["api.github.com", "registry.npmjs.org"]Use "*" for unrestricted internet (but this does not bypass credential rules—each secret still honors its own policy).
Optional: block specific hosts even when egress is broad:
egress_hosts = ["*"]
deny_hosts = ["api.stashbase.dev"]Define which hosts, methods, and paths may receive each credential:
[secrets.GITHUB_TOKEN]
env = "GH_TOKEN"
[[secrets.GITHUB_TOKEN.rules]]
effect = "allow"
hosts = ["api.github.com"]
methods = ["GET"]
paths = ["/repos/*/*", "/repos/*/*/issues*"]
[[secrets.GITHUB_TOKEN.rules]]
effect = "deny"
hosts = ["api.github.com"]
methods = ["DELETE"]
paths = ["*"]- Rules are unordered; a matching
denyalways blocks the credential. - Paths use
*as a wildcard; query strings are ignored. - At least one matching
allowrule is required for injection. - Methods are normalized to uppercase (
GET,POST, etc.).
See credential-policies.md for detailed rule syntax.
HTTP-based MCP servers can be configured with tool-level access control:
[secrets.LINEAR_API_KEY]
env = "LINEAR_API_KEY"
[[secrets.LINEAR_API_KEY.rules]]
effect = "allow"
hosts = ["mcp.linear.app"]
methods = ["GET", "POST"]
paths = ["/mcp"]
[mcp_servers.linear]
url = "https://mcp.linear.app/mcp"
binding = "LINEAR_API_KEY"
allow_tools = ["search_issues", "get_issue"]The HTTP rule controls credential injection at the endpoint. The MCP rule controls which tools the agent may call. Omit allow_tools or leave it empty to deny all tools by default. Use allow_tools = ["*"] to allow all tools (a matching deny_tools still takes precedence).
Hooks that call the Stashbase API (dependency checking, secret scanning) must be granted explicitly:
allow_hooks = ["dependency_check", "secret_scan"]The child receives only a scoped local broker token, never your API key. Hooks are disabled by default.
dependency_check works with the hooks from stashbase agent hooks deps install. With the native backend they run your installed CLI, which asks the Agent Proxy to call the API. The Docker image has no Stashbase CLI, so in a Docker run a small stand-in is mounted as stashbase inside the container: it forwards the hook's input to the Agent Proxy, which runs stashbase agent hooks on the host, confined the same way as the secret scan below, and returns its answer. If the proxy can't be reached, the stand-in blocks the command instead of letting the install through unchecked. In a Docker run the agent can't see the global hook config in your home directory. Either install the hook per repository (without --global; the container sees the repo), or install it into the Docker sandbox's own home, which every Docker run shares: stashbase agent hooks deps install claude --docker (likewise check and uninstall).
secret_scan lets the git hooks from stashbase scan install run inside the sandbox. The hook sends only a token to the Agent Proxy, which runs stashbase scan staged (or unpushed) on the host, in the run's working directory, with your key. Because the agent can edit the repo's scan config, these scans ignore its match and output-dir settings; excluded-files and ignored-secrets still apply. The host-side scan is confined, so a symlink or a .git redirect planted by the agent can't point it at your other files. On Linux it sees only system files plus the run's working tree, git directories and the CLI binary. On macOS it can't read file contents anywhere users' data lives (home directories, /Volumes, /tmp, /var/folders, /opt) except those same run paths; system files stay readable, and file names and sizes stay visible because path lookup needs them. The profile's deny_read entries stay denied on both. This uses Seatbelt on macOS and bubblewrap on Linux; where neither is available (including Windows), a profile with secret_scan refuses to start. Restricted scans also stop at 1 MiB per changed file, 32 MiB in total, 10,000 files or 1,000 commits; larger changes have to be committed from outside the sandbox. The hook needs curl in the sandbox (the default Docker image has it). Hooks installed before this existed need stashbase scan install once more to pick up the sandbox-aware block. Without secret_scan, a scan hook inside the sandbox fails with a message pointing here.
agent run writes a local JSONL audit log of proxy events—host, method, action, status code, and byte counts. No secret values, placeholders, or request bodies are logged. Startup prints the audit session ID and log path.
View audit events:
stashbase agent logs list
stashbase agent logs --session <id> --action injectedList and revoke active sessions:
stashbase agent sessions list
stashbase agent sessions revoke <session-id>Personal credentials are account-specific secrets available only in remote sessions. They are never fetched, printed, or stored locally.
[personal_credentials.MY_TOKEN]
env = "MY_TOKEN"
[[personal_credentials.MY_TOKEN.rules]]
effect = "allow"
hosts = ["api.example.com"]
methods = ["GET", "POST"]
paths = ["/v1/*"]See personal-credentials.md for more details.
Common patterns for GitHub, Codex, Claude Code, and generic HTTP APIs:
See integrations.md for practical examples.
stashbase agent validate --profile my-profile
stashbase agent profiles show my-profile
stashbase agent explain --profile my-profile \
--host api.github.com --method GET --path /userThe proxy is HTTP/HTTPS only and designed for standard developer tools. It does not support:
- SSH, Git-over-SSH, raw TCP/UDP, databases, local sockets
- Tools that bypass
HTTP_PROXYenvironment variables - Request-body or query-parameter injection (credentials are header-only)
- Process-level isolation (same-user processes can still access broader system credentials)
For stronger filesystem and network isolation than the native backend provides, see Sandboxing.
This is a lightweight local guide. For comprehensive documentation on advanced features, policy regression tests, compatibility matrices, and operational details, see https://docs.stashbase.dev/agents.