Skip to content

Add Secret type for reading database password from file #1088

@hieblmi

Description

@hieblmi

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions