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
cmd/git-sync
Mconvert_sha256.go+4/-2
Mflags.go+40/-8
Aflags_test.go+58
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)))
}