{ pkgs, lib, config, ... }: let inherit (lib) mkOption mkPackageOption types; strNotEmpty = s: lib.stringLength s != 0; wrapperType = lib.types.submodule { options = { package = mkPackageOption "wrapped" { } { nullable = false; }; executable = mkOption { type = types.str; default = ""; }; args = mkOption { type = types.listOf types.str; default = [ ]; }; env = mkOption { type = types.attrsOf (types.listOf types.str); default = { }; }; retainEnv = mkOption { type = types.bool; default = false; }; addPwd = mkOption { type = types.bool; default = false; }; pathRules = mkOption { type = types.listOf types.str; default = [ ]; }; tcpRules = mkOption { type = types.listOf types.str; default = [ ]; }; unrestrictTcp = mkOption { type = types.bool; default = false; }; unrestrictSockets = mkOption { type = types.bool; default = false; }; unrestrictSignals = mkOption { type = types.bool; default = false; }; }; }; in { options = { security.wrappers = mkOption { type = types.attrsOf wrapperType; }; security.wrapperPkg = mkPackageOption "wrapper" { } { nullable = false; }; }; config = let wrap = name: opts: let envs = lib.concatStringsSep " " ( lib.mapAttrsToList (n: v: "${n}=${lib.concatStringsSep ":" v}") opts.env ); extraPaths = lib.concatStringsSep " " opts.pathRules; tcpRules = lib.concatStringsSep " " opts.tcpRules; sandboxArgs = pkgs.stdenvNoCC.mkDerivation { name = "${name}-opts"; __structuredAttrs = true; exportReferencesGraph.closure = [ opts.package ]; preferLocalBuild = true; nativeBuildInputs = [ pkgs.coreutils pkgs.jq ]; buildCommand = '' echo -n "--fs rx=" > $out jq -r '.closure[].path' < "$NIX_ATTRS_JSON_FILE" \ | tr '\n' ':' | sed 's/:$//' >> $out ${lib.optionalString (lib.length opts.pathRules != 0) "echo -n ' ${extraPaths}' >> $out"} ${lib.optionalString (lib.length opts.tcpRules != 0) "echo -n ' --tcp ${tcpRules}' >> $out"} ${lib.optionalString (strNotEmpty envs) "echo -n ' --env ${envs}' >> $out"} ''; }; command = lib.getExe' opts.package ( if (strNotEmpty opts.executable) then opts.executable else opts.package.pname ); wrappedArgs = lib.concatStringsSep " " opts.args; script = '' #! /usr/bin/env bash ${lib.getExe' config.security.wrapperPkg "yoke"} \ ${lib.optionalString opts.addPwd "--fs rwx=$PWD"} \ ${lib.optionalString opts.retainEnv "--retain-env"} \ ${lib.optionalString opts.unrestrictTcp "--no-tcp"} \ ${lib.optionalString opts.unrestrictSockets "--sockets"} \ ${lib.optionalString opts.unrestrictSignals "--signals"} \ --fd-args -- \ ${command} \ ${wrappedArgs} $@ \ 3< ${sandboxArgs} ''; in pkgs.writeScriptBin "${name}" script; in { environment.systemPackages = lib.mapAttrsToList wrap config.security.wrappers; }; }