Advanced Permissioning
Centaur gives each chat context a principal in the Centaur Console. The principal controls which credentials its sandbox can use and which internal surfaces the sandbox can reach.
Use advanced permissioning when one Centaur deployment serves people or teams with different access requirements. A support channel can receive CRM access, for example, without giving the same credential to engineering channels or direct messages.
Permission Model
Credential access follows this path:
chat context -> principal -> assigned roles + direct grants -> secrets -> request rules- A principal is a user, channel, issue, conversation, or workflow.
- A role is a reusable bundle of secret grants.
- A grant connects one secret to a principal or role.
- A secret describes a credential source and the hosts, methods, paths, and request locations where the credential may be used.
A principal receives the union of its direct grants and the grants inherited from all assigned roles. A grant does not expose the credential to the sandbox. The sandbox receives a placeholder, and iron-proxy injects the real value only when the principal has access and the outbound request matches the secret's request rules.
Sandbox capabilities are a separate layer. Each principal also controls:
| Capability | Options | Effect |
|---|---|---|
| Repo cache | none, public, or all | Controls which repo-cache-backed workspaces and tool sources the sandbox can mount. |
| Observability | Enabled or disabled | Controls access to the deployment's logs and metrics surfaces. |
| API server | Enabled or disabled | Controls access from the sandbox to the api-rs control plane. |
Removing a tool grant does not remove repo-cache or API access. Review both layers when designing a least-privilege role.
Principal Mapping
Centaur creates or updates the principal when a session starts. Let each target user or conversation start one session before configuring it, then select the resulting principal in Principals. This avoids manually calculating ids and preserves identity metadata from the chat ingress.
The stable principal ids follow these rules:
| Context | Principal foreign id | Shared by |
|---|---|---|
| Slack direct message | slack-user-<team-id>-<user-id> | That Slack user in that workspace |
| Slack channel or group | slack-channel-[<team-id>-]<channel-id> | Everyone using Centaur in that channel |
| Discord channel | discord-channel-<guild-id>-<channel-id> | Every thread in that channel |
| Linear issue | linear-issue-<issue-id> | Every Centaur session on that issue |
| Teams personal chat | teams-user-<user-id> | That Teams user |
| Teams channel or group conversation | teams-conversation-<conversation-id> | Everyone using Centaur in that conversation |
Workflow with WORKFLOW_PRINCIPAL = True | workflow-<workflow-name> | Runs of that workflow |
Workflow with WORKFLOW_PRINCIPAL = "<foreign-id-or-oid>" | The selected existing principal | Runs of that workflow |
| Other session key | thread-<session-key> | That session key |
Values are lowercased and converted to URL-safe slugs. The team scope is required for Slack DMs and optional for Slack channel keys. Brackets mark an optional scope, not literal characters. Display names can change when a channel is renamed, but the foreign id remains stable.
Configure the Console
The Helm chart deploys the Centaur Console and wires api-rs to it. A production
infra Secret must contain the Console database, bootstrap login, API key,
encryption, and signing values described in Deploying in Production.
For local development, just bootstrap-secrets generates them when they are
absent.
Expose the Console through console.ingress or temporarily port-forward it:
kubectl port-forward -n centaur service/centaur-centaur-console 3000:3000Sign in, open System Settings, and choose the defaults for newly created principals:
- Default Roles assigns selected roles only when a new principal is first
created. The
infrarole is selected by default so new sessions can use the configured model harness. - Default Sandbox Capabilities sets repo-cache, observability, and API access for new principals.
Changing defaults does not rewrite existing principals. Update existing principals individually from Principals.
Two common baselines are:
| Baseline | Default roles | When to use it |
|---|---|---|
| Tool access by allowlist | Keep infra as a default; grant tool roles selectively. | Most shared installations. Every user may run an agent, but tools differ by context. |
| Full deny by default | Clear all default roles; assign infra and approved tool roles to each allowed principal. | Installations where even model-backed agent execution requires approval. |
The infra role contains harness and platform credentials, not every tool
credential. Tool-specific access normally belongs in tool-<slug> roles.
Configure the Operator CLI
Use the Console UI for individual changes. Use centaur-perms for repeatable
provisioning, bulk changes, and tool-secret registration.
Set the admin API connection. For a local port-forward, the URL is
http://localhost:3000. In Kubernetes, the API key is the
IRON_CONTROL_INITIAL_API_KEY value from the infra Secret.
export IRON_CONTROL_URL=http://localhost:3000
export IRON_CONTROL_API_KEY="$(
kubectl get secret -n centaur centaur-infra-env \
-o jsonpath='{.data.IRON_CONTROL_INITIAL_API_KEY}' | openssl base64 -d -A
)"Point the CLI at the same tool directories api-rs uses. Explicit
--tools-dir values are read first, followed by the colon-separated
TOOL_DIRS value. Later directories shadow earlier ones, matching overlay
ordering.
export TOOL_DIRS="$PWD/tools:$HOME/centaur-overlay/tools"
cd services/api-rs
cargo run -p centaur-perms -- --helpThe examples below assume the current directory is services/api-rs.
Register and Grant a Tool
The --tool flow reads the tool's pyproject.toml, registers its declared
secrets, creates or updates its tool-<slug> role, grants the secrets to that
role, and assigns the role to the principal.
cargo run -p centaur-perms -- \
--tools-dir ../../tools \
principals grant slack-user-t123-u123 \
--tool githubUse the same secret-source policy as the deployed ironProxy.secretSource.
The CLI defaults to env.
cargo run -p centaur-perms -- \
--source-policy onepassword-connect \
--op-vault Engineering \
--tools-dir ../../tools \
principals grant slack-user-t123-u123 \
--tool github| CLI policy | Helm value | Secret source |
|---|---|---|
env | ironProxy.secretSource: env | Environment variable on the proxy |
onepassword | ironProxy.secretSource: onepassword | 1Password service account |
onepassword-connect | ironProxy.secretSource: onepassword-connect | In-cluster 1Password Connect |
Using the wrong policy registers a valid-looking secret with the wrong source, so verify this value before provisioning.
Grant Users and Channels
In the Console, open Principals, select a principal, then use:
- Roles to assign a reusable role.
- Direct Grants to grant one secret without creating a role.
- Effective Grants to inspect the combined result.
- Sandbox Access to configure repo-cache, observability, and API access.
Grant a whole tool to one Slack user:
cargo run -p centaur-perms -- \
principals grant slack-user-t123-u123 \
--tool githubGrant an existing role:
cargo run -p centaur-perms -- \
principals grant slack-user-t123-u123 \
--role tool-githubGrant one existing secret directly by OID:
cargo run -p centaur-perms -- \
principals grant slack-user-t123-u123 \
--secret ssr_...Grant tools to everyone using Centaur in a Slack channel:
cargo run -p centaur-perms -- \
principals grant slack-channel-c456 \
--tool linear \
--tool githubThe CLI also accepts a canonical thread key and derives its principal. For a Slack DM, include both the team in the thread key and the acting user because the DM principal is scoped to that user in that workspace:
cargo run -p centaur-perms -- \
principals grant 'slack:T123:D456:1780000000.0001' \
--slack-user U123 \
--tool githubPrefer selecting an existing foreign id from the Console or principals list
when possible.
Enable Centaur for External Slack Channels
Slack Connect channels can include participants from other workspaces. Configure the workspace and permission controls before adding Centaur to an external channel.
Allow External Workspaces
Allowlist each external workspace by its Slack team id. Messages and Block Kit actions attributed to any other external workspace are ignored.
slackbotv2:
externalOrgAllowlist: "T01234567,T07654321"Restrict the Shared Infra Role
In the Console, reduce the shared infra role to the minimum harness and
platform secrets every admitted principal needs. Remove tool grants from this
role. Then disable automatic infra-secret synchronization so a later tool
change cannot add them back:
apiRs:
syncInfraSecrets: falseThe equivalent environment variable is
IRON_CONTROL_SYNC_INFRA_SECRETS=false. With synchronization disabled, manage
changes to the infra role and its secrets explicitly in the Console.
Set Defaults for External Channels
Open System Settings and configure Default Roles for new principals.
Select the reduced infra role plus only the other roles that every new
external channel should inherit. Put tools approved for broad use in a separate
role such as company-shared, then add that role to the defaults only if all
admitted external channels may use those tools. Grant narrower tool roles to
individual channel principals after reviewing who can participate in each
channel. For a restrictive sandbox baseline, set repo-cache access to none
and disable observability and API server access.
Console defaults apply only when a principal is first created. If Centaur has already received a message from an external channel, open Principals and restrict that channel principal directly. Disabling synchronization does not remove existing secrets or grants, and changing default roles does not update existing principals.
Optional: Allow Bots to Mention Centaur
Human-authored mentions work without this setting. If another bot needs to
trigger Centaur, add it to slackbotv2.triggerBotAllowlist. Centaur ignores
bot-authored messages that are not on this list, including messages that
mention Centaur.
For an exact Slack bot id from an event's bot_id field, prefix the B... id
with bot::
slackbotv2:
triggerBotAllowlist: "bot:B01234567,bot:B07654321"Use bot ids that start with B, not Slack app ids that start with A. A bare
B... value is not accepted. Bot member ids that start with U or W are
also supported without a prefix.
Build Reusable Roles
Create a custom role such as tool-support from Roles in the Console. Then
attach existing secrets or register selected tool secrets with the CLI.
Grant an existing secret to the role:
cargo run -p centaur-perms -- \
roles grant tool-support \
--secret ssr_...Register every secret declared by a tool and grant it to the custom role:
cargo run -p centaur-perms -- \
--tools-dir ../../tools \
roles grant tool-support \
--tool githubRegister only selected secrets from that tool:
cargo run -p centaur-perms -- \
--tools-dir ../../tools \
roles grant tool-support \
--tool github \
--secret-name GITHUB_TOKENAssign the finished role to users, channels, or workflows:
cargo run -p centaur-perms -- \
principals grant slack-channel-c456 \
--role tool-supportVerify Effective Access
List managed principals and inspect one principal's assigned roles, direct grants, and effective secret placeholders:
cargo run -p centaur-perms -- principals list --managed --filter slack
cargo run -p centaur-perms -- principals show slack-user-t123-u123Inspect the role and registered secrets:
cargo run -p centaur-perms -- roles show tool-github
cargo run -p centaur-perms -- secrets list --managed --filter githubFrom a running sandbox, inspect the same principal's redacted live view:
centaur-console permissionsThe response includes effective credential metadata and sandbox capabilities, but never returns credential values. Finish verification with one real allowed tool request and one request from a principal that should be denied.
Revoke Access
In the Console, open the principal and remove a role from Roles or revoke a secret from Direct Grants.
Revoke a tool role from a principal:
cargo run -p centaur-perms -- \
principals revoke slack-user-t123-u123 \
--tool githubRevoke one direct secret or a known grant:
cargo run -p centaur-perms -- \
principals revoke slack-user-t123-u123 \
--secret ssr_...
cargo run -p centaur-perms -- \
principals revoke slack-user-t123-u123 \
--grant-id grant_...Revoking a role assignment leaves the role and its secrets available to other
principals. Deleting a secret removes every grant that points to it. After a
revocation, verify the denied path from a new turn and inspect
centaur-console permissions again.
OAuth and Workflow Principals
OAuth consent creates a grantable wrapper secret for the resulting broker credential. Grant that wrapper secret to a user, channel, or role like any other secret. See OAuth Apps for registration and consent.
Workflows opt into isolated permissions with WORKFLOW_PRINCIPAL = True, which
derives workflow-<workflow-name>. They can instead set WORKFLOW_PRINCIPAL to
an existing principal foreign ID or prn_-prefixed OID. Agent turns can select
an existing principal with ctx.agent_turn(..., principal="<foreign-id>").
Unknown foreign IDs fail instead of falling back to a broader identity. Grant
workflows and agent turns only the roles or secrets they need. See
Creating Workflows.
Operational Checklist
- Keep request rules narrow. A principal grant does not replace host, method, path, header, query, or Postgres routing restrictions on the secret.
- Prefer roles for repeatable access packages and direct grants for exceptions.
- Review default roles and default sandbox capabilities before inviting users.
- Remember that changes to defaults affect new principals only.
- Test both an allowed principal and a denied principal after every policy change.
- Revoke stale assignments instead of deleting a shared role or secret unless every consumer should lose access.