diff --git a/cmd/root.go b/cmd/root.go index 7a669dad..b66d1cf9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -218,6 +218,9 @@ yq -P -oy sample.json panic(err) } + rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredSecurityPreferences.DisableEnvOps, "security-disable-env-ops", "", false, "Disable env related operations.") + rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredSecurityPreferences.DisableFileOps, "security-disable-file-ops", "", false, "Disable file related operations (e.g. load)") + rootCmd.AddCommand( createEvaluateSequenceCommand(), createEvaluateAllCommand(), diff --git a/pkg/yqlib/doc/operators/env-variable-operators.md b/pkg/yqlib/doc/operators/env-variable-operators.md index c616f66c..4c39a216 100644 --- a/pkg/yqlib/doc/operators/env-variable-operators.md +++ b/pkg/yqlib/doc/operators/env-variable-operators.md @@ -29,6 +29,9 @@ as follows: yq '(.. | select(tag == "!!str")) |= envsubst' file.yaml ``` +## Disabling env operators +If required, you can use the `--security-disable-env-ops` to disable env operations. + ## Read string environment variable Running @@ -254,3 +257,39 @@ will output Error: variable ${notThere} not set ``` +## env() operation fails when security is enabled +Use `--security-disable-env-ops` to disable env operations for security. + +Running +```bash +yq --null-input 'env("MYENV")' +``` +will output +```bash +Error: env operations have been disabled +``` + +## strenv() operation fails when security is enabled +Use `--security-disable-env-ops` to disable env operations for security. + +Running +```bash +yq --null-input 'strenv("MYENV")' +``` +will output +```bash +Error: env operations have been disabled +``` + +## envsubst() operation fails when security is enabled +Use `--security-disable-env-ops` to disable env operations for security. + +Running +```bash +yq --null-input '"value: ${MYENV}" | envsubst' +``` +will output +```bash +Error: env operations have been disabled +``` + diff --git a/pkg/yqlib/doc/operators/headers/env-variable-operators.md b/pkg/yqlib/doc/operators/headers/env-variable-operators.md index b8d795c6..643bbd3e 100644 --- a/pkg/yqlib/doc/operators/headers/env-variable-operators.md +++ b/pkg/yqlib/doc/operators/headers/env-variable-operators.md @@ -29,3 +29,6 @@ as follows: yq '(.. | select(tag == "!!str")) |= envsubst' file.yaml ``` +## Disabling env operators +If required, you can use the `--security-disable-env-ops` to disable env operations. + diff --git a/pkg/yqlib/doc/operators/headers/load.md b/pkg/yqlib/doc/operators/headers/load.md index d6ad6085..da007dba 100644 --- a/pkg/yqlib/doc/operators/headers/load.md +++ b/pkg/yqlib/doc/operators/headers/load.md @@ -46,3 +46,7 @@ this.is = a properties file ``` bXkgc2VjcmV0IGNoaWxsaSByZWNpcGUgaXMuLi4u ``` + +## Disabling file operators +If required, you can use the `--security-disable-file-ops` to disable file operations. + diff --git a/pkg/yqlib/doc/operators/load.md b/pkg/yqlib/doc/operators/load.md index dc0463aa..588e0076 100644 --- a/pkg/yqlib/doc/operators/load.md +++ b/pkg/yqlib/doc/operators/load.md @@ -47,6 +47,10 @@ this.is = a properties file bXkgc2VjcmV0IGNoaWxsaSByZWNpcGUgaXMuLi4u ``` +## Disabling file operators +If required, you can use the `--security-disable-file-ops` to disable file operations. + + ## Simple example Given a sample.yml file of: ```yaml @@ -194,3 +198,63 @@ cool: things more_stuff: my secret chilli recipe is.... ``` +## load() operation fails when security is enabled +Use `--security-disable-file-ops` to disable file operations for security. + +Running +```bash +yq --null-input 'load("../../examples/thing.yml")' +``` +will output +```bash +Error: file operations have been disabled +``` + +## load_str() operation fails when security is enabled +Use `--security-disable-file-ops` to disable file operations for security. + +Running +```bash +yq --null-input 'load_str("../../examples/thing.yml")' +``` +will output +```bash +Error: file operations have been disabled +``` + +## load_xml() operation fails when security is enabled +Use `--security-disable-file-ops` to disable file operations for security. + +Running +```bash +yq --null-input 'load_xml("../../examples/small.xml")' +``` +will output +```bash +Error: file operations have been disabled +``` + +## load_props() operation fails when security is enabled +Use `--security-disable-file-ops` to disable file operations for security. + +Running +```bash +yq --null-input 'load_props("../../examples/small.properties")' +``` +will output +```bash +Error: file operations have been disabled +``` + +## load_base64() operation fails when security is enabled +Use `--security-disable-file-ops` to disable file operations for security. + +Running +```bash +yq --null-input 'load_base64("../../examples/base64.txt")' +``` +will output +```bash +Error: file operations have been disabled +``` + diff --git a/pkg/yqlib/operator_env.go b/pkg/yqlib/operator_env.go index 4605804a..6f0f938e 100644 --- a/pkg/yqlib/operator_env.go +++ b/pkg/yqlib/operator_env.go @@ -17,6 +17,9 @@ type envOpPreferences struct { } func envOperator(_ *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + if ConfiguredSecurityPreferences.DisableEnvOps { + return Context{}, fmt.Errorf("env operations have been disabled") + } envName := expressionNode.Operation.CandidateNode.Value log.Debug("EnvOperator, env name:", envName) @@ -54,6 +57,9 @@ func envOperator(_ *dataTreeNavigator, context Context, expressionNode *Expressi } func envsubstOperator(_ *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + if ConfiguredSecurityPreferences.DisableEnvOps { + return Context{}, fmt.Errorf("env operations have been disabled") + } var results = list.New() preferences := envOpPreferences{} if expressionNode.Operation.Preferences != nil { diff --git a/pkg/yqlib/operator_env_test.go b/pkg/yqlib/operator_env_test.go index e80508c3..f6cfc439 100644 --- a/pkg/yqlib/operator_env_test.go +++ b/pkg/yqlib/operator_env_test.go @@ -250,3 +250,40 @@ func TestEnvOperatorScenarios(t *testing.T) { } documentOperatorScenarios(t, "env-variable-operators", envOperatorScenarios) } + +var envOperatorSecurityDisabledScenarios = []expressionScenario{ + { + description: "env() operation fails when security is enabled", + subdescription: "Use `--security-disable-env-ops` to disable env operations for security.", + expression: `env("MYENV")`, + expectedError: "env operations have been disabled", + }, + { + description: "strenv() operation fails when security is enabled", + subdescription: "Use `--security-disable-env-ops` to disable env operations for security.", + expression: `strenv("MYENV")`, + expectedError: "env operations have been disabled", + }, + { + description: "envsubst() operation fails when security is enabled", + subdescription: "Use `--security-disable-env-ops` to disable env operations for security.", + expression: `"value: ${MYENV}" | envsubst`, + expectedError: "env operations have been disabled", + }, +} + +func TestEnvOperatorSecurityDisabledScenarios(t *testing.T) { + // Save original security preferences + originalDisableEnvOps := ConfiguredSecurityPreferences.DisableEnvOps + defer func() { + ConfiguredSecurityPreferences.DisableEnvOps = originalDisableEnvOps + }() + + // Test that env() fails when DisableEnvOps is true + ConfiguredSecurityPreferences.DisableEnvOps = true + + for _, tt := range envOperatorSecurityDisabledScenarios { + testScenario(t, &tt) + } + appendOperatorDocumentScenario(t, "env-variable-operators", envOperatorSecurityDisabledScenarios) +} diff --git a/pkg/yqlib/operator_load.go b/pkg/yqlib/operator_load.go index 970b9e5b..a4a2e506 100644 --- a/pkg/yqlib/operator_load.go +++ b/pkg/yqlib/operator_load.go @@ -63,6 +63,9 @@ func loadWithDecoder(filename string, decoder Decoder) (*CandidateNode, error) { func loadStringOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { log.Debugf("loadString") + if ConfiguredSecurityPreferences.DisableFileOps { + return Context{}, fmt.Errorf("file operations have been disabled") + } var results = list.New() @@ -94,6 +97,9 @@ func loadStringOperator(d *dataTreeNavigator, context Context, expressionNode *E func loadOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { log.Debugf("loadOperator") + if ConfiguredSecurityPreferences.DisableFileOps { + return Context{}, fmt.Errorf("file operations have been disabled") + } loadPrefs := expressionNode.Operation.Preferences.(loadPrefs) diff --git a/pkg/yqlib/operator_load_test.go b/pkg/yqlib/operator_load_test.go index 15acb2d9..aab885ae 100644 --- a/pkg/yqlib/operator_load_test.go +++ b/pkg/yqlib/operator_load_test.go @@ -131,3 +131,52 @@ func TestLoadScenarios(t *testing.T) { } documentOperatorScenarios(t, "load", loadScenarios) } + +var loadOperatorSecurityDisabledScenarios = []expressionScenario{ + { + description: "load() operation fails when security is enabled", + subdescription: "Use `--security-disable-file-ops` to disable file operations for security.", + expression: `load("../../examples/thing.yml")`, + expectedError: "file operations have been disabled", + }, + { + description: "load_str() operation fails when security is enabled", + subdescription: "Use `--security-disable-file-ops` to disable file operations for security.", + expression: `load_str("../../examples/thing.yml")`, + expectedError: "file operations have been disabled", + }, + { + description: "load_xml() operation fails when security is enabled", + subdescription: "Use `--security-disable-file-ops` to disable file operations for security.", + expression: `load_xml("../../examples/small.xml")`, + expectedError: "file operations have been disabled", + }, + { + description: "load_props() operation fails when security is enabled", + subdescription: "Use `--security-disable-file-ops` to disable file operations for security.", + expression: `load_props("../../examples/small.properties")`, + expectedError: "file operations have been disabled", + }, + { + description: "load_base64() operation fails when security is enabled", + subdescription: "Use `--security-disable-file-ops` to disable file operations for security.", + expression: `load_base64("../../examples/base64.txt")`, + expectedError: "file operations have been disabled", + }, +} + +func TestLoadOperatorSecurityDisabledScenarios(t *testing.T) { + // Save original security preferences + originalDisableFileOps := ConfiguredSecurityPreferences.DisableFileOps + defer func() { + ConfiguredSecurityPreferences.DisableFileOps = originalDisableFileOps + }() + + // Test that load operations fail when DisableFileOps is true + ConfiguredSecurityPreferences.DisableFileOps = true + + for _, tt := range loadOperatorSecurityDisabledScenarios { + testScenario(t, &tt) + } + appendOperatorDocumentScenario(t, "load", loadOperatorSecurityDisabledScenarios) +} diff --git a/pkg/yqlib/security_prefs.go b/pkg/yqlib/security_prefs.go new file mode 100644 index 00000000..3e2fe6b4 --- /dev/null +++ b/pkg/yqlib/security_prefs.go @@ -0,0 +1,11 @@ +package yqlib + +type SecurityPreferences struct { + DisableEnvOps bool + DisableFileOps bool +} + +var ConfiguredSecurityPreferences = SecurityPreferences{ + DisableEnvOps: false, + DisableFileOps: false, +}