Merge pull request #77 from entireio/fix/credential-token-usage-leak · Entire

Merge pull request #77 from entireio/fix/credential-token-usage-leak

f4069f1→main·

Soph·1mo ago·3 files·+105 added/-12 removed

Stop credential tokens leaking into usage output

Changes

3

73 unmodified lines

74
75
76
77
78
77
78
79
81
82
80
81
82
83

73 unmodified lines

cmd.Flags().BoolVar(&req.SourceFollowInfoRefsRedirect, "source-follow-info-refs-redirect",
        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")
    addSecretFlag(cmd, &req.SourceAuth.Token, "source-token", "GITSYNC_SOURCE_TOKEN", "source token/password")
    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")
    addSecretFlag(cmd, &req.SourceAuth.BearerToken, "source-bearer-token", "GITSYNC_SOURCE_BEARER_TOKEN", "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+2/-4

24 unmodified lines

25
26
27
28
28
29
30
30
31
32
33
34
35
36
37
37
38
39
39
40
41
42
29 unmodified lines

72
73
74
75
76
75
76
77
78
1 unmodified line

80
81
82
83
84
85
86
87
88
89
90
91
92
93
85
94
95
96
97
87
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

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")
    addSecretFlag(cmd, &auth.Token, "source-token", "GITSYNC_SOURCE_TOKEN", "source token/password")
    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")
    addSecretFlag(cmd, &auth.BearerToken, "source-bearer-token", "GITSYNC_SOURCE_BEARER_TOKEN", "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")
}

func addTargetAuth(cmd *cobra.Command, auth *gitsync.EndpointAuth) {
    cmd.Flags().StringVar(&auth.Token, "target-token", envOr("GITSYNC_TARGET_TOKEN", ""), "target token/password")
    addSecretFlag(cmd, &auth.Token, "target-token", "GITSYNC_TARGET_TOKEN", "target token/password")
    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")
    addSecretFlag(cmd, &auth.BearerToken, "target-bearer-token", "GITSYNC_TARGET_BEARER_TOKEN", "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")
29 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)
    }
}

// addSecretFlag registers a string flag whose value may also come from an
// environment variable. Unlike non-secret flags, it must NOT register the env
// value as the pflag default: 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). Registration and the post-parse env fallback are paired in
// one call so a new secret flag cannot accidentally re-introduce the leak by
// reaching for envOr.
func addSecretFlag(cmd *cobra.Command, dst *string, name, env, usage string) {
    cmd.Flags().StringVar(dst, name, "", fmt.Sprintf("%s (env: %s)", usage, env))
    addSecretEnvFallback(cmd, name, dst, env)
}

// addSecretEnvFallback fills *dst from the named environment variable after
// parsing, but only when the flag was not given explicitly.
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)))
}

Mcmd/git-sync/flags.go+45/-8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

package main

import (
    "strings"
    "testing"

gitsync "entire.io/entire/git-sync"
    "github.com/spf13/cobra"
)

// Secret-bearing flags must not register their env value as the pflag
// default: 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).
// The env value must instead be applied after parsing.
func TestAuthSecretEnvDoesNotLeakIntoUsage(t *testing.T) {
    t.Setenv("GITSYNC_SOURCE_TOKEN", "SUPERSECRET")
    t.Setenv("GITSYNC_TARGET_TOKEN", "TOPSECRET")
    t.Setenv("GITSYNC_SOURCE_BEARER_TOKEN", "BEARERSECRET")
    t.Setenv("GITSYNC_TARGET_BEARER_TOKEN", "BEARERSECRET2")

var source, target gitsync.EndpointAuth
    cmd := &cobra.Command{Use: "x", RunE: func(*cobra.Command, []string) error { return nil }}
    addSourceAuth(cmd, &source)
    addTargetAuth(cmd, &target)

usage := cmd.UsageString()
    for _, secret := range []string{"SUPERSECRET", "TOPSECRET", "BEARERSECRET", "BEARERSECRET2"} {
        if strings.Contains(usage, secret) {
            t.Fatalf("secret %q leaked into usage output:\n%s", secret, usage)
        }
    }
}

// The env fallback must still populate auth when the flag is not given
// explicitly, and an explicit flag must win over the environment.
func TestAuthSecretEnvFallbackApplies(t *testing.T) {
    t.Setenv("GITSYNC_SOURCE_TOKEN", "from-env")
    t.Setenv("GITSYNC_TARGET_TOKEN", "target-from-env")

// Source token comes from the environment; target token is given
    // explicitly and must override its environment value.
    cmd.SetArgs([]string{"--target-token", "from-flag"})
    if err := cmd.Execute(); err != nil {
        t.Fatalf("execute: %v", err)
    }

if source.Token != "from-env" {
        t.Errorf("source token = %q, want env fallback %q", source.Token, "from-env")
    }
    if target.Token != "from-flag" {
        t.Errorf("target token = %q, want explicit flag to win over env", target.Token)
    }
}