I'm surprised to see that very little is known about the Linux kernel keyring. With keyctl[0] you can put secrets scoped to process and ensure that they stay there only for a limited period of time. The tool isn't intuitive, but it's the way I put my 2FA and secrets in shell without bothering about leaking anything.
It's definitely a powerful approach; I don't think it's particularly viable for the sort of use cases where you're throwing secrets around in a shell:
- It's not supported natively by most software (if I wanted to use it with `curl` for example, it would only be able to replace the `rbw` example since I still need to pass the secret to curl somehow);
- I don't think it's likely to gain widespread adoption, due to being a linux-specific API;
- The API itself suffers from some poor design choices, imho; it is not currently possible to set an expiry on a keyring entry without an intermediate state where the data is loaded but no expiry is set: https://lore.kernel.org/keyrings/ygar0hbrm05.fsf@localhost/T...
It's really nice as a concept and when you're developing an application where you control the entire flow of the secret data, but I don't see much practical value in it for general use cases. Exposing it as a filesystem could be a potential bridge for application support (something like `curl -H @</proc/self/keyring/@u/gitlab-authorization-header`?), though I suspect that wouldn't fly upstream because files aren't generally treated as carefully as explicit secret things. Non-enumerability (`-r` on `/proc/self/keying` and `/proc/self/keyring/*`) would help here, but I still seriously doubt that the keyring maintainers would find this to be a sane proposition :)
This article does not mention that environment variables are also visible by process in /proc/*/environ (which has restrictive permissions, but is completely visible to root).
PuTTY has added a -pwfile option for use in ssh. If not exported, this interface is likely the best for non-key batch use. It seems much superior to sshpass.
The old .netrc format can be adapted for storage (which appears popular for curl), but I prefer sqlite databases, with permissions removed for all but the owner.
> This article does not mention that environment variables are also visible by process in /proc/*/environ (which has restrictive permissions, but is completely visible to root).
What isn't visible to root? Maybe if you're willing to go down a really deep rabbit hole you can play that game, but I would generally explicitly exclude root from my threat model.
Defense in depth. Malware is software programmed to do a number of things, not all possible things (well at least until the attacker gets a shell, which is rather noisy). Scanning env vars is trivial, scanning the entire file system and traversing mount points is a bit harder, traversing all memory and guessing what’s a secret is a hell lot harder even for an interactive attacker. If you happen to include some malicious library doing dragnet mining and exfilatration of secrets, you’re more likely to dodge a bullet if you don’t have secrets in env vars than if you do.
> This article does not mention that environment variables are also visible by process in /proc/*/environ (which has restrictive permissions, but is completely visible to root).
He's explicitly not using export, so they won't show up there. Plain variables are not in the environment.
(it's good to bring up this file as well as getting inherited by child processes though)
I believe that unexported shell variables will be visible in /proc/*/mem, so it would be prudent to overwrite then unset them as soon as reasonably possible in their usage.
As pointed out by evgpbfhnr, I do avoid using environment variables and justify it (though with different reasoning than yours).
Your justification is the kind of thing I mention as out-of-scope (for my purposes!) in my conclusion:
> There are also many bases that I don’t cover and routes through which sufficiently-smart malware could easily still obtain the secrets I’m working with.
/proc/$pid/environ, /proc/$pid/mem and other such vectors (ptrace, bpftrace, equivalents on other platforms) are real, but:
- they're not vectors of _accidental_ leakage like dumping the full process environment to logs or shell history are
- they rely on privileged access existing at the time that I'm handling the secret, while logs or shell history can be obtained _in the future_
- they're not the kind of thing I expect broad-spectrum malware to go rooting for: the memory of all processes is a lot of data to classify/exfiltrate, and if I were a malware author I'd fear that that would be far too resource-intensive and thus conspicuous. Browser cookie storage, password manager databases, keylogging, and the like are much easier and more valuable pickings.
I think single-secret files and filesystem permissions are superior between the presented options.
You don't need root to do what rootless podman does and create and work in directories that processes spawned from your normal user can't normally read using subuids. tmpfs to keep it off actual disks.
I'm sure other languages have equivalents but I rarely see this.. for example I was about to say serde doesn't do it, but it looks like it's possible with a wrapper type? https://docs.rs/redactrs/latest/redactrs/
Anyway, this kind of tagging is good, I want more!
> But I definitely feel a lot more comfortable when secrets are never written to persistent unencrypted files, and being aware of these leakage vectors is helpful to avoid that!
It is very common for people to set environment variables for a server process from a config file that is readable by the application which is a bigger problem. At least put them a file that is only root readable (and have the process started by root).
Lately I've been using my desktop keyring/wallet to store the secrets encrypted at rest. Then on login they get injected to my shell directly from the secure storage (unlocked at login).
I feel this is probably better than plain text, but if my machine gets popped while logged on you likely have Access to active browser sessions between MFA flows and could do more damage that way.
[0]: https://www.man7.org/linux/man-pages/man1/keyctl.1.html
- It's not supported natively by most software (if I wanted to use it with `curl` for example, it would only be able to replace the `rbw` example since I still need to pass the secret to curl somehow);
- I don't think it's likely to gain widespread adoption, due to being a linux-specific API;
- The API itself suffers from some poor design choices, imho; it is not currently possible to set an expiry on a keyring entry without an intermediate state where the data is loaded but no expiry is set: https://lore.kernel.org/keyrings/ygar0hbrm05.fsf@localhost/T...
It's really nice as a concept and when you're developing an application where you control the entire flow of the secret data, but I don't see much practical value in it for general use cases. Exposing it as a filesystem could be a potential bridge for application support (something like `curl -H @</proc/self/keyring/@u/gitlab-authorization-header`?), though I suspect that wouldn't fly upstream because files aren't generally treated as carefully as explicit secret things. Non-enumerability (`-r` on `/proc/self/keying` and `/proc/self/keyring/*`) would help here, but I still seriously doubt that the keyring maintainers would find this to be a sane proposition :)
PuTTY has added a -pwfile option for use in ssh. If not exported, this interface is likely the best for non-key batch use. It seems much superior to sshpass.
The old .netrc format can be adapted for storage (which appears popular for curl), but I prefer sqlite databases, with permissions removed for all but the owner.
What isn't visible to root? Maybe if you're willing to go down a really deep rabbit hole you can play that game, but I would generally explicitly exclude root from my threat model.
He's explicitly not using export, so they won't show up there. Plain variables are not in the environment.
(it's good to bring up this file as well as getting inherited by child processes though)
Your justification is the kind of thing I mention as out-of-scope (for my purposes!) in my conclusion:
> There are also many bases that I don’t cover and routes through which sufficiently-smart malware could easily still obtain the secrets I’m working with.
/proc/$pid/environ, /proc/$pid/mem and other such vectors (ptrace, bpftrace, equivalents on other platforms) are real, but:
- they're not vectors of _accidental_ leakage like dumping the full process environment to logs or shell history are
- they rely on privileged access existing at the time that I'm handling the secret, while logs or shell history can be obtained _in the future_
- they're not the kind of thing I expect broad-spectrum malware to go rooting for: the memory of all processes is a lot of data to classify/exfiltrate, and if I were a malware author I'd fear that that would be far too resource-intensive and thus conspicuous. Browser cookie storage, password manager databases, keylogging, and the like are much easier and more valuable pickings.
It helps with pasting special chars, newlines, and remote sessions without access to the local clipboard.
https://manpages.debian.org/jessie/moreutils/vipe.1You don't need root to do what rootless podman does and create and work in directories that processes spawned from your normal user can't normally read using subuids. tmpfs to keep it off actual disks.
facet (rust) allows tagging fields as sensitive so they won't show up in logs: https://facet.rs/guide/attributes/#sensitive
I'm sure other languages have equivalents but I rarely see this.. for example I was about to say serde doesn't do it, but it looks like it's possible with a wrapper type? https://docs.rs/redactrs/latest/redactrs/
Anyway, this kind of tagging is good, I want more!
https://www.php.net/manual/en/class.sensitiveparameter.php
It is very common for people to set environment variables for a server process from a config file that is readable by the application which is a bigger problem. At least put them a file that is only root readable (and have the process started by root).
>op read "op://foo/bar/password"
>`$ token=$(rbw get gitlab-access-token) # get the token from a command-line password manager`
I feel this is probably better than plain text, but if my machine gets popped while logged on you likely have Access to active browser sessions between MFA flows and could do more damage that way.