Stop credential tokens leaking into usage output · Entire

Stop credential tokens leaking into usage output

Token flags registered their env value as the pflag default (e.g. StringVar(&auth.Token, "source-token", envOr("GITSYNC_SOURCE_TOKEN", ""))). pflag prints non-empty defaults in --help, and the unknown-flag fallback in main.go dumps the full usage block to stderr — so a typo'd flag in a CI job with GITSYNC_***_TOKEN set wrote the credential into CI logs.

Register secret flags (source/target token + bearer-token, including the inlined ones in convert-sha256) with empty defaults and apply the env value after parsing via a PreRunE hook, only when the flag was not given explicitly. An explicit flag still wins over the environment.

Add chainPreRunE so independent flag helpers can each attach post-parse logic without clobbering one another, and route allRefsFlag through it too.

Sessions

Changes

3

74 unmodified lines

envBool("GITSYNC_SOURCE_FOLLOW_INFO_REFS_REDIRECT"), "send follow-up source RPCs to the final /info/refs redirect host") cmd.Flags().StringVar(&req.SourceAuth.Token, "source-token", envOr("GITSYNC_SOURCE_TOKEN", ""), "source token/password") "", "source token/password (env: GITSYNC_SOURCE_TOKEN)") cmd.Flags().StringVar(&req.SourceAuth.Username, "source-username", envOr("GITSYNC_SOURCE_USERNAME", "git"), "source basic auth username") cmd.Flags().StringVar(&req.SourceAuth.BearerToken, "source-bearer-token", envOr("GITSYNC_SOURCE_BEARER_TOKEN", ""), "source bearer token") "", "source bearer token (env: GITSYNC_SOURCE_BEARER_TOKEN)") addSecretEnvFallback(cmd, "source-token", &req.SourceAuth.Token, "GITSYNC_SOURCE_TOKEN") addSecretEnvFallback(cmd, "source-bearer-token", &req.SourceAuth.BearerToken, "GITSYNC_SOURCE_BEARER_TOKEN") cmd.Flags().BoolVar(&req.SourceAuth.SkipTLSVerify, "source-insecure-skip-tls-verify", envBool("GITSYNC_SOURCE_INSECURE_SKIP_TLS_VERIFY"), "skip TLS certificate verification for the source")


Mcmd/git-sync/convert_sha256.go+4/-2

24 unmodified lines


func addSourceAuth(cmd *cobra.Command, auth *gitsync.EndpointAuth) {
    cmd.Flags().StringVar(&auth.Token, "source-token", envOr("GITSYNC_SOURCE_TOKEN", ""), "source token/password")
    cmd.Flags().StringVar(&auth.Token, "source-token", "", "source token/password (env: GITSYNC_SOURCE_TOKEN)")
    cmd.Flags().StringVar(&auth.Username, "source-username", envOr("GITSYNC_SOURCE_USERNAME", "git"), "source basic auth username")
    cmd.Flags().StringVar(&auth.BearerToken, "source-bearer-token", envOr("GITSYNC_SOURCE_BEARER_TOKEN", ""), "source bearer token")
    cmd.Flags().StringVar(&auth.BearerToken, "source-bearer-token", "", "source bearer token (env: GITSYNC_SOURCE_BEARER_TOKEN)")
    cmd.Flags().BoolVar(&auth.SkipTLSVerify, "source-insecure-skip-tls-verify",
        envBool("GITSYNC_SOURCE_INSECURE_SKIP_TLS_VERIFY"),
        "skip TLS certificate verification for the source")
    addSecretEnvFallback(cmd, "source-token", &auth.Token, "GITSYNC_SOURCE_TOKEN")
    addSecretEnvFallback(cmd, "source-bearer-token", &auth.BearerToken, "GITSYNC_SOURCE_BEARER_TOKEN")
}

func addTargetAuth(cmd *cobra.Command, auth *gitsync.EndpointAuth) {
    cmd.Flags().StringVar(&auth.Token, "target-token", envOr("GITSYNC_TARGET_TOKEN", ""), "target token/password")
    cmd.Flags().StringVar(&auth.Token, "target-token", "", "target token/password (env: GITSYNC_TARGET_TOKEN)")
    cmd.Flags().StringVar(&auth.Username, "target-username", envOr("GITSYNC_TARGET_USERNAME", "git"), "target basic auth username")
    cmd.Flags().StringVar(&auth.BearerToken, "target-bearer-token", envOr("GITSYNC_TARGET_BEARER_TOKEN", ""), "target bearer token")
    cmd.Flags().StringVar(&auth.BearerToken, "target-bearer-token", "", "target bearer token (env: GITSYNC_TARGET_BEARER_TOKEN)")
    cmd.Flags().BoolVar(&auth.SkipTLSVerify, "target-insecure-skip-tls-verify",
        envBool("GITSYNC_TARGET_INSECURE_SKIP_TLS_VERIFY"),
        "skip TLS certificate verification for the target")
    addSecretEnvFallback(cmd, "target-token", &auth.Token, "GITSYNC_TARGET_TOKEN")
    addSecretEnvFallback(cmd, "target-bearer-token", &auth.BearerToken, "GITSYNC_TARGET_BEARER_TOKEN")
}

func addProtocolFlag(cmd *cobra.Command, mode *protocolModeFlag) {
26 unmodified lines

if len(implies) == 0 {
        return
    }
    prev := cmd.PreRunE
    cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
    chainPreRunE(cmd, func(*cobra.Command, []string) error {
        if *allRefs {
            for _, p := range implies {
                if p != nil {
1 unmodified line

}
            }
        }
        return nil
        }
    })
}

// chainPreRunE appends fn to cmd's PreRunE, preserving any hook already
// registered (which runs first). Lets independent flag helpers each attach
// post-parse logic to the same command without clobbering one another.
func chainPreRunE(cmd *cobra.Command, fn func(*cobra.Command, []string) error) {
    prev := cmd.PreRunE
    cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
        if prev != nil {
            return prev(cmd, args)
            if err := prev(cmd, args); err != nil {
                return err
            }
        }
        return nil
        return fn(cmd, args)
    }
}

// addSecretEnvFallback fills *dst from the named environment variable after
// parsing, but only when the flag was not given explicitly. Secret-bearing
// flags must register an empty pflag default rather than the env value:
// pflag prints non-empty defaults in --help and in the usage block dumped on
// a flag error, which would leak the secret (e.g. into CI logs).
func addSecretEnvFallback(cmd *cobra.Command, flag string, dst *string, env string) {
    chainPreRunE(cmd, func(cmd *cobra.Command, _ []string) error {
        if cmd.Flags().Changed(flag) {
            return nil
        }
        if v := os.Getenv(env); v != "" {
            *dst = v
        }
        return nil
    })
}

func newProtocolFlag() protocolModeFlag {
    return protocolModeFlag(protocolMode(envOr("GITSYNC_PROTOCOL", validation.ProtocolAuto)))
}