1- // Command redact reads text from positional args or stdin and writes a
2- // copy with detected secrets replaced by a placeholder .
1+ // Command redact reads text from -string or stdin and writes a copy with
2+ // detected secrets replaced by a mask character .
33package main
44
55import (
6- "bufio"
76 "errors"
87 "flag"
98 "fmt"
109 "io"
1110 "os"
12- "strings"
1311
1412 "github.com/inancgumus/redact"
1513)
@@ -25,18 +23,22 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) error {
2523 fs := flag .NewFlagSet ("redact" , flag .ContinueOnError )
2624 fs .SetOutput (stderr )
2725 fs .Usage = func () {
28- _ , _ = fmt .Fprintln (stderr , "usage: redact [flags] [text...] " )
29- _ , _ = fmt .Fprintln (stderr , "reads text from args; if none, reads from stdin." )
26+ _ , _ = fmt .Fprintln (stderr , "usage: redact [flags]" )
27+ _ , _ = fmt .Fprintln (stderr , "reads -string if set, otherwise stdin." )
3028 fs .PrintDefaults ()
3129 }
3230
3331 opts := redact .DefaultOptions
3432 fs .Var ((* runeFlag )(& opts .Mask ), "mask" ,
3533 "character repeated for each byte of a secret" )
36- fs .Float64Var (& opts .MinEntropy , "min- entropy" , opts .MinEntropy ,
34+ fs .Float64Var (& opts .MinEntropy , "entropy" , opts .MinEntropy ,
3735 "how random a value must look to be redacted (lower = redacts more)" )
38- fs .IntVar (& opts .MinSubmatch , "min-submatch " , opts .MinSubmatch ,
36+ fs .IntVar (& opts .MinSubmatch , "strength " , opts .MinSubmatch ,
3937 "how strong a match must be to redact unknown secrets (higher = redacts less)" )
38+ detect := fs .Bool ("detect" , false ,
39+ "exit 1 if input contains secrets, 0 otherwise; no output" )
40+ var input string
41+ fs .StringVar (& input , "string" , "" , "redact this text instead of reading stdin" )
4042
4143 if err := fs .Parse (args ); err != nil {
4244 if errors .Is (err , flag .ErrHelp ) {
@@ -45,27 +47,40 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) error {
4547 return err
4648 }
4749
48- if rest := fs .Args (); len (rest ) > 0 {
49- _ , err := io .WriteString (stdout , redact .String (strings .Join (rest , " " ), opts ))
50- return err
50+ if fs .NArg () > 0 {
51+ return fmt .Errorf ("unexpected positional args; use -string for inline text" )
5152 }
5253
53- // Peek so empty stdin prints usage instead of silently exiting.
54- // The peeked byte stays buffered for ReadAll.
55- br := bufio .NewReader (stdin )
56- if _ , err := br .Peek (1 ); err != nil {
57- fs .Usage ()
58- return nil
54+ text := input
55+ if ! isStringFlagSet (fs ) {
56+ buf , err := io .ReadAll (stdin )
57+ if err != nil {
58+ return fmt .Errorf ("read stdin: %w" , err )
59+ }
60+ text = string (buf )
5961 }
6062
61- buf , err := io .ReadAll (br )
62- if err != nil {
63- return fmt .Errorf ("read stdin: %w" , err )
63+ if * detect {
64+ if redact .HasSecrets (text , opts ) {
65+ os .Exit (1 )
66+ }
67+ return nil
6468 }
65- _ , err = io .WriteString (stdout , redact .String (string (buf ), opts ))
69+
70+ _ , err := io .WriteString (stdout , redact .String (text , opts ))
6671 return err
6772}
6873
74+ func isStringFlagSet (fs * flag.FlagSet ) bool {
75+ var set bool
76+ fs .Visit (func (f * flag.Flag ) {
77+ if f .Name == "string" {
78+ set = true
79+ }
80+ })
81+ return set
82+ }
83+
6984// runeFlag adapts a *rune to the flag.Value interface so the mask flag
7085// accepts exactly one character.
7186type runeFlag rune
0 commit comments