diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 2b859a8..1ae8c3f 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -192,7 +192,15 @@ ''; }; target = lib.mkOption { - type = lib.types.str; + # First char must be alphanumeric/underscore (rules out + # leading `/`, leading `.`, leading `-`); inner chars + # include `.` and `/` so nested layouts like + # `"games/bitburner"` work. This is the shape check — + # the `..`-segment traversal check is the assertion in + # `config.assertions` below (regex alone can't reject + # mid-path `..` segments without lookahead, which nix + # POSIX regex doesn't support). + type = lib.types.strMatching "^[A-Za-z0-9_][A-Za-z0-9_./-]*$"; default = name; defaultText = lib.literalMD "the attribute name"; description = '' @@ -201,6 +209,11 @@ the on-disk layout in the merged derivation. Defaults to the attribute name. Use forward slashes for nested layouts (e.g. `"games/bitburner"`). + + Constrained shape: must start with an alphanumeric or + `_`, and only contain alphanumerics, `_`, `.`, `/`, + `-`. `..` segments are separately rejected at config + eval time. ''; }; }; @@ -452,21 +465,20 @@ message = "hyperhive.icon must point to an .svg file"; } # hyperhive.frontend.extraFiles[*].target is concatenated into - # $out during the mergedDist build. Reject path traversal - # (`..` segments) and absolute paths (leading `/`) — both would - # let an entry escape the static dir. agent.nix is operator- - # reviewed, so this is belt-and-braces, but it's the kind of - # mistake that's easy to make and hard to spot during review. + # $out during the mergedDist build. The option's strMatching + # type already rejects leading `/`, leading `.`, and the + # weirder characters; this assertion catches mid-path `..` + # segments (e.g. `foo/../etc/passwd`) that the type's regex + # can't easily express without lookahead. agent.nix is + # operator-reviewed, so this is belt-and-braces — but it's the + # kind of mistake that's easy to make and hard to spot. { assertion = lib.all ( - entry: - !(lib.hasPrefix "/" entry.target) - && !(builtins.any (seg: seg == "..") (lib.splitString "/" entry.target)) + entry: !(builtins.any (seg: seg == "..") (lib.splitString "/" entry.target)) ) (lib.attrValues config.hyperhive.frontend.extraFiles); message = '' - hyperhive.frontend.extraFiles: `target` must be a relative - path inside the static dir — no leading `/`, no `..` - segments. + hyperhive.frontend.extraFiles: `target` must not contain + `..` path segments. ''; } ];