Background
This issue tracks a follow-up suggestion from PR #1078: staticaddr: various fixes
Original Comment: #1078 (comment)
Author: @starius
Nit for a follow-up PR if you like it.
I wish flags' parsing libraries provided a convenient way to pass password as files. Like:
Password string `long:"password" description:"Database user's password." from:"file"`
Unfortunately it is not supported, but we can make a custom field type:
type Secret string
func (s *Secret) UnmarshalFlag(v string) error {
// Example convention: @/path/to/file => read from file, else raw value
if strings.HasPrefix(v, "@") {
b, err := os.ReadFile(strings.TrimPrefix(v, "@"))
if err != nil {
return err
}
*s = Secret(strings.TrimRight(string(b), "\r\n"))
return nil
}
*s = Secret(v)
return nil
}
type Opts struct {
Password Secret `long:"password" description:"Database user's password or @file to read the password from it."`
}
Context
PR #1078 added a //nolint:gosec comment to suppress a linter warning about the Password field in PostgresConfig. The comment suggests a better long-term solution: instead of just suppressing the lint warning, introduce a custom Secret type that supports reading passwords from files, avoiding plaintext passwords in CLI arguments or config files.
Proposed Change
Introduce a Secret string type that implements UnmarshalFlag with a convention where values prefixed with @ are treated as file paths. This allows users to pass --password @/path/to/secret to read the password from a file, while still supporting raw string values for backward compatibility.
This is a security improvement — passing secrets via files avoids leaking them through process listings (ps aux) and shell history.
Implementation Approach
- Add a
Secret type in an appropriate package (e.g., loopdb or a shared config package).
- Implement
UnmarshalFlag(string) error on *Secret so go-flags calls it automatically.
- If value starts with
@, read the file at the remaining path and trim trailing newlines.
- Otherwise, use the raw value directly.
- Change
PostgresConfig.Password from string to Secret in loopdb/postgres.go.
- Update
DSN() and any other callers to cast Secret back to string where needed.
- Update the field description to document the
@file convention.
- The
//nolint:gosec annotation can likely be removed since the field type is no longer a raw string named Password.
Related
Background
This issue tracks a follow-up suggestion from PR #1078: staticaddr: various fixes
Original Comment: #1078 (comment)
Author: @starius
Context
PR #1078 added a
//nolint:goseccomment to suppress a linter warning about thePasswordfield inPostgresConfig. The comment suggests a better long-term solution: instead of just suppressing the lint warning, introduce a customSecrettype that supports reading passwords from files, avoiding plaintext passwords in CLI arguments or config files.Proposed Change
Introduce a
Secretstring type that implementsUnmarshalFlagwith a convention where values prefixed with@are treated as file paths. This allows users to pass--password @/path/to/secretto read the password from a file, while still supporting raw string values for backward compatibility.This is a security improvement — passing secrets via files avoids leaking them through process listings (
ps aux) and shell history.Implementation Approach
Secrettype in an appropriate package (e.g.,loopdbor a sharedconfigpackage).UnmarshalFlag(string) erroron*Secretsogo-flagscalls it automatically.@, read the file at the remaining path and trim trailing newlines.PostgresConfig.PasswordfromstringtoSecretinloopdb/postgres.go.DSN()and any other callers to castSecretback tostringwhere needed.@fileconvention.//nolint:gosecannotation can likely be removed since the field type is no longer a rawstringnamedPassword.Related