# 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)))
}
