From 89a91f8929305f5c89b2a1217bb80887ed64872e Mon Sep 17 00:00:00 2001 From: Julien Doutre Date: Wed, 2 Aug 2023 13:17:29 +0200 Subject: [PATCH 1/3] Schema change proposal for Atomic Red Team support --- pkg/threatest/parser/main.go | 7 ++- pkg/threatest/parser/parser.go | 74 ++++++++++++++++++++++------- schemas/atomicRedTeam.schema.json | 27 +++++++++++ schemas/localDetonator.schema.json | 21 +++++++- schemas/remoteDetonator.schema.json | 21 +++++++- 5 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 schemas/atomicRedTeam.schema.json diff --git a/pkg/threatest/parser/main.go b/pkg/threatest/parser/main.go index 78f3175..13504ce 100644 --- a/pkg/threatest/parser/main.go +++ b/pkg/threatest/parser/main.go @@ -2,12 +2,13 @@ package parser import ( "fmt" + "strings" + "time" + "github.com/datadog/threatest/pkg/threatest" "github.com/datadog/threatest/pkg/threatest/detonators" "github.com/datadog/threatest/pkg/threatest/matchers/datadog" "sigs.k8s.io/yaml" // we use this library as it provides a handy "YAMLToJSON" function - "strings" - "time" ) // Parse turns a YAML input string into a list of Threatest scenarios @@ -42,9 +43,11 @@ func buildScenarios(parsed *ThreatestSchemaJson, sshHostname string, sshUsername // Detonation if localDetonator := parsedScenario.Detonate.LocalDetonator; localDetonator != nil { + // TODO: handle Atomic Red Team commandToRun := strings.Join(parsedScenario.Detonate.LocalDetonator.Commands, "; ") scenario.Detonator = detonators.NewCommandDetonator(&detonators.LocalCommandExecutor{}, commandToRun) } else if remoteDetonator := parsedScenario.Detonate.RemoteDetonator; remoteDetonator != nil { + // TODO: handle Atomic Red Team commandToRun := strings.Join(remoteDetonator.Commands, "; ") //TODO: decouple //TODO: confirm 1 SSH executor per attack makes sense diff --git a/pkg/threatest/parser/parser.go b/pkg/threatest/parser/parser.go index 69284d7..b9f1102 100644 --- a/pkg/threatest/parser/parser.go +++ b/pkg/threatest/parser/parser.go @@ -5,24 +5,21 @@ package parser import "encoding/json" import "fmt" -// UnmarshalJSON implements json.Unmarshaler. -func (j *DatadogSecuritySignalSchemaJson) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["name"]; !ok || v == nil { - return fmt.Errorf("field name in DatadogSecuritySignalSchemaJson: required") - } - type Plain DatadogSecuritySignalSchemaJson - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = DatadogSecuritySignalSchemaJson(plain) - return nil +// Configuration of an Atomic Red Team test case +type AtomicRedTeamSchemaJson struct { + // Inputs for the Atomic Red Team test case + Inputs AtomicRedTeamSchemaJsonInputs `json:"inputs,omitempty" yaml:"inputs,omitempty" mapstructure:"inputs,omitempty"` + + // Atomic Red Team test case name + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // MITRE ATT&CK technique ID + Technique string `json:"technique" yaml:"technique" mapstructure:"technique"` } +// Inputs for the Atomic Red Team test case +type AtomicRedTeamSchemaJsonInputs map[string]string + // Definition of an AWS CLI detonation type AwsCliDetonatorSchemaJson struct { // Script corresponds to the JSON schema field "script". @@ -40,12 +37,18 @@ type DatadogSecuritySignalSchemaJson struct { // Definition of a local command detonation type LocalDetonatorSchemaJson struct { + // AtomicReadTeam corresponds to the JSON schema field "atomicReadTeam". + AtomicReadTeam *AtomicRedTeamSchemaJson `json:"atomicReadTeam,omitempty" yaml:"atomicReadTeam,omitempty" mapstructure:"atomicReadTeam,omitempty"` + // Commands corresponds to the JSON schema field "commands". Commands []string `json:"commands,omitempty" yaml:"commands,omitempty" mapstructure:"commands,omitempty"` } // Definition of a remote command detonation type RemoteDetonatorSchemaJson struct { + // AtomicReadTeam corresponds to the JSON schema field "atomicReadTeam". + AtomicReadTeam *AtomicRedTeamSchemaJson `json:"atomicReadTeam,omitempty" yaml:"atomicReadTeam,omitempty" mapstructure:"atomicReadTeam,omitempty"` + // Commands corresponds to the JSON schema field "commands". Commands []string `json:"commands,omitempty" yaml:"commands,omitempty" mapstructure:"commands,omitempty"` } @@ -73,6 +76,45 @@ type ThreatestSchemaJsonScenariosElemDetonate struct { StratusRedTeamDetonator *StratusRedTeamDetonatorSchemaJson `json:"stratusRedTeamDetonator,omitempty" yaml:"stratusRedTeamDetonator,omitempty" mapstructure:"stratusRedTeamDetonator,omitempty"` } +// UnmarshalJSON implements json.Unmarshaler. +func (j *AtomicRedTeamSchemaJson) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in AtomicRedTeamSchemaJson: required") + } + if v, ok := raw["technique"]; !ok || v == nil { + return fmt.Errorf("field technique in AtomicRedTeamSchemaJson: required") + } + type Plain AtomicRedTeamSchemaJson + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = AtomicRedTeamSchemaJson(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *DatadogSecuritySignalSchemaJson) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in DatadogSecuritySignalSchemaJson: required") + } + type Plain DatadogSecuritySignalSchemaJson + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = DatadogSecuritySignalSchemaJson(plain) + return nil +} + // Expectations type ThreatestSchemaJsonScenariosElemExpectationsElem struct { // DatadogSecuritySignal corresponds to the JSON schema field diff --git a/schemas/atomicRedTeam.schema.json b/schemas/atomicRedTeam.schema.json new file mode 100644 index 0000000..93cc203 --- /dev/null +++ b/schemas/atomicRedTeam.schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/datadog/threatest/atomicRedTeam.schema.json", + "description": "Configuration of an Atomic Red Team test case", + "type": "object", + "required": [ + "technique", + "name" + ], + "properties": { + "technique": { + "type": "string", + "description": "MITRE ATT&CK technique ID" + }, + "name": { + "type": "string", + "description": "Atomic Red Team test case name" + }, + "inputs": { + "type": "object", + "description": "Inputs for the Atomic Red Team test case", + "additionalProperties": { + "type": "string" + } + } + } +} diff --git a/schemas/localDetonator.schema.json b/schemas/localDetonator.schema.json index 2d196ee..e2f8017 100644 --- a/schemas/localDetonator.schema.json +++ b/schemas/localDetonator.schema.json @@ -1,10 +1,27 @@ { "type": "object", "description": "Definition of a local command detonation", + "oneOf": [ + { + "required": [ + "commands" + ] + }, + { + "required": [ + "atomicRedTeam" + ] + } + ], "properties": { "commands": { "type": "array", - "items": {"type": "string"} + "items": { + "type": "string" + } + }, + "atomicReadTeam": { + "$ref": "atomicRedTeam.schema.json" } } -} \ No newline at end of file +} diff --git a/schemas/remoteDetonator.schema.json b/schemas/remoteDetonator.schema.json index e810129..04ef8d3 100644 --- a/schemas/remoteDetonator.schema.json +++ b/schemas/remoteDetonator.schema.json @@ -1,10 +1,27 @@ { "type": "object", "description": "Definition of a remote command detonation", + "oneOf": [ + { + "required": [ + "commands" + ] + }, + { + "required": [ + "atomicRedTeam" + ] + } + ], "properties": { "commands": { "type": "array", - "items": {"type": "string"} + "items": { + "type": "string" + } + }, + "atomicReadTeam": { + "$ref": "atomicRedTeam.schema.json" } } -} \ No newline at end of file +} From 4e6fcdef6d4991eeb64e4bac63935d3f46553025 Mon Sep 17 00:00:00 2001 From: Julien Doutre Date: Wed, 2 Aug 2023 22:22:55 +0200 Subject: [PATCH 2/3] Add an option to specify the art version to use --- pkg/threatest/parser/parser.go | 4 ++++ schemas/atomicRedTeam.schema.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/pkg/threatest/parser/parser.go b/pkg/threatest/parser/parser.go index b9f1102..e663038 100644 --- a/pkg/threatest/parser/parser.go +++ b/pkg/threatest/parser/parser.go @@ -15,6 +15,10 @@ type AtomicRedTeamSchemaJson struct { // MITRE ATT&CK technique ID Technique string `json:"technique" yaml:"technique" mapstructure:"technique"` + + // An optional commit hash pointing to the Atomic Red Team version to use. Will + // default to the latest commit of the `main` branch. + Version *string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version,omitempty"` } // Inputs for the Atomic Red Team test case diff --git a/schemas/atomicRedTeam.schema.json b/schemas/atomicRedTeam.schema.json index 93cc203..09b02eb 100644 --- a/schemas/atomicRedTeam.schema.json +++ b/schemas/atomicRedTeam.schema.json @@ -22,6 +22,10 @@ "additionalProperties": { "type": "string" } + }, + "version": { + "type": "string", + "description": "An optional commit hash pointing to the Atomic Red Team version to use. Will default to the latest commit of the `main` branch." } } } From 2238b7dbe310cc5ebf35c76f1916a4c1fba04154 Mon Sep 17 00:00:00 2001 From: Julien Doutre Date: Thu, 17 Aug 2023 18:01:02 +0200 Subject: [PATCH 3/3] Add basic support for atomic red team --- go.mod | 2 +- pkg/atomic/fmt.go | 47 +++++++++++++++++++++ pkg/atomic/fmt_test.go | 63 +++++++++++++++++++++++++++++ pkg/atomic/get.go | 46 +++++++++++++++++++++ pkg/atomic/spec.go | 41 +++++++++++++++++++ pkg/threatest/parser/main.go | 47 +++++++++++++++++++-- pkg/threatest/parser/parser.go | 2 +- pkg/threatest/parser/parser_test.go | 24 ++++++++++- schemas/atomicRedTeam.schema.json | 2 +- 9 files changed, 265 insertions(+), 9 deletions(-) create mode 100644 pkg/atomic/fmt.go create mode 100644 pkg/atomic/fmt_test.go create mode 100644 pkg/atomic/get.go create mode 100644 pkg/atomic/spec.go diff --git a/go.mod b/go.mod index 1bfba64..d1ea558 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/stretchr/testify v1.8.1 golang.org/x/crypto v0.7.0 gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 + gopkg.in/yaml.v3 v3.0.1 sigs.k8s.io/yaml v1.3.0 ) @@ -104,7 +105,6 @@ require ( google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.25.4 // indirect k8s.io/apimachinery v0.25.4 // indirect k8s.io/client-go v0.25.4 // indirect diff --git a/pkg/atomic/fmt.go b/pkg/atomic/fmt.go new file mode 100644 index 0000000..b021f80 --- /dev/null +++ b/pkg/atomic/fmt.go @@ -0,0 +1,47 @@ +package atomic + +import ( + "fmt" + "strings" +) + +func (t *Test) FormatCommand(arguments map[string]string) (string, error) { + if t.Executor.Name != "bash" { + return "", fmt.Errorf("invalid executor `%s`, only `bash` is currently supported", t.Executor.Name) + } + + if t.Executor.Command == nil { + return "", fmt.Errorf("no command was specified for this test") + } + + // TODO: handle dependencies install + // for _, dependency := range t.Dependencies { + // t.InterpolateCommand(dependency.PreReqCommand, arguments) + // t.InterpolateCommand(dependency.GetPreReqCommand, arguments) + // } + + // TODO: handle additional files management + + return t.interpolateCommand(*t.Executor.Command, arguments), nil + + // TODO: handle cleanup command + // if t.Executor.CleanupCommand != nil { + // t.InterpolateCommand(*t.Executor.CleanupCommand, arguments) + // } +} + +func (t *Test) interpolateCommand(command string, arguments map[string]string) string { + for parameterName, parameterDefinition := range t.InputArguments { + var parameterValue string + + if _, ok := arguments[parameterName]; ok { + parameterValue = arguments[parameterName] + } else if parameterDefinition.Default != nil { + parameterValue = *parameterDefinition.Default + } + + command = strings.ReplaceAll(command, fmt.Sprintf("${%s}", parameterName), parameterValue) + } + + return command +} diff --git a/pkg/atomic/fmt_test.go b/pkg/atomic/fmt_test.go new file mode 100644 index 0000000..b5eec11 --- /dev/null +++ b/pkg/atomic/fmt_test.go @@ -0,0 +1,63 @@ +package atomic + +import ( + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/stretchr/testify/assert" +) + +func TestInterpolateCommand(t *testing.T) { + testCases := map[string]struct { + test *Test + arguments map[string]string + expectedOutput string + }{ + "no templating and no arguments": { + test: &Test{ + Executor: Executor{Command: aws.String("echo 'Hello World!'")}, + InputArguments: nil, + }, + arguments: nil, + expectedOutput: "echo 'Hello World!'", + }, + "no templating and some arguments": { + test: &Test{ + Executor: Executor{Command: aws.String("echo 'Hello World!'")}, + InputArguments: map[string]InputArgument{"first_name": {}, "last_name": {}}, + }, + arguments: map[string]string{"first_name": "John", "last_name": "Doe"}, + expectedOutput: "echo 'Hello World!'", + }, + "templating and no arguments": { + test: &Test{ + Executor: Executor{Command: aws.String("echo 'Hello ${first_name} ${last_name}!'")}, + InputArguments: map[string]InputArgument{"first_name": {}, "last_name": {}}, + }, + arguments: nil, + expectedOutput: "echo 'Hello !'", + }, + "templating and some arguments": { + test: &Test{ + Executor: Executor{Command: aws.String("echo 'Hello ${first_name} ${last_name}!'")}, + InputArguments: map[string]InputArgument{"first_name": {}, "last_name": {}}, + }, + arguments: map[string]string{"first_name": "John"}, + expectedOutput: "echo 'Hello John !'", + }, + "templating and all arguments": { + test: &Test{ + Executor: Executor{Command: aws.String("echo 'Hello ${first_name} ${last_name}!'")}, + InputArguments: map[string]InputArgument{"first_name": {}, "last_name": {}}, + }, + arguments: map[string]string{"first_name": "John", "last_name": "Doe"}, + expectedOutput: "echo 'Hello John Doe!'", + }, + } + + for testCaseName, testCaseData := range testCases { + t.Run(testCaseName, func(t *testing.T) { + assert.Equal(t, testCaseData.expectedOutput, testCaseData.test.interpolateCommand(*testCaseData.test.Executor.Command, testCaseData.arguments)) + }) + } +} diff --git a/pkg/atomic/get.go b/pkg/atomic/get.go new file mode 100644 index 0000000..0c0445a --- /dev/null +++ b/pkg/atomic/get.go @@ -0,0 +1,46 @@ +package atomic + +import ( + "fmt" + "io" + "net/http" + + "gopkg.in/yaml.v3" +) + +func GetTest(technique, name, version string) (*Test, error) { + tech, err := GetTechnique(technique, version) + if err != nil { + return nil, err + } + + for _, test := range tech.AtomicTests { + if test.Name == name { + return &test, nil + } + } + + return nil, fmt.Errorf("test '%s' not found for technique '%s'", technique, name) +} + +func GetTechnique(id, version string) (*Technique, error) { + url := fmt.Sprintf("https://raw.githubusercontent.com/redcanaryco/atomic-red-team/%s/atomics/%s/%s.yaml", version, id, id) + + res, err := http.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + var technique *Technique + if err := yaml.Unmarshal(body, &technique); err != nil { + return nil, err + } + + return technique, nil +} diff --git a/pkg/atomic/spec.go b/pkg/atomic/spec.go new file mode 100644 index 0000000..5b0e932 --- /dev/null +++ b/pkg/atomic/spec.go @@ -0,0 +1,41 @@ +package atomic + +// The following structures were designed following the Atomic test specification: +// https://github.com/redcanaryco/atomic-red-team/blob/master/atomic_red_team/spec.yaml + +type Technique struct { + AttackTechnique string `yaml:"attack_technique"` + DisplayName string `yaml:"display_name"` + AtomicTests []Test `yaml:"atomic_tests"` +} + +type Test struct { + Name string `yaml:"name"` + AutoGeneratedGUID string `yaml:"auto_generated_guid"` + Description string `yaml:"description"` + SupportedPlatforms []string `yaml:"supported_platforms"` + InputArguments map[string]InputArgument `yaml:"input_arguments,omitempty"` + DependencyExecutorName *string `yaml:"dependency_executor_name,omitempty"` + Dependencies []Dependency `yaml:"dependencies,omitempty"` + Executor Executor `yaml:"executor"` +} + +type InputArgument struct { + Description string `yaml:"description"` + Type string `yaml:"type"` + Default *string `yaml:"default,omitempty"` +} + +type Dependency struct { + Description string `yaml:"description"` + PreReqCommand string `yaml:"prereq_command"` + GetPreReqCommand string `yaml:"get_prereq_command"` +} + +type Executor struct { + Name string `yaml:"name"` + ElevationRequired *bool `yaml:"elevation_required,omitempty"` + Command *string `yaml:"command,omitempty"` + CleanupCommand *string `yaml:"cleanup_command,omitempty"` + Steps *string `yaml:"steps,omitempty"` +} diff --git a/pkg/threatest/parser/main.go b/pkg/threatest/parser/main.go index 13504ce..38aacb5 100644 --- a/pkg/threatest/parser/main.go +++ b/pkg/threatest/parser/main.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "github.com/datadog/threatest/pkg/atomic" "github.com/datadog/threatest/pkg/threatest" "github.com/datadog/threatest/pkg/threatest/detonators" "github.com/datadog/threatest/pkg/threatest/matchers/datadog" @@ -43,12 +44,50 @@ func buildScenarios(parsed *ThreatestSchemaJson, sshHostname string, sshUsername // Detonation if localDetonator := parsedScenario.Detonate.LocalDetonator; localDetonator != nil { - // TODO: handle Atomic Red Team - commandToRun := strings.Join(parsedScenario.Detonate.LocalDetonator.Commands, "; ") + var commandToRun string + + if localDetonator.AtomicReadTeam != nil { + version := "master" // default git tree to fetch atomic red tests from + if localDetonator.AtomicReadTeam.Version != nil { + version = *localDetonator.AtomicReadTeam.Version + } + + test, err := atomic.GetTest(localDetonator.AtomicReadTeam.Technique, localDetonator.AtomicReadTeam.Name, version) + if err != nil { + return nil, fmt.Errorf("failed to retrieve atomic red team test '%s' (%s): %w", localDetonator.AtomicReadTeam.Name, localDetonator.AtomicReadTeam.Technique, err) + } + + commandToRun, err = test.FormatCommand(localDetonator.AtomicReadTeam.Inputs) + if err != nil { + return nil, err + } + } else { + commandToRun = strings.Join(localDetonator.Commands, "; ") + } + scenario.Detonator = detonators.NewCommandDetonator(&detonators.LocalCommandExecutor{}, commandToRun) } else if remoteDetonator := parsedScenario.Detonate.RemoteDetonator; remoteDetonator != nil { - // TODO: handle Atomic Red Team - commandToRun := strings.Join(remoteDetonator.Commands, "; ") + var commandToRun string + + if remoteDetonator.AtomicReadTeam != nil { + version := "master" // default git tree to fetch atomic red tests from + if remoteDetonator.AtomicReadTeam.Version != nil { + version = *remoteDetonator.AtomicReadTeam.Version + } + + test, err := atomic.GetTest(remoteDetonator.AtomicReadTeam.Technique, remoteDetonator.AtomicReadTeam.Name, version) + if err != nil { + return nil, fmt.Errorf("failed to retrieve atomic red team test '%s' (%s): %w", remoteDetonator.AtomicReadTeam.Name, remoteDetonator.AtomicReadTeam.Technique, err) + } + + commandToRun, err = test.FormatCommand(remoteDetonator.AtomicReadTeam.Inputs) + if err != nil { + return nil, err + } + } else { + commandToRun = strings.Join(remoteDetonator.Commands, "; ") + } + //TODO: decouple //TODO: confirm 1 SSH executor per attack makes sense sshExecutor, err := detonators.NewSSHCommandExecutor(sshHostname, sshUsername, sshKey) diff --git a/pkg/threatest/parser/parser.go b/pkg/threatest/parser/parser.go index e663038..b88394d 100644 --- a/pkg/threatest/parser/parser.go +++ b/pkg/threatest/parser/parser.go @@ -17,7 +17,7 @@ type AtomicRedTeamSchemaJson struct { Technique string `json:"technique" yaml:"technique" mapstructure:"technique"` // An optional commit hash pointing to the Atomic Red Team version to use. Will - // default to the latest commit of the `main` branch. + // default to the latest commit of the `master` branch. Version *string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version,omitempty"` } diff --git a/pkg/threatest/parser/parser_test.go b/pkg/threatest/parser/parser_test.go index 5d61d5e..97f5cec 100644 --- a/pkg/threatest/parser/parser_test.go +++ b/pkg/threatest/parser/parser_test.go @@ -1,8 +1,9 @@ package parser import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestParserCorrectlyParsesValidInput(t *testing.T) { @@ -31,10 +32,25 @@ scenarios: - timeout: 15m datadogSecuritySignal: name: "Potential administrative port open to the world via AWS security group" + + # Example 3: Atomic Red Team detonation + - name: deleting an AWS GuardDuty detector + detonate: + localDetonator: + atomicRedTeam: + technique: T1562.001 + name: "AWS - GuardDuty Suspension or Deletion" + inputs: + region: us-west-1 + expectations: + - timeout: 1m + datadogSecuritySignal: + name: "AWS GuardDuty detector deleted" + severity: high ` scenarios, err := Parse([]byte(validYaml), "", "", "") assert.Nil(t, err, "parsing a valid YAML scenario file should not return an error") - assert.Len(t, scenarios, 2) + assert.Len(t, scenarios, 3) assert.Equal(t, scenarios[0].Name, "curl metadata service") assert.NotNil(t, scenarios[0].Detonator) @@ -43,4 +59,8 @@ scenarios: assert.Equal(t, scenarios[1].Name, "opening a security group to the Internet") assert.NotNil(t, scenarios[1].Detonator) assert.Len(t, scenarios[1].Assertions, 1) + + assert.Equal(t, scenarios[2].Name, "deleting an AWS GuardDuty detector") + assert.NotNil(t, scenarios[2].Detonator) + assert.Len(t, scenarios[2].Assertions, 1) } diff --git a/schemas/atomicRedTeam.schema.json b/schemas/atomicRedTeam.schema.json index 09b02eb..6441441 100644 --- a/schemas/atomicRedTeam.schema.json +++ b/schemas/atomicRedTeam.schema.json @@ -25,7 +25,7 @@ }, "version": { "type": "string", - "description": "An optional commit hash pointing to the Atomic Red Team version to use. Will default to the latest commit of the `main` branch." + "description": "An optional commit hash pointing to the Atomic Red Team version to use. Will default to the latest commit of the `master` branch." } } }