Skip to content

Commit

Permalink
feat: Add optional redaction value, default 100 (#1229)
Browse files Browse the repository at this point in the history
This commit introduces an optional value for the --redact flag, allowing
users to specify the extent of redaction for secrets. The valid range of
values is from 0 (no redaction) to 100 (full redaction).

If no value is provided, the default redaction value is set to 100,
resulting in complete redaction by replacing the secret with the term
'REDACTED'. This preserves the original behavior of the --redact flag.

Co-authored-by: Zachary Rice <zachary.rice@trufflesec.com>
  • Loading branch information
fr12k and zricethezav committed Aug 29, 2023
1 parent e9135cf commit 30c6117
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 14 deletions.
2 changes: 1 addition & 1 deletion cmd/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func runDetect(cmd *cobra.Command, args []string) {
log.Fatal().Err(err).Msg("")
}
// set redact flag
if detector.Redact, err = cmd.Flags().GetBool("redact"); err != nil {
if detector.Redact, err = cmd.Flags().GetUint("redact"); err != nil {
log.Fatal().Err(err).Msg("")
}
if detector.MaxTargetMegaBytes, err = cmd.Flags().GetInt("max-target-megabytes"); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/protect.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func runProtect(cmd *cobra.Command, args []string) {
log.Fatal().Err(err).Msg("")
}
// set redact flag
if detector.Redact, err = cmd.Flags().GetBool("redact"); err != nil {
if detector.Redact, err = cmd.Flags().GetUint("redact"); err != nil {
log.Fatal().Err(err).Msg("")
}
if detector.MaxTargetMegaBytes, err = cmd.Flags().GetInt("max-target-megabytes"); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ func init() {
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "show verbose output from scan")
rootCmd.PersistentFlags().BoolP("no-color", "", false, "turn off color for verbose output")
rootCmd.PersistentFlags().Int("max-target-megabytes", 0, "files larger than this will be skipped")
rootCmd.PersistentFlags().Bool("redact", false, "redact secrets from logs and stdout")
rootCmd.PersistentFlags().Uint("redact", 0, "redact secrets from logs and stdout. To redact only parts of the secret just apply a percent value from 0..100. For example --redact=20 (default 100%)")
rootCmd.Flag("redact").NoOptDefVal = "100"
rootCmd.PersistentFlags().Bool("no-banner", false, "suppress banner")
err := viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion detect/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type Detector struct {
// Redact is a flag to redact findings. This is exported
// so users using gitleaks as a library can set this flag
// without calling `detector.Start(cmd *cobra.Command)`
Redact bool
Redact uint

// verbose is a flag to print findings
Verbose bool
Expand Down
6 changes: 3 additions & 3 deletions detect/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func shannonEntropy(data string) (entropy float64) {
}

// filter will dedupe and redact findings
func filter(findings []report.Finding, redact bool) []report.Finding {
func filter(findings []report.Finding, redact uint) []report.Finding {
var retFindings []report.Finding
for _, f := range findings {
include := true
Expand All @@ -81,8 +81,8 @@ func filter(findings []report.Finding, redact bool) []report.Finding {
}
}

if redact {
f.Redact()
if redact > 0 {
f.Redact(redact)
}
if include {
retFindings = append(retFindings, f)
Expand Down
27 changes: 23 additions & 4 deletions report/finding.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package report

import (
"math"
"strings"
)

Expand Down Expand Up @@ -43,8 +44,26 @@ type Finding struct {
}

// Redact removes sensitive information from a finding.
func (f *Finding) Redact() {
f.Line = strings.Replace(f.Line, f.Secret, "REDACTED", -1)
f.Match = strings.Replace(f.Match, f.Secret, "REDACTED", -1)
f.Secret = "REDACTED"
func (f *Finding) Redact(percent uint) {
secret := maskSecret(f.Secret, percent)
if percent >= 100 {
secret = "REDACTED"
}
f.Line = strings.Replace(f.Line, f.Secret, secret, -1)
f.Match = strings.Replace(f.Match, f.Secret, secret, -1)
f.Secret = secret
}

func maskSecret(secret string, percent uint) string {
if percent > 100 {
percent = 100
}
len := float64(len(secret))
if len <= 0 {
return secret
}
prc := float64(100 - percent)
lth := int64(math.RoundToEven(len * prc / float64(100)))

return secret[:lth] + "..."
}
69 changes: 66 additions & 3 deletions report/finding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,80 @@ func TestRedact(t *testing.T) {
redact: true,
findings: []Finding{
{
Secret: "line containing secret",
Match: "secret",
Match: "line containing secret",
Secret: "secret",
},
}},
}
for _, test := range tests {
for _, f := range test.findings {
f.Redact()
f.Redact(100)
if f.Secret != "REDACTED" {
t.Error("redact not redacting: ", f.Secret)
}
if f.Match != "line containing REDACTED" {
t.Error("redact not redacting: ", f.Secret)
}
}
}
}

func TestMask(t *testing.T) {

tests := map[string]struct {
finding Finding
percent uint
expect Finding
}{
"normal secret": {
finding: Finding{Match: "line containing secret", Secret: "secret"},
expect: Finding{Match: "line containing se...", Secret: "se..."},
percent: 75,
},
"empty secret": {
finding: Finding{Match: "line containing", Secret: ""},
expect: Finding{Match: "line containing", Secret: ""},
percent: 75,
},
"short secret": {
finding: Finding{Match: "line containing", Secret: "ss"},
expect: Finding{Match: "line containing", Secret: "..."},
percent: 75,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
f := test.finding
e := test.expect
f.Redact(test.percent)
if f.Secret != e.Secret {
t.Error("redact not redacting: ", f.Secret)
}
if f.Match != e.Match {
t.Error("redact not redacting: ", f.Match)
}
})
}
}

func TestMaskSecret(t *testing.T) {

tests := map[string]struct {
secret string
percent uint
expect string
}{
"normal masking": {secret: "secret", percent: 75, expect: "se..."},
"high masking": {secret: "secret", percent: 90, expect: "s..."},
"low masking": {secret: "secret", percent: 10, expect: "secre..."},
"invalid masking": {secret: "secret", percent: 1000, expect: "..."},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
got := maskSecret(test.secret, test.percent)
if got != test.expect {
t.Error("redact not redacting: ", got)
}
})
}
}

0 comments on commit 30c6117

Please sign in to comment.