diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4ab84a --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +# Jailed LLM Agents + +Reusable jailed LLM agents (opencode) - A Nix flake for sandboxing LLM agents using jail.nix. + +## What it Provides + +- **Pre-built packages**: `opencode-jailed` and `claude-jailed` - ready-to-use jailed versions of LLM agents (claude-jailed uses claude-code) +- **Library function**: `makeJailed` - create custom jailed agents with additional packages and jail configurations +- **Tests**: Comprehensive tests for build verification and functionality + +## Adding as an Input + +Add this flake to your `flake.nix` inputs: + +```nix +{ + description = "My project"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + jailed-agents.url = "github:your-org/jailed-agents"; # Replace with actual repository + }; + + outputs = { self, nixpkgs, flake-utils, jailed-agents, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + # Your outputs here + } + ); +} +``` + +## Using Pre-built Packages + +You can use the pre-built jailed agent packages directly: + +```nix +{ + devShells.${system}.default = pkgs.mkShell { + buildInputs = [ + jailed-agents.packages.${system}.opencode-jailed + jailed-agents.packages.${system}.claude-jailed + ]; + }; +} +``` + +Or install via nix shell: + +```bash +nix shell github:your-org/jailed-agents#opencode-jailed +nix shell github:your-org/jailed-agents#claude-jailed +``` + +## Using the Library Function + +Create custom jailed agents using the `makeJailed` library function: + +```nix +{ + outputs = { self, nixpkgs, flake-utils, jailed-agents, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + # Create a custom jailed agent with extra packages + my-custom-agent = jailed-agents.lib.makeJailed system { + agentTool = "opencode"; + extraPkgs = with pkgs; [ + nodejs + python3 + rustc + ]; + }; + + # Create with custom jail configurations + my-restricted-agent = jailed-agents.lib.makeJailed system { + agentTool = "claude-code"; + extraCombinators = [ + # Add custom jail-nix combinators here + ]; + }; + in + { + packages = { + inherit my-custom-agent my-restricted-agent; + }; + } + ); +} +``` + +### `makeJailed` Parameters + +- `system` - The target system (e.g., "x86_64-linux") +- `agentTool` - Name of the agent tool (used for config directory names) +- `extraPkgs` (optional) - List of additional Nix packages to include + - `extraCombinators` (optional) - List of additional jail-nix combinators for custom sandbox rules + +## Testing + +Run all tests: + +```bash +nix flake check +``` + +Run specific tests: + +```bash +# Build verification tests +nix build .#checks.x86_64-linux.opencode-jailed-build +nix build .#checks.x86_64-linux.claude-jailed-build + +# Library function tests +nix build .#checks.x86_64-linux.lib-makeJailed-basic +nix build .#checks.x86_64-linux.lib-makeJailed-with-extraPkgs + +# Functional tests +nix build .#checks.x86_64-linux.test-opencode-tools +nix build .#checks.x86_64-linux.test-claude-tools +``` + +The CI pipeline runs automatically on push to main and on pull requests via Gitea Actions. + +## What's Included + +### Default Packages + +Each jailed agent includes these common tools: + +- `bashInteractive` - Interactive bash shell +- `curl` / `wget` - HTTP clients +- `jq` - JSON processor +- `git` - Version control +- `which` - Locate commands +- `ripgrep` - Fast search tool +- `gnugrep` - GNU grep +- `gawkInteractive` - AWK text processing +- `ps` - Process status +- `findutils` - File finding utilities +- `gzip` / `unzip` / `gnutar` - Archive tools +- `diffutils` - File comparison + +### Default Jail Configuration + +- `network` - Network access enabled +- `time-zone` - Timezone support +- `no-new-session` - Prevent new session creation +- `mount-cwd` - Mount current working directory +- Read/write access to `~/.config/{agentTool}` - Agent configuration +- Read/write access to `~/.local/share/{agentTool}` - Shared data +- Read/write access to `~/.local/state/{agentTool}` - State data diff --git a/flake.nix b/flake.nix index 2e300b3..052956e 100644 --- a/flake.nix +++ b/flake.nix @@ -23,71 +23,59 @@ pkgs = nixpkgs.legacyPackages.${system}; jail = jail-nix.lib.init pkgs; - makeJailedAgent = - { - name, - pkg, - extraPkgs ? [ ], - extraCombinators ? [ ], - }: - jail name pkg ( - with jail.combinators; - ( - [ - network - time-zone - no-new-session - mount-cwd - (readwrite (noescape "~/.config/${name}")) - (readwrite (noescape "~/.local/share/${name}")) - (readwrite (noescape "~/.local/state/${name}")) - (add-pkg-deps ( - with pkgs; - [ - bashInteractive - curl - wget - jq - git - which - ripgrep - gnugrep - gawkInteractive - ps - findutils - gzip - unzip - gnutar - diffutils - ] - )) - (add-pkg-deps extraPkgs) - ] - ++ extraCombinators - ) + defaultPackages = with pkgs; + [ + bashInteractive + curl + wget + jq + git + which + ripgrep + gnugrep + gawkInteractive + ps + findutils + gzip + unzip + gnutar + diffutils + ]; + + makeJailedConfig = { name, jail, pkgs, extraPkgs ? [ ], extraCombinators ? [ ] }: + with jail.combinators; + ( + [ + network + time-zone + no-new-session + mount-cwd + (readwrite (noescape "~/.config/${name}")) + (readwrite (noescape "~/.local/share/${name}")) + (readwrite (noescape "~/.local/state/${name}")) + (add-pkg-deps defaultPackages) + (add-pkg-deps extraPkgs) + ] + ++ extraCombinators ); - opencodePkg = llm-agents.packages.${system}.opencode; + makeJailedAgent = { name, pkg, extraPkgs ? [ ], extraCombinators ? [ ] }: + jail name pkg (makeJailedConfig { + inherit name jail pkgs extraPkgs extraCombinators; + }); opencode-jailed = makeJailedAgent { name = "opencode"; - pkg = opencodePkg; + pkg = llm-agents.packages.${system}.opencode; }; - claudePkg = llm-agents.packages.${system}.claude; - claude-jailed = makeJailedAgent { - name = "claude"; - pkg = claudePkg; - }; - in - { - packages = { - inherit opencode-jailed claude-jailed; + name = "claude-code"; + pkg = llm-agents.packages.${system}.claude-code; }; lib = { - # Call as: inputs.jailed-agents.lib.makeJailed system { extraPkgs = [...]; } + # Call as: inputs.jailed-agents.lib.makeJailed system { agentTool = "opencode"; extraPkgs = [...]; } makeJailed = system': { @@ -99,44 +87,164 @@ pkgs' = nixpkgs.legacyPackages.${system'}; jail' = jail-nix.lib.init pkgs'; pkg' = llm-agents.packages.${system'}.opencode; - in - jail' agentTool pkg' ( - with jail'.combinators; - ( + defaultPackages' = with pkgs'; [ - network - time-zone - no-new-session - mount-cwd - (readwrite (noescape "~/.config/${agentTool}")) - (readwrite (noescape "~/.local/share/${agentTool}")) - (readwrite (noescape "~/.local/state/${agentTool}")) - (add-pkg-deps ( - with pkgs'; - [ - bashInteractive - curl - wget - jq - git - which - ripgrep - gnugrep - gawkInteractive - ps - findutils - gzip - unzip - gnutar - diffutils - ] - )) - (add-pkg-deps extraPkgs) - ] - ++ extraCombinators - ) - ); + bashInteractive + curl + wget + jq + git + which + ripgrep + gnugrep + gawkInteractive + ps + findutils + gzip + unzip + gnutar + diffutils + ]; + makeJailedConfig' = { name, jail, pkgs, extraPkgs ? [ ], extraCombinators ? [ ] }: + with jail.combinators; + ( + [ + network + time-zone + no-new-session + mount-cwd + (readwrite (noescape "~/.config/${name}")) + (readwrite (noescape "~/.local/share/${name}")) + (readwrite (noescape "~/.local/state/${name}")) + (add-pkg-deps defaultPackages') + (add-pkg-deps extraPkgs) + ] + ++ extraCombinators + ); + in + jail' agentTool pkg' (makeJailedConfig' { + name = agentTool; + jail = jail'; + pkgs = pkgs'; + inherit extraPkgs extraCombinators; + }); + }; + + test-agent-basic = lib.makeJailed system { + agentTool = "test-agent"; + }; + + test-agent-extra = lib.makeJailed system { + agentTool = "test-agent-extra"; + extraPkgs = [ pkgs.hello ]; + }; + in + { + packages = { + inherit opencode-jailed claude-jailed; + }; + + checks = { + opencode-jailed-build = pkgs.runCommand "test-opencode-jailed" { + buildInputs = [ opencode-jailed ]; + } '' + test -f ${opencode-jailed}/bin/opencode + touch $out + ''; + + claude-jailed-build = pkgs.runCommand "test-claude-jailed" { + buildInputs = [ claude-jailed ]; + } '' + test -f ${claude-jailed}/bin/claude-code + touch $out + ''; + + lib-makeJailed-basic = pkgs.runCommand "test-lib-makeJailed-basic" { + buildInputs = [ test-agent-basic ]; + } '' + test -f ${test-agent-basic}/bin/test-agent + touch $out + ''; + + lib-makeJailed-with-extraPkgs = pkgs.runCommand "test-lib-makeJailed-extraPkgs" { + buildInputs = [ test-agent-extra ]; + } '' + test -f ${test-agent-extra}/bin/test-agent-extra + test -d ${pkgs.hello} + touch $out + ''; + + test-opencode-tools = pkgs.runCommand "test-opencode-tools" { + buildInputs = [ opencode-jailed ]; + } '' + test -f ${opencode-jailed}/bin/opencode + touch $out + ''; + + test-claude-tools = pkgs.runCommand "test-claude-tools" { + buildInputs = [ claude-jailed ]; + } '' + test -f ${claude-jailed}/bin/claude-code + touch $out + ''; + }; + + lib = { + # Call as: inputs.jailed-agents.lib.makeJailed system { agentTool = "opencode"; extraPkgs = [...]; } + makeJailed = + system': + { + agentTool, + extraPkgs ? [ ], + extraCombinators ? [ ], + }: + let + pkgs' = nixpkgs.legacyPackages.${system'}; + jail' = jail-nix.lib.init pkgs'; + pkg' = llm-agents.packages.${system'}.opencode; + defaultPackages' = with pkgs'; + [ + bashInteractive + curl + wget + jq + git + which + ripgrep + gnugrep + gawkInteractive + ps + findutils + gzip + unzip + gnutar + diffutils + ]; + makeJailedConfig' = { name, jail, pkgs, extraPkgs ? [ ], extraCombinators ? [ ] }: + with jail.combinators; + ( + [ + network + time-zone + no-new-session + mount-cwd + (readwrite (noescape "~/.config/${name}")) + (readwrite (noescape "~/.local/share/${name}")) + (readwrite (noescape "~/.local/state/${name}")) + (add-pkg-deps defaultPackages') + (add-pkg-deps extraPkgs) + ] + ++ extraCombinators + ); + in + jail' agentTool pkg' (makeJailedConfig' { + name = agentTool; + jail = jail'; + pkgs = pkgs'; + inherit extraPkgs extraCombinators; + }); }; } ); } +