Skip to content

Commit b6d3002

Browse files
api-clients-generation-pipeline[bot]ci.datadog-api-spec
andauthored
Add SDS rule should_save_match field (#3305)
Co-authored-by: ci.datadog-api-spec <packages@datadoghq.com>
1 parent 3f6c208 commit b6d3002

7 files changed

Lines changed: 225 additions & 3 deletions

File tree

.generated-info

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"spec_repo_commit": "98e3371",
3-
"generated": "2025-08-27 08:46:47.876"
2+
"spec_repo_commit": "62a19e4",
3+
"generated": "2025-08-27 15:03:04.000"
44
}

.generator/schemas/v2/openapi.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39445,6 +39445,12 @@ components:
3944539445
replacement_string:
3944639446
description: Required if type == 'replacement_string'.
3944739447
type: string
39448+
should_save_match:
39449+
description: "Only valid when type == `replacement_string`. When enabled,
39450+
matches can be unmasked in logs by users with \u2018Data Scanner Unmask\u2019
39451+
permission. As a security best practice, avoid masking for highly-sensitive,
39452+
long-lived data."
39453+
type: boolean
3944839454
type:
3944939455
$ref: '#/components/schemas/SensitiveDataScannerTextReplacementType'
3945039456
type: object

api/datadogV2/model_sensitive_data_scanner_text_replacement.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ type SensitiveDataScannerTextReplacement struct {
1515
NumberOfChars *int64 `json:"number_of_chars,omitempty"`
1616
// Required if type == 'replacement_string'.
1717
ReplacementString *string `json:"replacement_string,omitempty"`
18+
// Only valid when type == `replacement_string`. When enabled, matches can be unmasked in logs by users with ‘Data Scanner Unmask’ permission. As a security best practice, avoid masking for highly-sensitive, long-lived data.
19+
ShouldSaveMatch *bool `json:"should_save_match,omitempty"`
1820
// Type of the replacement text. None means no replacement.
1921
// hash means the data will be stubbed. replacement_string means that
2022
// one can chose a text to replace the data. partial_replacement_from_beginning
@@ -104,6 +106,34 @@ func (o *SensitiveDataScannerTextReplacement) SetReplacementString(v string) {
104106
o.ReplacementString = &v
105107
}
106108

109+
// GetShouldSaveMatch returns the ShouldSaveMatch field value if set, zero value otherwise.
110+
func (o *SensitiveDataScannerTextReplacement) GetShouldSaveMatch() bool {
111+
if o == nil || o.ShouldSaveMatch == nil {
112+
var ret bool
113+
return ret
114+
}
115+
return *o.ShouldSaveMatch
116+
}
117+
118+
// GetShouldSaveMatchOk returns a tuple with the ShouldSaveMatch field value if set, nil otherwise
119+
// and a boolean to check if the value has been set.
120+
func (o *SensitiveDataScannerTextReplacement) GetShouldSaveMatchOk() (*bool, bool) {
121+
if o == nil || o.ShouldSaveMatch == nil {
122+
return nil, false
123+
}
124+
return o.ShouldSaveMatch, true
125+
}
126+
127+
// HasShouldSaveMatch returns a boolean if a field has been set.
128+
func (o *SensitiveDataScannerTextReplacement) HasShouldSaveMatch() bool {
129+
return o != nil && o.ShouldSaveMatch != nil
130+
}
131+
132+
// SetShouldSaveMatch gets a reference to the given bool and assigns it to the ShouldSaveMatch field.
133+
func (o *SensitiveDataScannerTextReplacement) SetShouldSaveMatch(v bool) {
134+
o.ShouldSaveMatch = &v
135+
}
136+
107137
// GetType returns the Type field value if set, zero value otherwise.
108138
func (o *SensitiveDataScannerTextReplacement) GetType() SensitiveDataScannerTextReplacementType {
109139
if o == nil || o.Type == nil {
@@ -144,6 +174,9 @@ func (o SensitiveDataScannerTextReplacement) MarshalJSON() ([]byte, error) {
144174
if o.ReplacementString != nil {
145175
toSerialize["replacement_string"] = o.ReplacementString
146176
}
177+
if o.ShouldSaveMatch != nil {
178+
toSerialize["should_save_match"] = o.ShouldSaveMatch
179+
}
147180
if o.Type != nil {
148181
toSerialize["type"] = o.Type
149182
}
@@ -159,21 +192,23 @@ func (o *SensitiveDataScannerTextReplacement) UnmarshalJSON(bytes []byte) (err e
159192
all := struct {
160193
NumberOfChars *int64 `json:"number_of_chars,omitempty"`
161194
ReplacementString *string `json:"replacement_string,omitempty"`
195+
ShouldSaveMatch *bool `json:"should_save_match,omitempty"`
162196
Type *SensitiveDataScannerTextReplacementType `json:"type,omitempty"`
163197
}{}
164198
if err = datadog.Unmarshal(bytes, &all); err != nil {
165199
return datadog.Unmarshal(bytes, &o.UnparsedObject)
166200
}
167201
additionalProperties := make(map[string]interface{})
168202
if err = datadog.Unmarshal(bytes, &additionalProperties); err == nil {
169-
datadog.DeleteKeys(additionalProperties, &[]string{"number_of_chars", "replacement_string", "type"})
203+
datadog.DeleteKeys(additionalProperties, &[]string{"number_of_chars", "replacement_string", "should_save_match", "type"})
170204
} else {
171205
return err
172206
}
173207

174208
hasInvalidField := false
175209
o.NumberOfChars = all.NumberOfChars
176210
o.ReplacementString = all.ReplacementString
211+
o.ShouldSaveMatch = all.ShouldSaveMatch
177212
if all.Type != nil && !all.Type.IsValid() {
178213
hasInvalidField = true
179214
} else {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Create Scanning Rule with should_save_match returns "OK" response
2+
3+
package main
4+
5+
import (
6+
"context"
7+
"encoding/json"
8+
"fmt"
9+
"os"
10+
11+
"github.com/DataDog/datadog-api-client-go/v2/api/datadog"
12+
"github.com/DataDog/datadog-api-client-go/v2/api/datadogV2"
13+
)
14+
15+
func main() {
16+
// there is a valid "scanning_group" in the system
17+
GroupDataID := os.Getenv("GROUP_DATA_ID")
18+
19+
body := datadogV2.SensitiveDataScannerRuleCreateRequest{
20+
Meta: datadogV2.SensitiveDataScannerMetaVersionOnly{},
21+
Data: datadogV2.SensitiveDataScannerRuleCreate{
22+
Type: datadogV2.SENSITIVEDATASCANNERRULETYPE_SENSITIVE_DATA_SCANNER_RULE,
23+
Attributes: datadogV2.SensitiveDataScannerRuleAttributes{
24+
Name: datadog.PtrString("Example-Sensitive-Data-Scanner"),
25+
Pattern: datadog.PtrString("pattern"),
26+
TextReplacement: &datadogV2.SensitiveDataScannerTextReplacement{
27+
Type: datadogV2.SENSITIVEDATASCANNERTEXTREPLACEMENTTYPE_REPLACEMENT_STRING.Ptr(),
28+
ReplacementString: datadog.PtrString("REDACTED"),
29+
ShouldSaveMatch: datadog.PtrBool(true),
30+
},
31+
Tags: []string{
32+
"sensitive_data:true",
33+
},
34+
IsEnabled: datadog.PtrBool(true),
35+
Priority: datadog.PtrInt64(1),
36+
},
37+
Relationships: datadogV2.SensitiveDataScannerRuleRelationships{
38+
Group: &datadogV2.SensitiveDataScannerGroupData{
39+
Data: &datadogV2.SensitiveDataScannerGroup{
40+
Type: datadogV2.SENSITIVEDATASCANNERGROUPTYPE_SENSITIVE_DATA_SCANNER_GROUP.Ptr(),
41+
Id: datadog.PtrString(GroupDataID),
42+
},
43+
},
44+
},
45+
},
46+
}
47+
ctx := datadog.NewDefaultContext(context.Background())
48+
configuration := datadog.NewConfiguration()
49+
apiClient := datadog.NewAPIClient(configuration)
50+
api := datadogV2.NewSensitiveDataScannerApi(apiClient)
51+
resp, r, err := api.CreateScanningRule(ctx, body)
52+
53+
if err != nil {
54+
fmt.Fprintf(os.Stderr, "Error when calling `SensitiveDataScannerApi.CreateScanningRule`: %v\n", err)
55+
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
56+
}
57+
58+
responseContent, _ := json.MarshalIndent(resp, "", " ")
59+
fmt.Fprintf(os.Stdout, "Response from `SensitiveDataScannerApi.CreateScanningRule`:\n%s\n", responseContent)
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2025-08-26T20:31:44.042Z
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
interactions:
2+
- request:
3+
body: ''
4+
form: {}
5+
headers:
6+
Accept:
7+
- application/json
8+
id: 0
9+
method: GET
10+
url: https://api.datadoghq.com/api/v2/sensitive-data-scanner/config
11+
response:
12+
body: '{"data":{"id":"7957915c634d4dcb581fa154157f5ad9c2947f50be632fb5599862069f4d2d87","attributes":{},"type":"sensitive_data_scanner_configuration","relationships":{"groups":{"data":[]}}},"meta":{"version":275277,"count_limit":250,"group_count_limit":20,"is_pci_compliant":false,"has_highlight_enabled":true,"has_multi_pass_enabled":true,"has_cascading_enabled":false,"is_configuration_superseded":false,"is_float_sampling_rate_enabled":false,"min_sampling_rate":10.0}}
13+
14+
'
15+
code: 200
16+
duration: 0ms
17+
headers:
18+
Content-Type:
19+
- application/json
20+
status: 200 OK
21+
- request:
22+
body: |
23+
{"data":{"attributes":{"filter":{"query":"*"},"is_enabled":false,"name":"my-test-group","product_list":["logs"],"samplings":[{"product":"logs","rate":100}]},"relationships":{"configuration":{"data":{"id":"7957915c634d4dcb581fa154157f5ad9c2947f50be632fb5599862069f4d2d87","type":"sensitive_data_scanner_configuration"}},"rules":{"data":[]}},"type":"sensitive_data_scanner_group"},"meta":{}}
24+
form: {}
25+
headers:
26+
Accept:
27+
- application/json
28+
Content-Type:
29+
- application/json
30+
id: 1
31+
method: POST
32+
url: https://api.datadoghq.com/api/v2/sensitive-data-scanner/config/groups
33+
response:
34+
body: '{"data":{"id":"18cc2267-f3cc-4c15-917d-d3efb15deb03","attributes":{"name":"my-test-group","is_enabled":false,"filter":{"query":"*"},"product_list":["logs"],"samplings":[{"product":"logs","rate":100.0}]},"type":"sensitive_data_scanner_group","relationships":{"configuration":{"data":{"id":"7957915c634d4dcb581fa154157f5ad9c2947f50be632fb5599862069f4d2d87","type":"sensitive_data_scanner_configuration"}},"rules":{"data":[]}}},"meta":{"version":275278}}
35+
36+
'
37+
code: 200
38+
duration: 0ms
39+
headers:
40+
Content-Type:
41+
- application/json
42+
status: 200 OK
43+
- request:
44+
body: |
45+
{"data":{"attributes":{"is_enabled":true,"name":"Test-Create_Scanning_Rule_with_should_save_match_returns_OK_response-1756240304","pattern":"pattern","priority":1,"tags":["sensitive_data:true"],"text_replacement":{"replacement_string":"REDACTED","should_save_match":true,"type":"replacement_string"}},"relationships":{"group":{"data":{"id":"18cc2267-f3cc-4c15-917d-d3efb15deb03","type":"sensitive_data_scanner_group"}}},"type":"sensitive_data_scanner_rule"},"meta":{}}
46+
form: {}
47+
headers:
48+
Accept:
49+
- application/json
50+
Content-Type:
51+
- application/json
52+
id: 2
53+
method: POST
54+
url: https://api.datadoghq.com/api/v2/sensitive-data-scanner/config/rules
55+
response:
56+
body: '{"data":{"id":"0e517b8a-04c1-4ae0-b57b-22b8e081190c","attributes":{"name":"Test-Create_Scanning_Rule_with_should_save_match_returns_OK_response-1756240304","namespaces":[],"excluded_namespaces":[],"pattern":"pattern","text_replacement":{"replacement_string":"REDACTED","should_save_match":true,"type":"replacement_string"},"tags":["sensitive_data:true"],"labels":[],"is_enabled":true,"priority":1},"type":"sensitive_data_scanner_rule","relationships":{"group":{"data":{"id":"18cc2267-f3cc-4c15-917d-d3efb15deb03","type":"sensitive_data_scanner_group"}}}},"meta":{"version":275279}}
57+
58+
'
59+
code: 200
60+
duration: 0ms
61+
headers:
62+
Content-Type:
63+
- application/json
64+
status: 200 OK
65+
- request:
66+
body: |
67+
{"meta":{}}
68+
form: {}
69+
headers:
70+
Accept:
71+
- application/json
72+
Content-Type:
73+
- application/json
74+
id: 3
75+
method: DELETE
76+
url: https://api.datadoghq.com/api/v2/sensitive-data-scanner/config/rules/0e517b8a-04c1-4ae0-b57b-22b8e081190c
77+
response:
78+
body: '{"meta":{"version":275280}}
79+
80+
'
81+
code: 200
82+
duration: 0ms
83+
headers:
84+
Content-Type:
85+
- application/json
86+
status: 200 OK
87+
- request:
88+
body: |
89+
{"meta":{}}
90+
form: {}
91+
headers:
92+
Accept:
93+
- application/json
94+
Content-Type:
95+
- application/json
96+
id: 4
97+
method: DELETE
98+
url: https://api.datadoghq.com/api/v2/sensitive-data-scanner/config/groups/18cc2267-f3cc-4c15-917d-d3efb15deb03
99+
response:
100+
body: '{"meta":{"version":275281}}
101+
102+
'
103+
code: 200
104+
duration: 0ms
105+
headers:
106+
Content-Type:
107+
- application/json
108+
status: 200 OK
109+
version: 2

tests/scenarios/features/v2/sensitive_data_scanner.feature

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ Feature: Sensitive Data Scanner
5050
And the response "data.attributes.included_keyword_configuration.character_count" is equal to 35
5151
And the response "data.attributes.included_keyword_configuration.keywords[0]" is equal to "credit card"
5252

53+
@team:DataDog/sensitive-data-scanner
54+
Scenario: Create Scanning Rule with should_save_match returns "OK" response
55+
Given a valid "configuration" in the system
56+
And there is a valid "scanning_group" in the system
57+
And new "CreateScanningRule" request
58+
And body with value {"meta":{},"data":{"type":"sensitive_data_scanner_rule","attributes":{"name":"{{ unique }}","pattern":"pattern","text_replacement":{"type":"replacement_string","replacement_string":"REDACTED","should_save_match":true},"tags":["sensitive_data:true"],"is_enabled":true,"priority":1},"relationships":{"group":{"data":{"type":"{{ group.data.type }}","id":"{{ group.data.id }}"}}}}}
59+
When the request is sent
60+
Then the response status is 200 OK
61+
And the response "data.type" is equal to "sensitive_data_scanner_rule"
62+
And the response "data.attributes.name" is equal to "{{ unique }}"
63+
5364
@generated @skip @team:DataDog/sensitive-data-scanner
5465
Scenario: Delete Scanning Group returns "Bad Request" response
5566
Given new "DeleteScanningGroup" request

0 commit comments

Comments
 (0)