NixOS / nix

Nix, the purely functional package manager
https://nixos.org/
GNU Lesser General Public License v2.1
12.8k stars 1.52k forks source link

Poison pill string context #8388

Open roberth opened 1 year ago

roberth commented 1 year ago

Is your feature request related to a problem? Please describe.

Some values should not be used, or at least not in certain use cases. This can be correctly modeled with the string context. Doing so makes the language safer and more reproducible, and let us implement features relying on these properties confidently.

Specific use cases so far:

Describe the solution you'd like

Extend StringContextElem with constructors for use cases such as the above. Implement restrictions in the primops, and in the CLI.

Change builtins.unsafeDiscardStringContext to only discard derivation context items. Stripping of context should be done with specific usages in mind, and current usages do not consider confidentiality for example. By stripping everything, it would be easy to leak a secret into a derivation name for example, as those are stripped of context by the name sanitizing function.

Perhaps have a primop builtins.unsafeExposeSecret to strip confidentiality context anyway. Not all secrets are highly valuable, so careful application makes the system more useful. It should be possible to disable this function, so that more security focused users still have the absolute guarantee.

Describe alternatives you've considered

Exception messages. Exception messages could perhaps be handled by a function a -> (String -> String) -> a, such that the original message is only available within the callback.

Hiding things voluntarily. Something object capability style could be leveraged inside the Nix language; not something that's been done yet afaik. Speaking of "exploit", we could use the method inequality hack for this.

Use method inequality hack to enforce control over secrets unpacking ``` nix-repl> secrets = let t = mkToken ""; in { mkSecret = s: t2: assert t == t2; s; key = t; } nix-repl> example = secrets.mkSecret "hi" # Try to make a mistake nix-repl> toString example error: cannot coerce a function to a string at «string»:1:1: 1| toString example | ^ nix-repl> "${example}" error: cannot coerce a function to a string at «string»:1:2: 1| "${example}" | ^ # Try to attack it nix-repl> example { token = x: x; } error: assertion '(t == t2)' failed at «string»:1:45: 1| let t = mkToken ""; in { mkSecret = s: t2: assert t == t2; s; key = t; } | ^ # Can access with the key nix-repl> example secrets.key "hi" ```

This is kind of neat, but a gimmick, as management of the "key" burdens the user, while not really guaranteeing that the secret isn't written to the store, as it always has to be unpacked and protections won't apply after that; after any logic that was designed for plain strings. tl;dr need less inconvenience, more safety.

Additional context

Priorities

Add :+1: to issues you find important.

roberth commented 1 year ago

fromJSON should preserve context, although this is not really possible without also allowing the exposure of attribute names, the number of items in lists, and the values of numbers, bools, and null. While this is not great, a possible remedy is to convert non-string primitive types to strings, which is lossy, but possibly quite rare in secret json files that have to be parsed by Nix. This could be done in a Nix library by calling unsafeExposeSecret and appendContext. The library should inherit the "unsafe" and/or "expose" parts of the function name.

A more correct solution would be to make context an implicit wrapper for any type, but that's a very significant change that I wouldn't want to discuss before extra string contexts are implemented and learned from.