From 65532e83870fc678ec0f53260ab155640493e5a8 Mon Sep 17 00:00:00 2001 From: iris Date: Sat, 23 May 2026 13:36:22 +0200 Subject: [PATCH] frontend: tighten extraFiles target type to strMatching regex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit damocles suggested using lib.types.strMatching for the target option itself rather than relying solely on the post-hoc assertion. Pattern: `^[A-Za-z0-9_][A-Za-z0-9_./-]*$` — first char alphanumeric/_, then alphanumerics + _ + . + / + - allowed (so nested layouts like "games/bitburner" still work). This rejects at type-check time: - leading `/` (absolute paths) - leading `.` (so `..` as a full string blocked, also `./foo`) - leading `-` (would parse as flag by some tools) - spaces, control chars, weird unicode The existing assertion stays — it catches mid-path `..` segments (`foo/../bar`) that the regex can't reject without lookahead. POSIX regex (which nix uses) doesn't support lookahead, so the type-and-assertion split is the cleanest expression. Refs #273. --- nix/templates/harness-base.nix | 36 ++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) 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. ''; } ];