Skip to content

Commit b387df2

Browse files
authored
Support for defaultRedactionConfig (#68)
Add support for defaultRedactionConfig to config file
1 parent 9e9495f commit b387df2

File tree

9 files changed

+128
-90
lines changed

9 files changed

+128
-90
lines changed

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,20 @@ Here's an example use case:
140140

141141
In the example, we are ignoring all file paths with a `tests` subdirectory, and only scanning on `go` and `json` files.
142142

143+
### Redaction
144+
145+
Redaction can be configured by using the key `defaultRedactionConfig`. Nightfall supports the following keys on this
146+
object:
147+
* `maskConfig`: replacing findings with a character; for example the SSN `678-99-8212` might be masked as `***-**-****`.
148+
* `substitutionConfig`: replacing findings with a custom phrase, such as `oh no! 🙈`
149+
* `infoTypeSubstitutionConfig`: replacing findings with the name of the matched info type, such as `CREDIT_CARD_NUMBER`.
150+
* `cryptoConfig`: using an RSA public key to encrypt findings
151+
152+
If your config file specifies a non-null value for `defaultRedactionConfig`, then exactly one of the above keys must be
153+
filled out; if more than one is filled out, the Nightfall API will return a 400 error code.
154+
155+
For more information on how to configure redaction-related fields, refer to the [Nightfall docs](https://docs.nightfall.ai/reference/scanpayloadv3).
156+
143157
## Configuration Examples
144158

145159
- Using a pre-built Detection Rule
@@ -273,3 +287,48 @@ In the example, we are ignoring all file paths with a `tests` subdirectory, and
273287
"fileInclusionList": ["*"]
274288
}
275289
```
290+
291+
- Sample Redaction Configurations:
292+
293+
Masking:
294+
```json
295+
{
296+
"defaultRedactionConfig": {
297+
"maskConfig": {
298+
"maskingChar": "👀",
299+
"charsToIgnore": ["-"," "]
300+
}
301+
}
302+
}
303+
```
304+
305+
Substitution:
306+
```json
307+
{
308+
"defaultRedactionConfig": {
309+
"substitutionConfig": {
310+
"substitutionPhrase": "REDACTED"
311+
}
312+
}
313+
}
314+
315+
```
316+
Info Type Substitution:
317+
```json
318+
{
319+
"defaultRedactionConfig": {
320+
"infoTypeSubstitutionConfig": {}
321+
}
322+
}
323+
```
324+
325+
Encryption:
326+
```json
327+
{
328+
"defaultRedactionConfig": {
329+
"cryptoConfig": {
330+
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2VUXMyeEZ8bCJd6OWUJG\n...-----END PUBLIC KEY-----"
331+
}
332+
}
333+
}
334+
```

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/golang/mock v1.4.4
88
github.com/google/go-github/v33 v33.0.0
99
github.com/google/uuid v1.3.0
10-
github.com/nightfallai/nightfall-go-sdk v1.1.0
10+
github.com/nightfallai/nightfall-go-sdk v1.1.1
1111
github.com/pkg/errors v0.9.1 // indirect
1212
github.com/spf13/pflag v1.0.5
1313
github.com/stretchr/testify v1.6.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
115115
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
116116
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
117117
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
118-
github.com/nightfallai/nightfall-go-sdk v1.1.0 h1:1m4sMvoVUE+ugYC9XvkMdw01mJNyM/NWP9ZGBJPawMM=
119-
github.com/nightfallai/nightfall-go-sdk v1.1.0/go.mod h1:IQxS8JqhFEGRfnOpaLqv+ZEhQrUKbJgFAQ5qHU3Lcbg=
118+
github.com/nightfallai/nightfall-go-sdk v1.1.1 h1:xx/e4ZP415oHQcJMOCBeU8kxOWiakrjtEEREZHJtE8Q=
119+
github.com/nightfallai/nightfall-go-sdk v1.1.1/go.mod h1:IQxS8JqhFEGRfnOpaLqv+ZEhQrUKbJgFAQ5qHU3Lcbg=
120120
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
121121
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
122122
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

internal/clients/nightfall/nightfall.go

Lines changed: 30 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,30 @@ import (
2020
)
2121

2222
const (
23-
// max request size is 500KB, so set max to 490Kb for buffer room
24-
// maxSizeBytes = 490000
25-
// max list size imposed by Nightfall API
26-
// maxListSize = 50000
2723
contentChunkByteSize = 1024
2824
// max number of items that can be sent to Nightfall API at a time
2925
maxItemsForAPIReq = 479
3026
// timeout for the total time spent sending scan requests and receiving responses for a diff
3127
defaultTimeout = time.Minute * 20
3228
// maximum attempts to Nightfall API upon receiving 429 Too Many Requests before failing
33-
MaxScanAttempts = 5
29+
maxScanAttempts = 5
3430
// initial delay before re-attempting scan request
35-
initialDelay = time.Second * 1
31+
initialDelay = time.Second
3632
)
3733

38-
// Client client which uses Nightfall API
39-
// to determine findings from input strings
34+
// Client uses the Nightfall API to scan text for findings
4035
type Client struct {
4136
APIClient interface {
4237
ScanText(ctx context.Context, request *nf.ScanTextRequest) (*nf.ScanTextResponse, error)
4338
}
44-
DetectionRuleUUIDs []uuid.UUID
45-
DetectionRules []nf.DetectionRule
46-
MaxNumberRoutines int
47-
InitialRetryDelay time.Duration
48-
TokenExclusionList []string
49-
FileInclusionList []string
50-
FileExclusionList []string
39+
DetectionRuleUUIDs []uuid.UUID
40+
DetectionRules []nf.DetectionRule
41+
MaxNumberRoutines int
42+
InitialRetryDelay time.Duration
43+
TokenExclusionList []string
44+
FileInclusionList []string
45+
FileExclusionList []string
46+
DefaultRedactionConfig *nf.RedactionConfig
5147
}
5248

5349
func NewClient(config nightfallconfig.Config) (*Client, error) {
@@ -56,14 +52,15 @@ func NewClient(config nightfallconfig.Config) (*Client, error) {
5652
return nil, err
5753
}
5854
return &Client{
59-
APIClient: client,
60-
DetectionRuleUUIDs: config.NightfallDetectionRuleUUIDs,
61-
DetectionRules: config.NightfallDetectionRules,
62-
MaxNumberRoutines: config.NightfallMaxNumberRoutines,
63-
InitialRetryDelay: initialDelay,
64-
TokenExclusionList: config.TokenExclusionList,
65-
FileInclusionList: config.FileInclusionList,
66-
FileExclusionList: config.FileExclusionList,
55+
APIClient: client,
56+
DetectionRuleUUIDs: config.NightfallDetectionRuleUUIDs,
57+
DetectionRules: config.NightfallDetectionRules,
58+
MaxNumberRoutines: config.NightfallMaxNumberRoutines,
59+
InitialRetryDelay: initialDelay,
60+
TokenExclusionList: config.TokenExclusionList,
61+
FileInclusionList: config.FileInclusionList,
62+
FileExclusionList: config.FileExclusionList,
63+
DefaultRedactionConfig: config.DefaultRedactionConfig,
6764
}, nil
6865
}
6966

@@ -73,31 +70,17 @@ type contentToScan struct {
7370
LineNumber int
7471
}
7572

76-
func blurContent(content string) string {
77-
contentRune := []rune(content)
78-
blurredContent := string(contentRune[:2])
79-
blurLength := 8
80-
if len(contentRune[2:]) < blurLength {
81-
blurLength = len(contentRune[2:])
82-
}
83-
for i := 0; i < blurLength; i++ {
84-
blurredContent += "*"
85-
}
86-
return blurredContent
87-
}
88-
8973
func getCommentMsg(finding *nf.Finding) string {
90-
if finding.Finding == "" || finding.Detector.DisplayName == "" {
74+
if finding.Finding == "" && finding.RedactedFinding == "" {
9175
return ""
9276
}
9377

94-
blurredContent := finding.RedactedFinding
95-
if blurredContent == "" {
96-
// by default use asterisks, so we don't spread data further
97-
blurredContent = blurContent(finding.Finding)
78+
content := finding.RedactedFinding
79+
if content == "" {
80+
content = finding.Finding
9881
}
9982

100-
return fmt.Sprintf("Suspicious content detected (%s, type %s)", blurredContent, finding.Detector.DisplayName)
83+
return fmt.Sprintf("Suspicious content detected (%q, type %q)", content, finding.Detector.DisplayName)
10184
}
10285

10386
func getCommentTitle(finding *nf.Finding) string {
@@ -219,8 +202,9 @@ func (n *Client) buildScanRequest(items []string) *nf.ScanTextRequest {
219202
return &nf.ScanTextRequest{
220203
Payload: items,
221204
Config: &nf.Config{
222-
DetectionRules: n.DetectionRules,
223-
DetectionRuleUUIDs: ruleUUIDStrs,
205+
DetectionRules: n.DetectionRules,
206+
DetectionRuleUUIDs: ruleUUIDStrs,
207+
DefaultRedactionConfig: n.DefaultRedactionConfig,
224208
},
225209
}
226210
}
@@ -371,8 +355,8 @@ func compileGlobs(globPatterns []string, logger logger.Logger) []glob.Glob {
371355
}
372356

373357
func matchGlob(filePath string, globs []glob.Glob) bool {
374-
for _, glob := range globs {
375-
if glob.Match(filePath) {
358+
for _, g := range globs {
359+
if g.Match(filePath) {
376360
return true
377361
}
378362
}

internal/clients/nightfall/nightfall_internal_test.go

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -216,34 +216,6 @@ func TestCreateCommentsFromScanResp(t *testing.T) {
216216
}
217217
}
218218

219-
func TestBlurContent(t *testing.T) {
220-
tests := []struct {
221-
have string
222-
want string
223-
}{
224-
{
225-
have: exampleCreditCardNumber,
226-
want: "49********",
227-
},
228-
{
229-
have: exampleAPIKey,
230-
want: "yr********",
231-
},
232-
{
233-
have: "汉字 Hello 123",
234-
want: "汉字********",
235-
},
236-
{
237-
have: "SHORT",
238-
want: "SH***",
239-
},
240-
}
241-
for _, tt := range tests {
242-
actual := blurContent(tt.have)
243-
assert.Equal(t, tt.want, actual, "Incorrect response from blurContent")
244-
}
245-
}
246-
247219
func TestFilterFileDiffs(t *testing.T) {
248220
filePaths := []string{"path/secondary_path/file.txt", "a.go", "a/a.go", "test.go", "path/main.go", "path/test.py"}
249221
fileDiffs := make([]*diffreviewer.FileDiff, len(filePaths))

internal/clients/nightfall/nightfall_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/stretchr/testify/assert"
1515
)
1616

17-
var blurredCreditCard = "49********"
17+
var blurredCreditCard = "49*****************"
1818

1919
type mockNightfall struct {
2020
scanFn func(context.Context, *nf.ScanTextRequest) (*nf.ScanTextResponse, error)
@@ -57,7 +57,8 @@ var expectedScanResponse = &nf.ScanTextResponse{
5757
{},
5858
{
5959
{
60-
Finding: exampleCreditCardNumber,
60+
Finding: exampleCreditCardNumber,
61+
RedactedFinding: "49*****************",
6162
Detector: nf.DetectorMetadata{
6263
DisplayName: "CREDIT_CARD_NUMBER",
6364
DetectorUUID: "2136e3c9-feb0-4aea-8d3e-a767afabf501",
@@ -105,7 +106,7 @@ func TestReviewDiff(t *testing.T) {
105106
c := diffreviewer.Comment{
106107
FilePath: filePath,
107108
LineNumber: lineNum,
108-
Body: fmt.Sprintf("Suspicious content detected (%s, type %s)", blurredCreditCard, "CREDIT_CARD_NUMBER"),
109+
Body: fmt.Sprintf("Suspicious content detected (%q, type %q)", blurredCreditCard, "CREDIT_CARD_NUMBER"),
109110
Title: fmt.Sprintf("Detected CREDIT_CARD_NUMBER"),
110111
}
111112
expectedComments := []*diffreviewer.Comment{&c, &c, &c}
@@ -179,7 +180,7 @@ func TestReviewDiffDetectionRuleUUID(t *testing.T) {
179180
c := diffreviewer.Comment{
180181
FilePath: filePath,
181182
LineNumber: lineNum,
182-
Body: fmt.Sprintf("Suspicious content detected (%s, type %s)", blurredCreditCard, "CREDIT_CARD_NUMBER"),
183+
Body: fmt.Sprintf("Suspicious content detected (%q, type %q)", blurredCreditCard, "CREDIT_CARD_NUMBER"),
183184
Title: "Detected CREDIT_CARD_NUMBER",
184185
}
185186
expectedComments := []*diffreviewer.Comment{&c, &c, &c}
@@ -232,7 +233,7 @@ func TestScanPaths(t *testing.T) {
232233
desc: "success on first scan request",
233234
},
234235
{
235-
haveNumRequests: MaxScanAttempts + 1,
236+
haveNumRequests: maxScanAttempts + 1,
236237
wantResponse: nil,
237238
wantErr: errors.New("failure"),
238239
desc: "failure from API",
@@ -248,7 +249,7 @@ func TestScanPaths(t *testing.T) {
248249

249250
mockAPIClient.scanFn = func(ctx context.Context, request *nf.ScanTextRequest) (*nf.ScanTextResponse, error) {
250251
assert.Equal(t, expectedScanReq, request, "request object did not match")
251-
if tt.haveNumRequests > MaxScanAttempts {
252+
if tt.haveNumRequests > maxScanAttempts {
252253
return nil, errors.New("failure")
253254
}
254255
return expectedScanResponse, nil

internal/nightfallconfig/nightfall_config.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,23 @@ var defaultNightfallConfig = &ConfigFile{
5151
},
5252
},
5353
MaxNumberRoutines: DefaultMaxNumberRoutines,
54+
DefaultRedactionConfig: &nf.RedactionConfig{
55+
MaskConfig: &nf.MaskConfig{
56+
MaskingChar: "*",
57+
NumCharsToLeaveUnmasked: 2,
58+
},
59+
},
5460
}
5561

5662
// ConfigFile is the struct of the JSON nightfall config file
5763
type ConfigFile struct {
58-
DetectionRuleUUIDs []uuid.UUID `json:"detectionRuleUUIDs"`
59-
DetectionRules []nf.DetectionRule `json:"detectionRules"`
60-
MaxNumberRoutines int `json:"maxNumberConcurrentRoutines"`
61-
TokenExclusionList []string `json:"tokenExclusionList"`
62-
FileInclusionList []string `json:"fileInclusionList"`
63-
FileExclusionList []string `json:"fileExclusionList"`
64+
DetectionRuleUUIDs []uuid.UUID `json:"detectionRuleUUIDs"`
65+
DetectionRules []nf.DetectionRule `json:"detectionRules"`
66+
MaxNumberRoutines int `json:"maxNumberConcurrentRoutines"`
67+
TokenExclusionList []string `json:"tokenExclusionList"`
68+
FileInclusionList []string `json:"fileInclusionList"`
69+
FileExclusionList []string `json:"fileExclusionList"`
70+
DefaultRedactionConfig *nf.RedactionConfig `json:"defaultRedactionConfig"`
6471
}
6572

6673
// Config general config struct
@@ -72,6 +79,7 @@ type Config struct {
7279
TokenExclusionList []string
7380
FileInclusionList []string
7481
FileExclusionList []string
82+
DefaultRedactionConfig *nf.RedactionConfig
7583
}
7684

7785
// GetNightfallConfigFile loads nightfall config from file, returns default if missing/invalid

internal/nightfallconfig/nightfall_config_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ func TestGetNightfallConfig(t *testing.T) {
5454
TokenExclusionList: []string{excludedCreditCardRegex, excludedApiToken, excludedIPRegex},
5555
FileInclusionList: []string{"*"},
5656
FileExclusionList: []string{".nightfalldlp/config.json"},
57+
DefaultRedactionConfig: &nf.RedactionConfig{
58+
SubstitutionConfig: &nf.SubstitutionConfig{SubstitutionPhrase: "REDACTED"},
59+
},
5760
}
5861
actualConfig, err := GetNightfallConfigFile(workspacePath, testFileName, nil)
5962
assert.NoError(t, err, "Unexpected error in test GetNightfallConfig")
@@ -93,6 +96,12 @@ func TestGetNightfallConfigMissingConfigFile(t *testing.T) {
9396
},
9497
},
9598
MaxNumberRoutines: DefaultMaxNumberRoutines,
99+
DefaultRedactionConfig: &nf.RedactionConfig{
100+
MaskConfig: &nf.MaskConfig{
101+
MaskingChar: "*",
102+
NumCharsToLeaveUnmasked: 2,
103+
},
104+
},
96105
}
97106
actualConfig, err := GetNightfallConfigFile(workspacePath, testMissingFileName, githublogger.NewDefaultGithubLogger())
98107
assert.NoError(t, err, "Unexpected error in test GetNightfallConfigMissingConfigFile")

test/data/nightfall_test_config.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,10 @@
3030
],
3131
"maxNumberConcurrentRoutines": 20,
3232
"tokenExclusionList": ["4242-4242-4242-[0-9]{4}", "xG0Ct4Wsu3OTcJnE1dFLAQfRgL6b8tIv", "^127\\."],
33-
"fileInclusionList": ["*"]
33+
"fileInclusionList": ["*"],
34+
"defaultRedactionConfig": {
35+
"substitutionConfig": {
36+
"substitutionPhrase": "REDACTED"
37+
}
38+
}
3439
}

0 commit comments

Comments
 (0)