Repository: Checkmarx/kics (language: python)

Issue:
Kics does not validate if output folder is valid
### Expected Behavior

When the user sets an output folder (for results and/or logs), kics may validate if the return of file creation in this path was successful.

### Actual Behavior

Even if the user defines an output folder that is not valid (with invalid characters), at the end of the scan, kics will display that files were saved in the output folder.

#### Example of kics scan using an invalid output folder and all report formats:
![image](https://user-images.githubusercontent.com/82591370/175283081-65f31278-df7b-48b3-9e7a-eb520c77a5c4.png)

### Steps to Reproduce the Problem

Open a terminal and run: `docker run -v "c/:/path" checkmarx/kics:latest scan --no-progress -t dockerfile --path "git::https://github.com/cxlucas/kics-scan-builder" --output-path "/path/*<?>*/:??/folder"`

Kics will display that the results file was saved to the specified path, but this folder contains invalid characters, so it's not possible to create a file in this specific path.
*Note:*: Kics is converting the invalid characters to some valid characters, by this way, kics is creating the result file(s) but not exactly in the same path that kics provides on the message "Results saved to file...".

![image](https://user-images.githubusercontent.com/82591370/175282127-28c6bdd1-6925-4d4d-86b6-58b98d99b65e.png)
### Specifications

-   Version: N/A
-   Platform: N/A
-   Subsystem: N/A



Discussion / hints:

Fix patch:
diff --git a/internal/console/assets/scan-flags.json b/internal/console/assets/scan-flags.json
index 6807a0ab05c..81422eccdcf 100644
--- a/internal/console/assets/scan-flags.json
+++ b/internal/console/assets/scan-flags.json
@@ -119,7 +119,8 @@
     "flagType": "str",
     "shorthandFlag": "o",
     "defaultValue": "",
-    "usage": "directory path to store reports"
+    "usage": "directory path to store reports",
+    "validation": "validatePath"
   },
   "path": {
     "flagType": "multiStr",
diff --git a/internal/console/flags/validate.go b/internal/console/flags/validate.go
index 1a356b0aa23..8b4322b92d9 100644
--- a/internal/console/flags/validate.go
+++ b/internal/console/flags/validate.go
@@ -9,6 +9,7 @@ var flagValidationFuncs = flagValidationFuncsMap{
 	"validateMultiStrEnum":              validateMultiStrEnum,
 	"validateStrEnum":                   validateStrEnum,
 	"allQueriesID":                      allQueriesID,
+	"validatePath":                      validatePath,
 }
 
 func isQueryID(id string) bool {
diff --git a/internal/console/flags/validate_path.go b/internal/console/flags/validate_path.go
new file mode 100644
index 00000000000..9582128ff6c
--- /dev/null
+++ b/internal/console/flags/validate_path.go
@@ -0,0 +1,22 @@
+package flags
+
+import (
+	"errors"
+	"regexp"
+)
+
+func validatePath(flagName string) error {
+	relPath := `^(?:\.\.\\|\.\\|\.\.\/|\.\/|\\|\/)?(?:[^<>:"\/\\|?*]+[\\\/])*[^<>:"\/\\|?*]+(\/|\\)?$`
+	absPath := `^[a-zA-Z]:[\\\/](?:[^<>:"\/\\|?*]+[\\\/])*[^<>:"\/\\|?*]+(?:\/|\\)?$`
+	regex := regexp.MustCompile(relPath + `|` + absPath)
+
+	path := GetStrFlag(flagName)
+	isValid := regex.MatchString(path) || path == ""
+
+	if !isValid {
+		errorMsg := "the directory name you provided for the " + flagName + " flag contains invalid characters"
+		return errors.New(errorMsg)
+	}
+
+	return nil
+}


Test patch:
diff --git a/e2e/fixtures/E2E_CLI_081_RESULT.json b/e2e/fixtures/E2E_CLI_081_RESULT.json
new file mode 100644
index 00000000000..055b9cee8ca
--- /dev/null
+++ b/e2e/fixtures/E2E_CLI_081_RESULT.json
@@ -0,0 +1,55 @@
+{
+	"kics_version": "development",
+	"files_scanned": 1,
+	"lines_scanned": 78,
+	"files_parsed": 1,
+	"lines_parsed": 78,
+	"lines_ignored": 0,
+	"files_failed_to_scan": 0,
+	"queries_total": 43,
+	"queries_failed_to_execute": 0,
+	"queries_failed_to_compute_similarity_id": 0,
+	"scan_id": "console",
+	"severity_counters": {
+		"HIGH": 1,
+		"INFO": 0,
+		"LOW": 0,
+		"MEDIUM": 0,
+		"TRACE": 0
+	},
+	"total_counter": 1,
+	"total_bom_resources": 0,
+	"start": "2024-02-12T12:34:07.3154393Z",
+	"end": "2024-02-12T12:34:25.658434Z",
+	"paths": [
+		"/path/test/fixtures/test_output_path"
+	],
+	"queries": [
+		{
+			"query_name": "Azure Instance Using Basic Authentication",
+			"query_id": "6797f581-0433-4768-ae3e-7ceb2f8b138e",
+			"query_url": "https://docs.microsoft.com/en-us/azure/templates/microsoft.compute/virtualmachines?tabs=json#linuxconfiguration-object",
+			"severity": "HIGH",
+			"platform": "AzureResourceManager",
+			"category": "Best Practices",
+			"experimental": false,
+			"description": "Azure Instances should use SSH Key instead of basic authentication",
+			"description_id": "98ba05ca",
+			"files": [
+				{
+					"file_name": "..\\test\\fixtures\\test_output_path\\positive1.json",
+					"similarity_id": "42d73d5b2fa1fbcb1145ea43b7dc4ec20f92adda85c61161b6a7714b6cd86219",
+					"line": 53,
+					"resource_type": "Microsoft.Compute/virtualMachines",
+					"resource_name": "[variables('vmName')]",
+					"issue_type": "IncorrectValue",
+					"search_key": "resources.name=[variables('vmName')].properties.osProfile.linuxConfiguration.disablePasswordAuthentication",
+					"search_line": 53,
+					"search_value": "",
+					"expected_value": "'disablePasswordAuthentication' should be set to true",
+					"actual_value": "'disablePasswordAuthentication' property value is set to false"
+				}
+			]
+		}
+	]
+}
diff --git a/e2e/fixtures/E2E_CLI_082_RESULT b/e2e/fixtures/E2E_CLI_082_RESULT
new file mode 100644
index 00000000000..da4219a4fa0
--- /dev/null
+++ b/e2e/fixtures/E2E_CLI_082_RESULT
@@ -0,0 +1,2 @@
+Error: the directory name you provided for the output-path flag contains invalid characters
+{{.ScanHelp}}
diff --git a/e2e/testcases/e2e-cli-081_output_path_valid.go b/e2e/testcases/e2e-cli-081_output_path_valid.go
new file mode 100644
index 00000000000..a362f3a16d5
--- /dev/null
+++ b/e2e/testcases/e2e-cli-081_output_path_valid.go
@@ -0,0 +1,26 @@
+package testcases
+
+// E2E-CLI-081 - KICS  scan
+// should check if output path is valid
+func init() { //nolint
+	testSample := TestCase{
+		Name: "should check if output path is valid [E2E-CLI-081]",
+		Args: args{
+			Args: []cmdArgs{
+				[]string{"scan", "-o", "/path/e2e/output",
+					"--output-name", "E2E_CLI_081_RESULT",
+					"-p", "\"/path/test/fixtures/test_output_path\"",
+				},
+			},
+			ExpectedResult: []ResultsValidation{
+				{
+					ResultsFile:    "E2E_CLI_081_RESULT",
+					ResultsFormats: []string{"json"},
+				},
+			},
+		},
+		WantStatus: []int{50},
+	}
+
+	Tests = append(Tests, testSample)
+}
diff --git a/e2e/testcases/e2e-cli-082_output_path_invalid.go b/e2e/testcases/e2e-cli-082_output_path_invalid.go
new file mode 100644
index 00000000000..b68ef31b3d9
--- /dev/null
+++ b/e2e/testcases/e2e-cli-082_output_path_invalid.go
@@ -0,0 +1,23 @@
+package testcases
+
+// E2E-CLI-082 - KICS  scan
+// should check if output path is invalid
+func init() { //nolint
+	testSample := TestCase{
+		Name: "should check if output path is invalid [E2E-CLI-082]",
+		Args: args{
+			Args: []cmdArgs{
+				[]string{"scan", "-o", "/path/e2e/output?",
+					"--output-name", "E2E_CLI_082_RESULT",
+					"-p", "\"/path/test/fixtures/test_output_path\"",
+				},
+			},
+			ExpectedOut: []string{
+				"E2E_CLI_082_RESULT",
+			},
+		},
+		WantStatus: []int{126},
+	}
+
+	Tests = append(Tests, testSample)
+}
diff --git a/internal/console/flags/validate_path_test.go b/internal/console/flags/validate_path_test.go
new file mode 100644
index 00000000000..d5beb232151
--- /dev/null
+++ b/internal/console/flags/validate_path_test.go
@@ -0,0 +1,136 @@
+package flags
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestFlags_validatePathEnum(t *testing.T) {
+	tests := []struct {
+		name      string
+		flagName  string
+		flagValue string
+		wantErr   bool
+	}{
+		{
+			name:      "should execute fine",
+			flagName:  "output-path",
+			flagValue: "C:/Users/user/files",
+			wantErr:   false,
+		},
+		{
+			name:      "should execute fine",
+			flagName:  "output-path",
+			flagValue: "/file",
+			wantErr:   false,
+		},
+		{
+			name:      "should execute fine",
+			flagName:  "output-path",
+			flagValue: "file",
+			wantErr:   false,
+		},
+		{
+			name:      "should execute fine",
+			flagName:  "output-path",
+			flagValue: "C:\\Users\\user\\.file",
+			wantErr:   false,
+		},
+		{
+			name:      "should execute fine",
+			flagName:  "output-path",
+			flagValue: "/user/files",
+			wantErr:   false,
+		},
+		{
+			name:      "should execute fine",
+			flagName:  "output-path",
+			flagValue: "\\user\\file",
+			wantErr:   false,
+		},
+		{
+			name:      "should execute fine",
+			flagName:  "output-path",
+			flagValue: "user\\file",
+			wantErr:   false,
+		},
+		{
+			name:      "should execute fine",
+			flagName:  "output-path",
+			flagValue: "./user/files",
+			wantErr:   false,
+		},
+		{
+			name:      "should execute fine",
+			flagName:  "output-path",
+			flagValue: "../user/files",
+			wantErr:   false,
+		},
+		{
+			name:      "should return an error regarding invalid characters (*)",
+			flagName:  "output-path",
+			flagValue: "../user/fil*es",
+			wantErr:   true,
+		},
+		{
+			name:      "should return an error regarding invalid characters (|)",
+			flagName:  "output-path",
+			flagValue: "C:/Users/user/files/|",
+			wantErr:   true,
+		},
+		{
+			name:      "should return an error regarding invalid characters (\")",
+			flagName:  "output-path",
+			flagValue: "C:/Users/user/files/\"",
+			wantErr:   true,
+		},
+		{
+			name:      "should return an error regarding invalid characters (?)",
+			flagName:  "output-path",
+			flagValue: "C:/Users/user/files/?",
+			wantErr:   true,
+		},
+		{
+			name:      "should return an error regarding invalid characters (?)",
+			flagName:  "output-path",
+			flagValue: "..\file?",
+			wantErr:   true,
+		},
+		{
+			name:      "should return an error regarding invalid characters (>)",
+			flagName:  "output-path",
+			flagValue: "C:/Users/user/files/>",
+			wantErr:   true,
+		},
+		{
+			name:      "should return an error regarding invalid characters (<)",
+			flagName:  "output-path",
+			flagValue: "C:/Users/user/files/<",
+			wantErr:   true,
+		},
+		{
+			name:      "should return an error regarding invalid characters (*)",
+			flagName:  "output-path",
+			flagValue: "C:/Users/user/files/*",
+			wantErr:   true,
+		},
+		{
+			name:      "should return an error regarding invalid characters",
+			flagName:  "output-path",
+			flagValue: "c:/*<?>*/:??/folder",
+			wantErr:   true,
+		},
+	}
+	for _, test := range tests {
+		flagsStrReferences[test.flagName] = &test.flagValue
+		t.Run(test.name, func(t *testing.T) {
+			gotErr := validatePath(test.flagName)
+			if !test.wantErr {
+				require.NoError(t, gotErr)
+			} else {
+				require.Error(t, gotErr)
+			}
+		})
+	}
+}
diff --git a/test/fixtures/test_output_path/positive1.json b/test/fixtures/test_output_path/positive1.json
new file mode 100644
index 00000000000..4c3b06490fc
--- /dev/null
+++ b/test/fixtures/test_output_path/positive1.json
@@ -0,0 +1,77 @@
+{
+  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+  "contentVersion": "1.0.0.0",
+  "parameters": {
+    "projectName": {
+      "type": "string",
+      "metadata": {
+        "description": "Specifies a name for generating resource names."
+      }
+    },
+    "location": {
+      "type": "string",
+      "defaultValue": "[resourceGroup().location]",
+      "metadata": {
+        "description": "Specifies the location for all resources."
+      }
+    },
+    "adminUsername": {
+      "type": "string",
+      "metadata": {
+        "description": "Specifies a username for the Virtual Machine."
+      }
+    },
+    "vmSize": {
+      "type": "string",
+      "defaultValue": "Standard_D2s_v3",
+      "metadata": {
+        "description": "description"
+      }
+    }
+  },
+  "variables": {
+    "vmName": "[concat(parameters('projectName'), '-vm')]",
+    "networkInterfaceName": "[concat(parameters('projectName'), '-nic')]"
+  },
+  "resources": [
+    {
+      "type": "Microsoft.Compute/virtualMachines",
+      "apiVersion": "2021-03-01",
+      "name": "[variables('vmName')]",
+      "location": "[parameters('location')]",
+      "dependsOn": [
+        "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]"
+      ],
+      "properties": {
+        "hardwareProfile": {
+          "vmSize": "[parameters('vmSize')]"
+        },
+        "osProfile": {
+          "computerName": "[variables('vmName')]",
+          "adminUsername": "[parameters('adminUsername')]",
+          "linuxConfiguration": {
+            "disablePasswordAuthentication": false
+          }
+        },
+        "storageProfile": {
+          "imageReference": {
+            "publisher": "Canonical",
+            "offer": "UbuntuServer",
+            "sku": "18.04-LTS",
+            "version": "latest"
+          },
+          "osDisk": {
+            "createOption": "FromImage"
+          }
+        },
+        "networkProfile": {
+          "networkInterfaces": [
+            {
+              "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('networkInterfaceName'))]"
+            }
+          ]
+        }
+      }
+    }
+  ]
+}