Skip to main content

Agent security configuration generator — translates canonical security rules into agent-specific configs

Project description

twsrt logo

Agent security configuration generator — translates canonical security rules into agent-specific configs.

The Problem

AI coding agents (Claude Code, Copilot CLI, etc.) each have their own permission model and configuration format. Maintaining security rules independently per agent leads to configuration drift, and coverage gaps.

Meanwhile, Anthropic's Sandbox Runtime Tool (SRT) enforces OS-level restrictions (filesystem deny, network allowlists) for Bash commands via kernel sandboxing. But SRT cannot control an agent's built-in tools (Read, Write, Edit, WebFetch) — those run inside the agent's own process.

The Solution: Defense in Depth

twsrt tries to bridge the gap. It reads the same SRT policy that enforces OS-level Bash restrictions and translates it into application-level rules for every agent's built-in tools:

                CANONICAL SOURCES (human-maintained)
                ====================================
                ~/.srt-settings.json        — OS-level sandbox rules
                ~/.config/twsrt/bash-rules.json — command deny/ask rules
                          |
                          v
                +-----------------+
                |      twsrt      |  deterministic translation
                |   (generator)   |  + drift detection
                +--------+--------+
                         |
            +------------+------------+
            v            v            v
     Claude Code    Copilot CLI    (future agents)
     settings.json  --flag args

                ENFORCEMENT LAYERS
                ==================
     Layer 1 (OS):  SRT sandbox — kernel-level deny (Bash only)
     Layer 2 (App): Agent permissions — tool-level deny/ask (all tools)

This gives you two layers for the most dangerous attack vector (Bash commands accessing credentials or network) and one consistent layer for built-in tools — all generated from a single source of truth.

Access Path SRT (Layer 1) Agent Permissions (Layer 2) Depth
Bash(cat ~/.aws/credentials) Kernel-enforced deny Tool-level deny Two layers
Read(~/.aws/credentials) Not covered Tool-level deny One layer
Bash(curl evil.com) Network proxy blocks Tool-level deny Two layers
WebFetch(evil.com) Not covered Tool-level allow check One layer

You then start your agent either with SRT builtin (e.g. claude-code, pi-mono via extenstion) or with srt as wrapper, e.g. copilot-cli.

srt -c "copilot \
    --allow-tool 'shell(*)' \
    --allow-tool 'read' \
    --allow-tool 'edit' \
    --allow-tool 'write' \
    --deny-tool 'shell(rm)' \
    --deny-tool 'shell(rmdir)' \
    --deny-tool 'shell(dd)' \
    --deny-tool 'shell(mkfs)' \
    ...

For the full security analysis and threat model see SECURITY_CONCEPT.md.

Overview

twsrt reads two canonical sources:

  • SRT settings (~/.srt-settings.json) — filesystem read/write deny rules, write allow rules, network domain allowlists
  • Bash rules (~/.config/twsrt/bash-rules.json) — command deny/ask rules for Bash execution

It generates security configurations for:

  • Claude Code (~/.claude/settings.json) — permissions.deny, permissions.ask, permissions.allow, sandbox.network
  • Copilot CLI--allow-tool and --deny-tool flag snippets

Key invariant: Source files are never written by twsrt. Target managed sections are never hand-edited.

Installation

# Install as editable uv tool
make install

# Or via pip
pip install twsrt

Usage

Initialize config directory

twsrt init                    # Creates ~/.config/twsrt/ with config.toml + bash-rules.json
twsrt init --force            # Overwrite existing files

Generate agent configs

twsrt generate claude         # Print Claude Code permissions to stdout
twsrt generate copilot        # Print Copilot CLI flags to stdout
twsrt generate                # Generate for all agents

twsrt generate claude --write # Write to ~/.claude/settings.json (selective merge)
twsrt generate claude -n -w   # Dry run: show what would be written

Edit canonical sources

twsrt edit srt                # Open ~/.srt-settings.json in $EDITOR
twsrt edit bash               # Open ~/.config/twsrt/bash-rules.json in $EDITOR
twsrt edit                    # Show available sources

Detect configuration drift

twsrt diff claude             # Compare generated vs existing settings.json
twsrt diff                    # Check all agents

Exit codes: 0 = no drift, 1 = drift detected, 2 = missing file.

Typical workflow

twsrt edit srt                # Add a domain to allowedDomains
twsrt generate claude         # Preview the change
twsrt generate claude --write # Apply (selective merge preserves hooks, MCP, etc.)
twsrt diff claude             # Verify: exit 0 = no drift

Configuration

SRT is a dependency and needs to be installed separately.

GOTCHA: sandbox write allowlist being hardcoded

~/.srt-settings.json (SRT — prerequisite)

SRT configuration is the primary canonical source that defines OS-level enforcement boundaries. twsrt reads it to generate matching agent-level rules:

{
  "filesystem": {
    "denyRead":  ["~/.aws", "~/.ssh", "~/.gnupg", "~/.netrc"],
    "denyWrite": ["**/.env", "**/*.pem", "**/*.key", "**/secrets/**"],
    "allowWrite": [".", "/tmp", "~/dev"]
  },
  "network": {
    "allowedDomains": [
      "github.com", "*.github.com",
      "pypi.org", "*.pypi.org",
      "registry.npmjs.org"
    ]
  }
}

Comprehensive example: .srt-settings.json

~/.config/twsrt/config.toml

[sources]
srt = "~/.srt-settings.json"
bash_rules = "~/.config/twsrt/bash-rules.json"

[targets]
claude_settings = "~/.claude/settings.json"
# copilot_output = "~/.config/twsrt/copilot-flags.txt"  # optional, stdout if omitted

~/.config/twsrt/bash-rules.json

{
  "deny": ["rm", "sudo", "git push --force"],
  "ask": ["git push", "git commit", "pip install"]
}

Comprehensive example: bash-rules.json

Rule Mapping

SRT / Bash Rule Claude Code Copilot CLI
denyRead directory Tool(path) + Tool(path/**) in deny (SRT enforces)
denyRead file Tool(path) in deny (SRT enforces)
denyWrite pattern Write/Edit/MultiEdit in deny (SRT enforces)
allowWrite path (no output) --allow-tool flags
allowedDomains domain WebFetch(domain:X) in allow + sandbox.network (SRT enforces)
Bash deny cmd Bash(cmd) + Bash(cmd *) in deny --deny-tool 'shell(cmd)'
Bash ask cmd Bash(cmd) + Bash(cmd *) in ask --deny-tool (lossy, warns)

Where Tool = Read, Write, Edit, MultiEdit. Directory vs file detection uses the filesystem at generation time; glob patterns and unknown paths are treated as bare patterns (no /** suffix for globs, /** added for unknown paths).

Sandbox Key Mapping

Claude Code's sandbox section has 17 configurable keys. twsrt manages a subset of them (sourced from .srt-settings.json) and never touches the rest:

Claude Code Key SRT Source Status
sandbox.network.allowedDomains network.allowedDomains Managed
sandbox.network.deniedDomains network.deniedDomains Managed
sandbox.network.allowLocalBinding network.allowLocalBinding Managed (pass-through)
sandbox.network.allowUnixSockets network.allowUnixSockets Managed (pass-through)
sandbox.network.allowAllUnixSockets network.allowAllUnixSockets Managed (pass-through)
sandbox.network.httpProxyPort network.httpProxyPort Managed (pass-through)
sandbox.network.socksProxyPort network.socksProxyPort Managed (pass-through)
sandbox.filesystem.allowWrite filesystem.allowWrite Managed (pass-through)
sandbox.filesystem.denyWrite filesystem.denyWrite Managed (pass-through)
sandbox.filesystem.denyRead filesystem.denyRead Managed (pass-through)
sandbox.enabled enabled Managed (pass-through)
sandbox.enableWeakerNetworkIsolation enableWeakerNetworkIsolation Managed (pass-through)
sandbox.enableWeakerNestedSandbox enableWeakerNestedSandbox Managed (pass-through)
sandbox.ignoreViolations ignoreViolations Managed (pass-through)
sandbox.excludedCommands (no SRT source) Claude-only — never generated, never removed
sandbox.autoAllowBashIfSandboxed (no SRT source) Claude-only — never generated, never removed
sandbox.allowUnsandboxedCommands (no SRT source) Claude-only — never generated, never removed

Pass-through keys are copied verbatim from SRT to Claude settings without transformation. If a key is absent from SRT, it is omitted from generated output (never set to a default).

Claude-only keys exist only in Claude Code's schema and have no SRT equivalent. twsrt generate never creates them, and twsrt generate --write preserves them via selective merge. They are invisible to twsrt.

Merge Behavior (--write)

When writing to ~/.claude/settings.json, twsrt uses selective merge — it does not overwrite the entire file. Each section has its own merge strategy:

Section Strategy Detail
permissions.deny Fully replaced All existing deny entries removed, replaced with generated ones
permissions.ask Fully replaced All existing ask entries removed, replaced with generated ones
permissions.allow Selective merge Only WebFetch(domain:...) entries replaced; everything else preserved
sandbox.network Key-by-key merge dict.update() — generated keys overwrite, unmanaged keys preserved
sandbox.filesystem Key-by-key merge dict.update() — generated keys overwrite, unmanaged keys preserved
sandbox.* (top-level) Key-by-key merge enabled, enableWeaker*, ignoreViolations overwrite; Claude-only keys preserved
hooks Preserved Untouched
additionalDirectories Preserved Untouched
All other keys Preserved Untouched

What gets preserved in permissions.allow

The allow section is the only one with nuanced logic. twsrt considers WebFetch(domain:...) entries as "managed" — it strips all existing ones and replaces them with generated ones. Everything else is treated as user-managed and preserved verbatim:

  • Blanket tool allows (Read, Glob, Grep, LS, Task, WebSearch) — kept
  • Bash allows (Bash(npm test:*), Bash(./gradlew:*)) — kept
  • MCP allows (mcp__memory__store, mcp__github__search) — kept
  • Any other custom allows — kept

Implication for Bash commands

twsrt never generates Bash(...) entries in permissions.allow. It only generates Bash entries in deny and ask (from bash-rules.json). Since those sections are fully replaced, any manually-added Bash deny/ask entries in settings.json will be lost on twsrt generate --write. Only entries defined in your bash-rules.json source survive.

However, Bash allow entries you've manually added are safe — they don't match the WebFetch(domain: prefix and are preserved.

Development

make test              # Run tests
make lint              # Ruff lint
make format            # Ruff format
make ty                # Type check with ty
make static-analysis   # All of the above

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

twsrt-0.3.1.tar.gz (17.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

twsrt-0.3.1-py3-none-any.whl (15.7 kB view details)

Uploaded Python 3

File details

Details for the file twsrt-0.3.1.tar.gz.

File metadata

  • Download URL: twsrt-0.3.1.tar.gz
  • Upload date:
  • Size: 17.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for twsrt-0.3.1.tar.gz
Algorithm Hash digest
SHA256 edc153032812003c7103fbaa282bbf149a9396d24e53766fdb6b5a0d5326784a
MD5 a0636cd888d9d3a50596563fbc44b805
BLAKE2b-256 4d37d30196f5d103a22c89a4399c76a3ffe7a63e5d35c8deee6cff96487bb167

See more details on using hashes here.

File details

Details for the file twsrt-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: twsrt-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 15.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for twsrt-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0a9d346bf1615b8b30b0984524ae35d7d224724d1d87db5b5a67c0fad6d07559
MD5 fc3f34d26d6c324bbd4ea30f1c3449ee
BLAKE2b-256 dca5fc31e16fd11511d655ff7fc907b4f8ef3162b772b68572c1942a7718a144

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page