Skip to main content

SecurityConfig

Struct SecurityConfig 

Source
pub struct SecurityConfig {
    pub allowed_workdirs: Vec<String>,
    pub allow_seed_commands: bool,
    pub allow_local_network: bool,
    pub allow_env_vars: Vec<String>,
    pub shell_env: ShellEnvMode,
    pub shell_env_withhold: Vec<String>,
    pub allow_blueprint_read_paths: bool,
    pub allow_blueprint_safe_commands: bool,
    pub allow_blueprint_permissions: bool,
    pub read_paths: Vec<String>,
    pub credential_store: CredentialStoreKind,
}
Expand description

[security] in ~/.leviath/config.toml.

Distinct from a blueprint’s [security] block, which configures taint tracking for one agent - this one holds machine-wide switches.

Fields§

§allowed_workdirs: Vec<String>

Directories a run’s workdir may sit under without being confirmed.

Empty by default, which means “ask once about anything alarming” rather than “ask about everything”: a workdir is only questioned when it is a home directory or a filesystem root, which is where an agent’s file writes do the most damage and the least good. Listing a path here says “yes, I work there” and silences the prompt for it and everything under it.

This exists because the workdir defaults to wherever lev run was invoked, and running from ~ is an easy thing to do by accident - issue #252 is a machine that lost 115 GB to an agent writing under a profile root. Confirming is cheap; noticing afterwards is not.

§allow_seed_commands: bool

Whether a blueprint’s seed = { command = "..." } regions may run.

On by default. A command seed executes at spawn - before the first inference, and therefore before any tool-approval prompt - so it is the one place a manifest can run something without the user being asked. It is still confined to the run’s workdir, routed through the entry stage’s sandbox when the agent declares one, and capped by [limits] script_shell_timeout_secs. Set this to false to refuse them machine-wide, or pass --no-seed-commands for a single run. Inspect an agent’s command seeds before installing it with lev validate <path>.

§allow_local_network: bool

Whether agent-driven fetches may reach loopback, private, and link-local addresses.

Off by default. An agent’s web_fetch URL is chosen by the model out of context an attacker can influence - a search result, a page fetched a moment ago, an issue body - so an unrestricted fetch makes the agent a confused deputy inside the user’s network. The concrete targets are http://169.254.169.254/… (cloud metadata, which returns instance credentials), http://127.0.0.1:3000/api/… (the user’s own lev serve), and anything on the LAN.

Turn this on when the agent is genuinely meant to talk to something local - a self-hosted model, a dev server under test. It applies to the script host’s http_get/http_post and to redirect following; see leviath_net.

§allow_env_vars: Vec<String>

Credential-shaped environment variables that agent scripts may read.

A Rhai script tool or script provider calling env_var("NAME") gets any ordinary variable - PATH, TZ, an app’s own config. A name that looks like a credential (see leviath_core::secrets::is_sensitive_env_name) is refused unless it appears here, because a two-line script tool reading ANTHROPIC_API_KEY and POSTing it elsewhere was otherwise a working exfiltration path with no prompt anywhere in it.

List the exact names a script legitimately needs - typically the key for a custom provider script:

[security]
allow_env_vars = ["MY_PROVIDER_KEY"]

Matching is case-insensitive and exact. There is no wildcard: "*" is read as a variable literally named *, not as “allow everything”.

§shell_env: ShellEnvMode

How much of the daemon’s environment a shell tool call, a Rhai shell() host call, and a region’s command seed inherit.

The daemon holds provider keys, LEVIATH_API_TOKEN, and whatever credentials the person who started it had exported. Handing all of that to every shell command means a single env in tool output leaks the lot.

filtered (the default) withholds credential-shaped names but keeps SSH_AUTH_SOCK, so git push over agent keys still works. strict drops the carve-out and also takes AWS_PROFILE, KUBECONFIG and friends. custom ignores the shape heuristic and withholds exactly what Self::shell_env_withhold names. inherit is the old behaviour.

Toolchain variables - PATH, HOME, CARGO_HOME, JAVA_HOME, VIRTUAL_ENV, NVM_DIR, GOPATH, DOCKER_HOST - pass through under every mode. Self::allow_env_vars hands a specific name over under every mode too.

§shell_env_withhold: Vec<String>

The names shell_env = "custom" withholds. Ignored under every other mode, where the name-shape heuristic decides instead.

§allow_blueprint_read_paths: bool

Whether a blueprint’s [read_paths] declarations are honored as-is.

Off by default. A [read_paths] block travels inside the agent.leviath you installed, and a manifest may only tighten what your config allows, never widen it - otherwise any agent package could read ~/.ssh, this very config file (your API keys), or a password store by shipping one TOML line. With this off, an agent’s declared read paths are inert until you grant them via Self::read_paths or [agent_read_paths.<name>]. Turning it on says “any blueprint I run may read every path it declares” - reads only, each access still resolves symlinks and must land inside a declared entry, but prefer the per-agent grant for anything you did not author yourself.

§allow_blueprint_safe_commands: bool

Honour every blueprint’s own [safe_commands] block.

Off by default, and for the same reason as Self::allow_blueprint_read_paths: a [safe_commands] block travels inside an agent.leviath you installed, so letting it count by itself would let any agent package pre-approve its own shell with one TOML line. With this off, a blueprint’s list is inert until you opt in, either here for every agent or per agent via [agent_safe_commands.<name>] allow_blueprint = true. Prefer the per-agent grant for anything you did not author yourself.

§allow_blueprint_permissions: bool

Honour a blueprint’s [tool_permissions] even where it is more permissive than the built-in default for a tool you have not configured.

Off by default, for the same reason as the two switches around it: declaring is not granting. Saying nothing about shell is the normal state, so without this a downloaded manifest could give itself shell = "allow" on a stock machine. With it off, a blueprint may still pre-approve the read-only web tools that are some agents’ whole point, and anything beyond that is clamped to the built-in default.

The per-agent grant needs no switch of its own: naming the tool under [agent_tool_permissions.<name>] makes it a ceiling for that agent, and a blueprint may go up to a ceiling. Prefer that for anything you did not author yourself - it says which agent and which tool, where this says “every agent, every tool”.

§read_paths: Vec<String>

Machine-wide read grants for agents that declare [read_paths].

Entries use the same three forms as a blueprint’s [read_paths] allow: an exact path (grants its subtree), glob: and regex: patterns (matched against the symlink-resolved real path, written with / on every OS, regex auto-anchored). ~/ expands to your home; a relative entry resolves against the run’s workdir.

[security]
read_paths = ["~/.leviath/runs", "glob:~/design-docs/**"]

A grant only takes effect for a path the running blueprint also declares - by itself it grants nothing, so listing a directory here does not open it to agents that never asked.

§credential_store: CredentialStoreKind

Where provider API keys and MCP OAuth tokens are kept.

file by default - ~/.leviath/config.toml and ~/.leviath/mcp-auth.json, both created 0600 so they are never even briefly world-readable. This is what Claude Code and Codex do, and it is the only backend that works headless, in a container, over SSH, and on a CI runner.

Set it to keychain to move secrets into the OS credential store (macOS Keychain, Windows Credential Manager, Secret Service elsewhere), so a stolen ~/.leviath directory yields nothing:

[security]
credential_store = "keychain"

Then run lev auth migrate to move the secrets you already have. It is opt-in rather than the default because an unavailable keychain is not a degraded experience but a broken one - every inference fails at once - and the environments Leviath is most useful in are the least likely to have a working credential store. lev auth status reports whether this machine actually has one.

Trait Implementations§

Source§

impl Clone for SecurityConfig

Source§

fn clone(&self) -> SecurityConfig

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SecurityConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for SecurityConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for SecurityConfig

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for SecurityConfig

Source§

impl PartialEq for SecurityConfig

Source§

fn eq(&self, other: &SecurityConfig) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for SecurityConfig

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for SecurityConfig

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> ConditionalSend for T
where T: Send,

Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Converts Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Converts &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSend for T
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DynEq for T
where T: Any + Eq,

Source§

fn dyn_eq(&self, other: &(dyn DynEq + 'static)) -> bool

This method tests for self and other values to be equal. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> FromTemplate for T
where T: Clone + Default + Unpin,

Source§

type Template = T

The Template for this type.
Source§

impl<T> FromWorld for T
where T: Default,

Source§

fn from_world(_world: &mut World) -> T

Creates Self using default().

Source§

impl<T> FutureExt for T

Source§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
Source§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> IntoResult<T> for T

Source§

fn into_result(self) -> Result<T, RunSystemError>

Converts this type into the system output type.
Source§

impl<A> Is for A
where A: Any,

Source§

fn is<T>() -> bool
where T: Any,

Checks if the current type “is” another type, using a TypeId equality comparison. This is most useful in the context of generic logic. Read more
Source§

impl<T> NoneValue for T
where T: Default,

Source§

type NoneType = T

Source§

fn null_value() -> T

The none-equivalent value.
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Serialize for T
where T: Serialize + ?Sized,

Source§

fn erased_serialize(&self, serializer: &mut dyn Serializer) -> Result<(), Error>

Source§

fn do_erased_serialize( &self, serializer: &mut dyn Serializer, ) -> Result<(), ErrorImpl>

Source§

impl<T> Template for T
where T: Clone + Default + Unpin,

Source§

type Output = T

The type of value produced by this Template.
Source§

fn build_template( &self, _context: &mut TemplateContext<'_, '_>, ) -> Result<<T as Template>::Output, BevyError>

Uses this template and the given entity context to produce a Template::Output.
Source§

fn clone_template(&self) -> T

Clones this template. See Clone.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> TypeData for T
where T: 'static + Send + Sync + Clone,

Source§

fn clone_type_data(&self) -> Box<dyn TypeData>

Creates a type-erased clone of this value.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more