diff --git a/modules/misc/news/2026/02/2026-02-13_12-21-10.nix b/modules/misc/news/2026/02/2026-02-13_12-21-10.nix new file mode 100644 index 00000000..bdab8f0d --- /dev/null +++ b/modules/misc/news/2026/02/2026-02-13_12-21-10.nix @@ -0,0 +1,11 @@ +{ + time = "2026-02-13T11:21:10+00:00"; + condition = true; + message = '' + + A new module is available: 'programs.tirith'. + + Tirith is a shell security monitor that helps protect against + malicious commands by analyzing shell inputs before execution. + ''; +} diff --git a/modules/programs/tirith.nix b/modules/programs/tirith.nix new file mode 100644 index 00000000..d8703d3e --- /dev/null +++ b/modules/programs/tirith.nix @@ -0,0 +1,84 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.programs.tirith; + yamlFormat = pkgs.formats.yaml { }; +in +{ + meta.maintainers = with lib.maintainers; [ malik ]; + + options.programs.tirith = { + enable = lib.mkEnableOption "Tirith, a shell security monitor"; + + package = lib.mkPackageOption pkgs "tirith" { }; + + allowlist = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + apply = builtins.filter (s: s != ""); + example = [ + "localhost" + ]; + description = '' + List of allowed domains that bypass Tirith analysis. + Written to `$XDG_CONFIG_HOME/tirith/allowlist`. + ''; + }; + + policy = lib.mkOption { + type = yamlFormat.type; + default = { }; + example = lib.literalExpression '' + { + version = 1; + fail_mode = "open"; + allow_bypass = true; + severity_overrides = { + docker_untrusted_registry = "critical"; + }; + } + ''; + description = '' + Tirith policy configuration. + Written to `$XDG_CONFIG_HOME/tirith/policy.yaml`. + + See + for policy examples. + ''; + }; + + enableBashIntegration = lib.hm.shell.mkBashIntegrationOption { inherit config; }; + enableFishIntegration = lib.hm.shell.mkFishIntegrationOption { inherit config; }; + enableZshIntegration = lib.hm.shell.mkZshIntegrationOption { inherit config; }; + }; + + config = lib.mkIf cfg.enable { + home.packages = [ cfg.package ]; + + xdg.configFile = { + "tirith/allowlist" = lib.mkIf (cfg.allowlist != [ ]) { + text = (lib.concatStringsSep "\n" cfg.allowlist) + "\n"; + }; + + "tirith/policy.yaml" = lib.mkIf (cfg.policy != { }) { + source = yamlFormat.generate "tirith-policy.yaml" cfg.policy; + }; + }; + + programs.bash.initExtra = lib.mkIf cfg.enableBashIntegration '' + eval "$(${lib.getExe cfg.package} init --shell bash)" + ''; + + programs.fish.interactiveShellInit = lib.mkIf cfg.enableFishIntegration '' + ${lib.getExe cfg.package} init --shell fish | source + ''; + + programs.zsh.initExtra = lib.mkIf cfg.enableZshIntegration '' + eval "$(${lib.getExe cfg.package} init --shell zsh)" + ''; + }; +} diff --git a/tests/modules/programs/tirith/basic.nix b/tests/modules/programs/tirith/basic.nix new file mode 100644 index 00000000..dc2451a4 --- /dev/null +++ b/tests/modules/programs/tirith/basic.nix @@ -0,0 +1,27 @@ +{ ... }: + +{ + programs.tirith = { + enable = true; + allowlist = [ + "localhost" + "example.com" + ]; + policy = { + version = 1; + fail_mode = "open"; + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/tirith/allowlist + assertFileContent \ + home-files/.config/tirith/allowlist \ + ${builtins.toFile "expected" '' + localhost + example.com + ''} + + assertFileExists home-files/.config/tirith/policy.yaml + ''; +} diff --git a/tests/modules/programs/tirith/default.nix b/tests/modules/programs/tirith/default.nix new file mode 100644 index 00000000..a5a837c9 --- /dev/null +++ b/tests/modules/programs/tirith/default.nix @@ -0,0 +1,3 @@ +{ + tirith-basic = ./basic.nix; +}