Skip to content

Commit eb057a0

Browse files
authored
Merge pull request #890 from Yo-DDV/fix/nix-remote-control-environment-file
fix(nix): reject store-backed remote control environment files
2 parents 2e04d92 + c4b69c4 commit eb057a0

4 files changed

Lines changed: 208 additions & 4 deletions

File tree

docs/nix.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ in
144144
}
145145
```
146146

147+
Set `remoteControl.environmentFile` to a quoted absolute runtime path such as
148+
`"/run/secrets/codex-remote-control.env"`. Prefix it with `-` only when systemd
149+
should ignore a missing file. Empty, relative, non-canonical, Nix-context, and
150+
store-backed paths are rejected. Do not interpolate a path containing secrets:
151+
Nix can copy it into the store before module validation rejects the
152+
configuration. The referenced runtime file must be readable by the user service
153+
and should remain owner-only.
154+
147155
Pinning `github:sadjow/codex-cli-nix` to a release tag or commit is
148156
recommended for fully reproducible configurations.
149157

nix/home-manager-module.nix

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
let
99
cfg = config.programs.codexDesktopLinux;
1010
remoteCfg = cfg.remoteControl;
11+
remoteEnvironmentFilePath =
12+
if remoteCfg.environmentFile == null then null else lib.removePrefix "-" remoteCfg.environmentFile;
13+
remoteEnvironmentFileSegments =
14+
if remoteEnvironmentFilePath == null then [ ] else lib.drop 1 (lib.splitString "/" remoteEnvironmentFilePath);
15+
remoteEnvironmentFileIsCanonical =
16+
remoteEnvironmentFilePath != null
17+
&& lib.hasPrefix "/" remoteEnvironmentFilePath
18+
&& lib.all (segment: segment != "" && segment != "." && segment != "..") remoteEnvironmentFileSegments;
1119
system = pkgs.stdenv.hostPlatform.system;
1220
flakePackages = self.packages.${system};
1321
linuxFeatures = import ./linux-features.nix { inherit lib; };
@@ -187,11 +195,14 @@ in
187195
};
188196

189197
environmentFile = lib.mkOption {
190-
type = lib.types.nullOr lib.types.path;
198+
type = lib.types.nullOr lib.types.str;
191199
default = null;
192200
example = "/run/secrets/codex-remote-control.env";
193201
description = ''
194-
Additional environment file as defined in {manpage}`systemd.exec(5)`.
202+
Runtime path to an additional environment file as defined in
203+
{manpage}`systemd.exec(5)`. Use a quoted runtime string. Nix path
204+
literals or interpolations can copy contents into the Nix store
205+
before module validation; store-backed values are rejected.
195206
'';
196207
};
197208

@@ -236,6 +247,28 @@ in
236247
assertion = !remoteCfg.enable || pkgs.stdenv.hostPlatform.isLinux;
237248
message = "`programs.codexDesktopLinux.remoteControl.enable` is only supported on Linux";
238249
}
250+
{
251+
assertion =
252+
remoteCfg.environmentFile == null
253+
|| (!builtins.hasContext remoteCfg.environmentFile && remoteEnvironmentFileIsCanonical);
254+
message = ''
255+
`programs.codexDesktopLinux.remoteControl.environmentFile` must be an
256+
absolute canonical runtime path without Nix store context, optionally
257+
prefixed with `-`
258+
'';
259+
}
260+
{
261+
assertion =
262+
remoteCfg.environmentFile == null
263+
|| (
264+
remoteEnvironmentFilePath != builtins.storeDir
265+
&& !lib.hasPrefix "${builtins.storeDir}/" remoteEnvironmentFilePath
266+
);
267+
message = ''
268+
`programs.codexDesktopLinux.remoteControl.environmentFile` must be a
269+
runtime path outside the Nix store
270+
'';
271+
}
239272
];
240273

241274
home.packages = [

nix/linux-features-test.nix

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,32 @@ let
140140
mkdir -p "$out"
141141
'';
142142
customConfig = combinedConfig // { package = customPackage; };
143+
remoteControlConfig = {
144+
enable = true;
145+
package = customPackage;
146+
remoteControl = {
147+
enable = true;
148+
package = pkgs.writeShellScriptBin "codex" "exit 0";
149+
environmentFile = "/run/secrets/codex-remote-control.env";
150+
};
151+
};
152+
remoteControlConfigWithEnvironmentFile = environmentFile:
153+
remoteControlConfig
154+
// {
155+
remoteControl = remoteControlConfig.remoteControl // { inherit environmentFile; };
156+
};
157+
homeRemoteService =
158+
(evalHomeManager remoteControlConfig).config.systemd.user.services.codex-remote-control;
159+
nixosRemoteService =
160+
(evalNixOS remoteControlConfig).config.systemd.user.services.codex-remote-control;
161+
optionalHomeRemoteService =
162+
(evalHomeManager (
163+
remoteControlConfigWithEnvironmentFile "-/run/secrets/codex-remote-control.env"
164+
)).config.systemd.user.services.codex-remote-control;
165+
optionalNixOSRemoteService =
166+
(evalNixOS (
167+
remoteControlConfigWithEnvironmentFile "-/run/secrets/codex-remote-control.env"
168+
)).config.systemd.user.services.codex-remote-control;
143169

144170
invalidBuilder = builtins.tryEval (
145171
(packages.codex-desktop.override {
@@ -162,6 +188,74 @@ let
162188
}).config.environment.systemPackages
163189
true
164190
);
191+
invalidHomeManagerEnvironmentFile = builtins.tryEval (
192+
builtins.deepSeq
193+
(evalHomeManager (
194+
remoteControlConfigWithEnvironmentFile ./linux-features-test.nix
195+
)).config.systemd.user.services.codex-remote-control
196+
true
197+
);
198+
invalidNixOSEnvironmentFile = builtins.tryEval (
199+
builtins.deepSeq
200+
(evalNixOS (
201+
remoteControlConfigWithEnvironmentFile ./linux-features-test.nix
202+
)).config.systemd.user.services.codex-remote-control
203+
true
204+
);
205+
storeEnvironmentFiles = [
206+
"${./linux-features-test.nix}"
207+
"-${./linux-features-test.nix}"
208+
];
209+
invalidRuntimeEnvironmentFiles = [
210+
""
211+
"secrets.env"
212+
"-secrets.env"
213+
"//nix/store/example-secret"
214+
"/run/../nix/store/example-secret"
215+
"/nix//store/example-secret"
216+
"/run/secrets/./codex-remote-control.env"
217+
"/run/secrets/"
218+
];
219+
contextEnvironmentFiles = [
220+
"/run/secrets/${./linux-features-test.nix}"
221+
"-/run/secrets/${./linux-features-test.nix}"
222+
];
223+
homeManagerStoreEnvironmentFileAssertions = map (
224+
environmentFile:
225+
(evalHomeManager (
226+
remoteControlConfigWithEnvironmentFile environmentFile
227+
)).config.assertions
228+
) storeEnvironmentFiles;
229+
nixosStoreEnvironmentFileAssertions = map (
230+
environmentFile:
231+
(evalNixOS (
232+
remoteControlConfigWithEnvironmentFile environmentFile
233+
)).config.assertions
234+
) storeEnvironmentFiles;
235+
homeManagerRuntimeEnvironmentFileAssertions = map (
236+
environmentFile:
237+
(evalHomeManager (
238+
remoteControlConfigWithEnvironmentFile environmentFile
239+
)).config.assertions
240+
) invalidRuntimeEnvironmentFiles;
241+
nixosRuntimeEnvironmentFileAssertions = map (
242+
environmentFile:
243+
(evalNixOS (
244+
remoteControlConfigWithEnvironmentFile environmentFile
245+
)).config.assertions
246+
) invalidRuntimeEnvironmentFiles;
247+
homeManagerContextEnvironmentFileAssertions = map (
248+
environmentFile:
249+
(evalHomeManager (
250+
remoteControlConfigWithEnvironmentFile environmentFile
251+
)).config.assertions
252+
) contextEnvironmentFiles;
253+
nixosContextEnvironmentFileAssertions = map (
254+
environmentFile:
255+
(evalNixOS (
256+
remoteControlConfigWithEnvironmentFile environmentFile
257+
)).config.assertions
258+
) contextEnvironmentFiles;
165259
in
166260
assert lib.assertMsg
167261
(linuxFeatures.normalize testFeatureIds == normalizedTestFeatureIds)
@@ -196,6 +290,42 @@ assert lib.assertMsg
196290
assert lib.assertMsg (!invalidBuilder.success) "the package builder accepted an unsupported feature";
197291
assert lib.assertMsg (!invalidHomeManager.success) "Home Manager accepted an unsupported feature";
198292
assert lib.assertMsg (!invalidNixOS.success) "NixOS accepted an unsupported feature";
293+
assert lib.assertMsg
294+
(homeRemoteService.Service.EnvironmentFile == "/run/secrets/codex-remote-control.env")
295+
"Home Manager changed the runtime remote-control environment-file path";
296+
assert lib.assertMsg
297+
(nixosRemoteService.serviceConfig.EnvironmentFile == "/run/secrets/codex-remote-control.env")
298+
"NixOS changed the runtime remote-control environment-file path";
299+
assert lib.assertMsg
300+
(optionalHomeRemoteService.Service.EnvironmentFile == "-/run/secrets/codex-remote-control.env")
301+
"Home Manager rejected or changed an optional absolute environment-file path";
302+
assert lib.assertMsg
303+
(optionalNixOSRemoteService.serviceConfig.EnvironmentFile == "-/run/secrets/codex-remote-control.env")
304+
"NixOS rejected or changed an optional absolute environment-file path";
305+
assert lib.assertMsg
306+
(!invalidHomeManagerEnvironmentFile.success)
307+
"Home Manager accepted a Nix path that can copy remote-control secrets into the store";
308+
assert lib.assertMsg
309+
(!invalidNixOSEnvironmentFile.success)
310+
"NixOS accepted a Nix path that can copy remote-control secrets into the store";
311+
assert lib.assertMsg
312+
(lib.all (assertions: !lib.all (item: item.assertion) assertions) homeManagerStoreEnvironmentFileAssertions)
313+
"Home Manager accepted a store path for the remote-control environment file";
314+
assert lib.assertMsg
315+
(lib.all (assertions: !lib.all (item: item.assertion) assertions) nixosStoreEnvironmentFileAssertions)
316+
"NixOS accepted a store path for the remote-control environment file";
317+
assert lib.assertMsg
318+
(lib.all (assertions: !lib.all (item: item.assertion) assertions) homeManagerRuntimeEnvironmentFileAssertions)
319+
"Home Manager accepted an empty, relative, or non-canonical remote-control environment-file path";
320+
assert lib.assertMsg
321+
(lib.all (assertions: !lib.all (item: item.assertion) assertions) nixosRuntimeEnvironmentFileAssertions)
322+
"NixOS accepted an empty, relative, or non-canonical remote-control environment-file path";
323+
assert lib.assertMsg
324+
(lib.all (assertions: !lib.all (item: item.assertion) assertions) homeManagerContextEnvironmentFileAssertions)
325+
"Home Manager accepted a context-bearing remote-control environment-file path";
326+
assert lib.assertMsg
327+
(lib.all (assertions: !lib.all (item: item.assertion) assertions) nixosContextEnvironmentFileAssertions)
328+
"NixOS accepted a context-bearing remote-control environment-file path";
199329
pkgs.runCommand "nix-linux-features-evaluation" { } ''
200330
touch "$out"
201331
''

nix/nixos-module.nix

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
let
99
cfg = config.programs.codexDesktopLinux;
1010
remoteCfg = cfg.remoteControl;
11+
remoteEnvironmentFilePath =
12+
if remoteCfg.environmentFile == null then null else lib.removePrefix "-" remoteCfg.environmentFile;
13+
remoteEnvironmentFileSegments =
14+
if remoteEnvironmentFilePath == null then [ ] else lib.drop 1 (lib.splitString "/" remoteEnvironmentFilePath);
15+
remoteEnvironmentFileIsCanonical =
16+
remoteEnvironmentFilePath != null
17+
&& lib.hasPrefix "/" remoteEnvironmentFilePath
18+
&& lib.all (segment: segment != "" && segment != "." && segment != "..") remoteEnvironmentFileSegments;
1119
system = pkgs.stdenv.hostPlatform.system;
1220
flakePackages = self.packages.${system};
1321
linuxFeatures = import ./linux-features.nix { inherit lib; };
@@ -185,11 +193,14 @@ in
185193
};
186194

187195
environmentFile = lib.mkOption {
188-
type = lib.types.nullOr lib.types.path;
196+
type = lib.types.nullOr lib.types.str;
189197
default = null;
190198
example = "/run/secrets/codex-remote-control.env";
191199
description = ''
192-
Additional environment file as defined in {manpage}`systemd.exec(5)`.
200+
Runtime path to an additional environment file as defined in
201+
{manpage}`systemd.exec(5)`. Use a quoted runtime string. Nix path
202+
literals or interpolations can copy contents into the Nix store
203+
before module validation; store-backed values are rejected.
193204
'';
194205
};
195206

@@ -235,6 +246,28 @@ in
235246
assertion = !remoteCfg.enable || pkgs.stdenv.hostPlatform.isLinux;
236247
message = "`programs.codexDesktopLinux.remoteControl.enable` is only supported on Linux";
237248
}
249+
{
250+
assertion =
251+
remoteCfg.environmentFile == null
252+
|| (!builtins.hasContext remoteCfg.environmentFile && remoteEnvironmentFileIsCanonical);
253+
message = ''
254+
`programs.codexDesktopLinux.remoteControl.environmentFile` must be an
255+
absolute canonical runtime path without Nix store context, optionally
256+
prefixed with `-`
257+
'';
258+
}
259+
{
260+
assertion =
261+
remoteCfg.environmentFile == null
262+
|| (
263+
remoteEnvironmentFilePath != builtins.storeDir
264+
&& !lib.hasPrefix "${builtins.storeDir}/" remoteEnvironmentFilePath
265+
);
266+
message = ''
267+
`programs.codexDesktopLinux.remoteControl.environmentFile` must be a
268+
runtime path outside the Nix store
269+
'';
270+
}
238271
];
239272

240273
environment.systemPackages = [

0 commit comments

Comments
 (0)