grithdocs

How profiles work

What a supervisor profile trusts, how grith picks one for a session, and why you cannot write your own.

Every supervised session runs under exactly one profile. A profile lists what a kind of tool touches as a matter of routine - paths, commands, destinations, listen addresses - and grith turns that list into a session allowlist when the session starts. A call that matches the allowlist is allowed immediately and never reaches the proxy: no filters run, no score is computed, and the call is recorded as filtered noise rather than as a decision.

That is the whole job. A profile removes noise. It does not grant a capability, move a threshold, or change what any filter scores.

How grith picks one

First match wins:

  1. --profile <name> passed to grith exec.
  2. Auto-detection from the basename of the command being supervised.
  3. supervisor.default_profile, which ships as generic.
terminal
$ grith config | grep default_profile
default_profile = "generic"

Auto-detection is a fixed basename table and nothing else - no config inspection, no version sniffing, no fuzzy matching:

Command basenameProfile
claude, claude-codeclaude-code
codexcodex
cursor-agentcursor
copilot, copilot-clicopilot
aideraider
goosegoose
clinecline
openclawopenclaw

Anything else falls through to the default. A --profile value that is not one of the 11 built-in profiles is fatal before the session starts, with no supervisor profile '<name>' found.

What a profile declares

FieldWhat matchesWhat it covers
routine_pathsA path prefix. A trailing /**, /* or * is trimmed to the prefix, so /tmp/claude-* trusts everything beginning /tmp/claude-.All file I/O under the prefix - read, write, delete, rename, chmod.
routine_commandsA command name, resolved through $PATH once at session start.Spawning that exact binary. Before honouring a match grith re-canonicalises the path and walks every component: each must be owned by root or by you, never world-writable, and never group-writable when root owns it.
routine_destinationsA domain suffix, so datadoghq.com also covers foo.bar.datadoghq.com.Outbound connections. These are loaded into the egress-policy filter as trusted domains for the profile too, so they also affect scoring inside the pipeline.
routine_exec_rootsA directory prefix.Executing anything underneath it.
routine_listen_addressesAn address, without a port. A wildcard (0.0.0.0, ::) is refused at parse time.Binding a listener.
readonly_pathsAn exact path, canonicalised at session start. No prefix, no glob - and an entry for a file that does not exist on this machine is silently dropped.Reads only. A write, append, delete, rename or chmod on the same path still runs the whole pipeline.
readonly_path_patternsThe same, with a single-segment * wildcard.Reads only.
scratch_rootsA directory prefix.Nothing in the pipeline. Deletes and renames under it are left out of the volume signal that watches for a mass-delete spree, so routine cache churn never trips it. Every filter still runs.
launch_contract-Arguments grith adds to the tool's own command line at launch.
local_listener_policyA port plus an address family.A declared bind that is allowed instead of queued, with the address rewritten to loopback when the entry sets allow_clamp, and the rewrite audited. The one shipped entry is Codex's kernel-assigned MCP port.

${HOME} and ${PROJECT_DIR} expand at session start. ${PROJECT_DIR} is the directory grith exec was launched in, not a curated value.

Two further fields, permit_authority_delegating and permit_control_sockets, switch enforcement off for named binaries and sockets. The supervisor honours both, but no shipped profile sets either.

Merge order

A profile resolves once, highest priority first:

  1. the profile's own entries,
  2. its parent chain, via extends,
  3. the [defaults] block, which every profile receives.

The merge is additive with de-duplication. There is no override and no subtract - a child cannot take back something its parent or the defaults granted.

That is why generic is not as narrow as its own block looks. It declares one entry, ${PROJECT_DIR}/**; after the defaults merge it carries 34 routine paths and 64 routine commands, including git, ssh, the ordinary file and text utilities, and the toolchain scratch directories under /tmp.

Four fields are never inherited and must be declared on the profile that uses them: launch_contract, namespace_users, permit_authority_delegating and permit_control_sockets. Security capabilities do not travel down an extends chain by accident.

What a profile cannot do

The allowlist is a fast path, not an override. Five things it never wins:

  • Session containment. While a session is contained the allowlist is not consulted at all, profile-trusted destinations included. Every call goes to the proxy. See session containment.
  • Delegating spawns and control sockets. A spawn that hands authority to another process, or a connect to a control socket, cannot be short-circuited by the allowlist. openclaw lists docker in routine_commands; that does not bypass enforcement.
  • Credential stores inside a trusted project. Project-derived trust carries a marker the allow gate re-checks, so a .ssh/, .aws/ or .gnupg/ directory, an .npmrc or .git-credentials, and in-project secrets like .env or a Rails master.key all still go to the proxy even when they sit under a trusted ${PROJECT_DIR}.
  • Explicit read-only scopes. A scope you approved for a sensitive directory still runs the pipeline, so taint and audit state are recorded; the queue is converted to an allow afterwards.
  • A dangerous launch directory. If grith exec was launched at /, at your home directory or at an ancestor of it, every ${PROJECT_DIR} entry is dropped for that session with a warning. Run the tool from inside the project.

ℹ️Subtraction is checked first

Both subtractive controls run before the session allowlist. A directory you block from a prompt with [b] beats the allowlist outright. grith exec --workspace-only denies file access outside the workspace at the same point, but it steps aside for the profile's own routine paths and for anything approved earlier in the session - it fences the session in without breaking the tool.

You cannot write your own

The profile set is compiled into the binary. There is no ~/.config/grith/profiles.toml, no config key pointing at one, and no dashboard editor. grith profile has a single subcommand, audit, which is a maintainer tool for reviewing a forensic trace against a profile. There is no grith profile list.

What you have instead:

  • Approve once, keep it. The always-allow answer at a prompt records a learned rule in ~/.config/grith/learned_rules.toml, scoped to the profile it was approved under, and loads it back into the allowlist on every later session under that profile.
  • Widen project trust. supervisor.trust.additional_project_roots and include_linked_worktrees extend the workspace across sibling checkouts. See supervisor configuration.
  • Pick a different built-in. --profile generic-cli and --profile generic are both reasonable choices for a tool with no profile of its own.
  • Share rules across a team. Pro pulls team learned rules into the same allowlist. See team sync.

See also

Last updated: 2026-08-24Edit this page on GitHub →