├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── release.yml ├── .gitignore ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── ignoredesc.gif ├── media ├── tfsec.png └── tfsec.svg ├── package.json ├── resources ├── critical.svg ├── dark │ ├── download.svg │ ├── help.svg │ ├── refresh.svg │ ├── run.svg │ └── tfsec.svg ├── high.svg ├── light │ ├── download.svg │ ├── help.svg │ ├── refresh.svg │ ├── run.svg │ └── tfsec.svg ├── low.svg └── medium.svg ├── src ├── check_manager.ts ├── explorer │ ├── check_helpview.ts │ ├── check_result.ts │ ├── issues_treeview.ts │ ├── tfsec_treeitem.ts │ └── utils.ts ├── extension.ts ├── ignore.ts ├── snippets │ └── custom_checks.json └── tfsec_wrapper.ts ├── tfsec-explorer-usage.gif ├── tfsec.png └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "**/*.d.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: owenrumney 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Desktop (please complete the following information):** 14 | - OS: [e.g. Windows] 15 | - Tfsec Version [e.g. v0.62.0] 16 | - VSCode Version 17 | 18 | **Additional context** 19 | Add any other context about the problem here. 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: vsix-release 2 | on: 3 | push: 4 | tags: 5 | - "*" 6 | 7 | jobs: 8 | build: 9 | name: releasing vsix for extension 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout latest 14 | uses: actions/checkout@v2 15 | 16 | - name: Run npm install 17 | run: npm install 18 | 19 | - name: run the package action 20 | uses: lannonbr/vsce-action@3.0.0 21 | with: 22 | args: "publish -p $VSCE_TOKEN" 23 | env: 24 | VSCE_TOKEN: ${{ secrets.VSCE_TOKEN }} 25 | 26 | - name: Create Release 27 | id: create_release 28 | uses: actions/create-release@v1 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | with: 32 | tag_name: ${{ github.ref }} 33 | release_name: ${{ github.ref }} 34 | draft: false 35 | prerelease: false 36 | 37 | - name: Attach artifact to release 38 | uses: actions/upload-release-asset@v1 39 | with: 40 | upload_url: ${{ steps.create_release.outputs.upload_url }} 41 | asset_path: tfsec.vsix 42 | asset_name: tfsec.vsix 43 | asset_content_type: application/vsix 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | *.js 7 | package-lock.json -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | *.gif 2 | *.png 3 | !tfsec.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "tfsec" extension will be documented in this file. 4 | 5 | ### 1.11.0 6 | - Add findings to the Problems tab 7 | 8 | ### 1.10.1 9 | - Fix Windows filepaths 10 | 11 | ### 1.10.0 12 | - Fix issue with file path names in the explorer 13 | - Add context support for locally ignoring files and directories 14 | 15 | ### 1.9.0 16 | - Support new tfsec filesystem (relative path resolution) 17 | - Maintain support older versions of tfsec 18 | 19 | ### 1.8.0 20 | - Add snippets support 21 | - using `tfsec-check-file` in a yaml file to create custom check 22 | - using `tfsec-custom-check` in the existing check file to add a new custom check 23 | - Add icon on toolbar to get the version 24 | 25 | ### 1.7.5 26 | - Update the severity icons inline with Aqua colours 27 | 28 | ### 1.7.4 29 | - Fix the icons for Severity and tfsec checkname 30 | 31 | ### 1.7.3 32 | - Fix issue with tfsec `v1.0.0-rc.2` 33 | 34 | ### 1.7.2 35 | - Fix issue with glob 36 | 37 | ### 1.7.1 38 | - Minify the extension 39 | 40 | ### 1.7.0 41 | - Support multi folder workspaces 42 | - Save results in a folder with unique names 43 | 44 | ### 1.6.2 45 | - Refactor the runner to clean up extension code 46 | - clean up some redundant code 47 | 48 | ### 1.6.1 49 | - Prettify with nice icons 50 | - 51 | ### 1.6.0 52 | - Switch from ExecSync to Spawn for running tfsec 53 | - Don't show the output window so much, we know its there 54 | - Update mass ignores to always add a new line 55 | 56 | ### 1.5.0 57 | - Check for tfsec before running any commands 58 | - Add debug setting for richer output option 59 | - remove some redundant logging 60 | 61 | ### 1.4.0 62 | - Use output channel instead of terminal for better cross platform command support 63 | - Remove explicit run command and use refresh to update the list with a fresh run 64 | - Add ignore all severity 65 | - Fix the refresh after ignores have been completed 66 | - Add more information to the update output 67 | 68 | ### 1.3.1 69 | - Update the repository link 70 | 71 | ### 1.3.0 72 | - Remove dependency on codes resource for resolving legacy IDs 73 | 74 | ### 1.2.2 75 | - Add support for AVD ID 76 | 77 | ### 1.2.1 78 | - Update the logo to the AquaSecurity one 79 | 80 | ### 1.2.0 81 | - Restructure explorer to be by severity 82 | - Fix the Help view for the checks 83 | - Add "Ignore all" to ignore all instances of an issue 84 | 85 | ### 1.1.11 86 | - Add menu button to update tfsec from within vscode (post tfsec v0.39.39) 87 | - Add command to show the current version of tfsec running 88 | 89 | ### 1.1.10 90 | - Updating the codes to support latest tfsec 91 | 92 | ### 1.1.9 93 | - Handle deprecated checks better in the help window 94 | 95 | ### 1.1.8 96 | - Add setting to choose if auto running tfsec after ignore should happen 97 | 98 | ### 1.1.7 99 | - Reload the tree when tfsec is run 100 | - move single line ignores above issue 101 | 102 | ### 1.1.6 103 | - Add tfsec ignore on a same line when single line issue 104 | - add local check help to the Tfsec navigation pane 105 | - restructure code for easier disable of plugin 106 | 107 | ### 1.1.5 108 | - Only use a single terminal for tfsec, don't create a new one on each run 109 | - Add option on extension settings to turn off the ignore code resolution 110 | 111 | ### 1.1.4 112 | - add link to check page from explorer view 113 | - update the icon for the activity bar 114 | 115 | ### 1.1.3 116 | - clean up the path in the treeview to remove prefix 117 | 118 | ### 1.1.2 119 | - Add some configuration options 120 | - binary path override 121 | - deep searching 122 | - exclude downloaded modules 123 | 124 | ### 1.1.1 125 | - Fixing issues with Windows path 126 | - Switch to using --out for results file, Powershell piping seems to use UTF8 BOM which is tricky 127 | 128 | ### 1.1.0 129 | - TFsec Explorer 130 | 131 | ### 1.0.8 132 | - Fix and issue with the code resolution 133 | - Record the demo gif and add 134 | - Fix ignore placement 135 | 136 | ### 1.0.6 137 | - Add ignore functionality in the context menu. 138 | 139 | ### 1.0.5 140 | - Fix issue reloading the results file on recreation 141 | 142 | ### 1.0.4 143 | - Fix issue when run against workspace with no data 144 | 145 | ### 1.0.3 146 | - Add the treeview for current issues in the workspace 147 | 148 | ### 1.0.2 149 | - Restructuring the code 150 | 151 | ### 1.0.1 152 | - Fixes to the Readme for the marketplace page 153 | 154 | ### 1.0.0 155 | - Initial release of tfsec extension with ignore parsing 156 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 AquaSecurity 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | 3 | build: 4 | npm run-script esbuild -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tfsec 2 | 3 | ![tfsec](tfsec.png) 4 | 5 | This VS Code extension is for [tfsec](https://aquasecurity.github.io/tfsec/latest). A static analysis security scanner for your Terraform code that discovers problems with your infrastructure before hackers do. 6 | 7 | ## Features 8 | 9 | ### Findings Explorer 10 | The Findings Explorer displays an an organised view the issues that have been found in the current workspace. 11 | 12 | The code runs tfsec in a VS Code integrated terminal so you can see the the output - when it is complete, press the refresh button to reload. 13 | 14 | Right clicking on an tfsec code will let you view the associated page on [https://aquasecurity.github.io/tfsec/latest](https://aquasecurity.github.io/tfsec/latest) 15 | 16 | Issues can be ignored by right clicking the location in the explorer and selecting `ignore this issue`. 17 | 18 | ![tfsec explorer](tfsec-explorer-usage.gif) 19 | ### Ignore Code Resolution 20 | 21 | Ignore codes will be automatically resolved and the description of the error will be displayed inline. 22 | 23 | ![ignoredesc](ignoredesc.gif) 24 | 25 | ### Ignoring filepaths 26 | 27 | In the Explorer view, you can right click on a folder or .tf file and select `Ignore path during tfsec runs`. This will pass the path to `--exclude-path` when running tfsec and is only applicable to this workspace on this machine. 28 | 29 | To remove ignores, edit the `tfsec.excludedPath` in the `.vscode/settings.json` file of the current workspace. 30 | 31 | ## Release Notes 32 | 33 | ### 1.11.0 34 | - Add findings to the Problems tab 35 | 36 | ### 1.10.1 37 | - Fix Windows filepaths 38 | 39 | ### 1.10.0 40 | - Fix issue with file path names in the explorer 41 | - Add context support for locally ignoring files and directories 42 | 43 | ### 1.9.0 44 | - Support new tfsec filesystem (relative path resolution) 45 | - Maintain support older versions of tfsec 46 | 47 | ### 1.8.0 48 | - Add snippets support 49 | - using `tfsec-check-file` in a yaml file to create custom check 50 | - using `tfsec-custom-check` in the existing check file to add a new custom check 51 | - Add icon on toolbar to get the version 52 | 53 | ### 1.7.5 54 | - Update the severity icons inline with Aqua colours 55 | 56 | ### 1.7.4 57 | - Fix the icons for Severity and tfsec checkname 58 | 59 | ### 1.7.3 60 | - Fix issue with tfsec `v1.0.0-rc.2` 61 | 62 | ### 1.7.2 63 | - Fix issue with glob 64 | 65 | ### 1.7.1 66 | - Minify the extension 67 | 68 | ### 1.7.0 69 | - Support multi folder workspaces 70 | - Save results in a folder with unique names 71 | 72 | ### 1.6.2 73 | - Refactor the runner to clean up extension code 74 | - clean up some redundant code 75 | 76 | 77 | #### See Change log for more information -------------------------------------------------------------------------------- /ignoredesc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquasecurity/vscode-tfsec/55b8d7ae7be501d6c23a77d0519ab7a001543a63/ignoredesc.gif -------------------------------------------------------------------------------- /media/tfsec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquasecurity/vscode-tfsec/55b8d7ae7be501d6c23a77d0519ab7a001543a63/media/tfsec.png -------------------------------------------------------------------------------- /media/tfsec.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tfsec", 3 | "displayName": "tfsec", 4 | "publisher": "tfsec", 5 | "description": "tfsec integration for Visual Studio Code", 6 | "version": "1.11.0", 7 | "engines": { 8 | "vscode": "^1.54.0" 9 | }, 10 | "icon": "tfsec.png", 11 | "categories": [ 12 | "Other", 13 | "Linters" 14 | ], 15 | "languages": [ 16 | { 17 | "id": "terraform", 18 | "extensions": [ 19 | ".tf", 20 | ".tf.json" 21 | ], 22 | "aliases": [ 23 | "Terraform" 24 | ] 25 | } 26 | ], 27 | "activationEvents": [ 28 | "onView:tfsec.issueview", 29 | "onLanguage:terraform", 30 | "onCommand:tfsec.run", 31 | "workspaceContains:**/*.tf" 32 | ], 33 | "main": "./out/main.js", 34 | "contributes": { 35 | "configuration": { 36 | "title": "tfsec", 37 | "properties": { 38 | "tfsec.binaryPath": { 39 | "type": "string", 40 | "default": "tfsec", 41 | "description": "Path to tfsec if not already on the PATH" 42 | }, 43 | "tfsec.ignoreDownloadedModules": { 44 | "type": "boolean", 45 | "default": "true", 46 | "description": "Don't include results from downloaded modules. (Still scanned, just not included in the results)" 47 | }, 48 | "tfsec.fullDepthSearch": { 49 | "type": "boolean", 50 | "default": "true", 51 | "description": "Scan all terraform in the workspace. This will start at the top and add all terraform files into the model" 52 | }, 53 | "tfsec.resolveIgnoreCodes": { 54 | "type": "boolean", 55 | "default": "true", 56 | "description": "Add the description for ignore codes inline with the code" 57 | }, 58 | "tfsec.runOnIgnore": { 59 | "type": "boolean", 60 | "default": "true", 61 | "description": "Automatically rerun tfsec when a check failure is ignored" 62 | }, 63 | "tfsec.debug": { 64 | "type": "boolean", 65 | "default": "false", 66 | "description": "Run tfsec with vebose flag to get more information" 67 | }, 68 | "tfsec.excludedPaths": { 69 | "type": "array", 70 | "default": [], 71 | "description": "Run tfsec but exclude these folders" 72 | } 73 | } 74 | }, 75 | "commands": [ 76 | { 77 | "command": "tfsec.run", 78 | "title": "tfsec: Run tfsec against workspace", 79 | "icon": { 80 | "light": "resources/light/refresh.svg", 81 | "dark": "resources/dark/refresh.svg" 82 | } 83 | }, 84 | { 85 | "command": "tfsec.refresh", 86 | "title": "tfsec: Refresh the issue list" 87 | }, 88 | { 89 | "command": "tfsec.updatebinary", 90 | "title": "tfsec: Download the latest version of tfsec", 91 | "icon": { 92 | "light": "resources/light/download.svg", 93 | "dark": "resources/dark/download.svg" 94 | } 95 | }, 96 | { 97 | "command": "tfsec.ignore", 98 | "title": "Ignore this issue instance" 99 | }, 100 | { 101 | "command": "tfsec.ignorePath", 102 | "title": "Ignore path during tfsec runs" 103 | }, 104 | { 105 | "command": "tfsec.ignoreAll", 106 | "title": "Ignore all instances" 107 | }, 108 | { 109 | "command": "tfsec.ignoreSeverity", 110 | "title": "Ignore all instances of severity" 111 | }, 112 | { 113 | "command": "tfsec.version", 114 | "title": "tfsec: Get the current version of tfsec", 115 | "icon": { 116 | "light": "resources/light/help.svg", 117 | "dark": "resources/dark/help.svg" 118 | } 119 | } 120 | ], 121 | "viewsContainers": { 122 | "activitybar": [ 123 | { 124 | "id": "tfsec", 125 | "title": "tfsec", 126 | "icon": "media/tfsec.svg" 127 | } 128 | ] 129 | }, 130 | "views": { 131 | "tfsec": [ 132 | { 133 | "id": "tfsec.issueview", 134 | "name": "Findings Explorer", 135 | "icon": "media/tfsec.svg", 136 | "contextualTitle": "Findings Explorer" 137 | }, 138 | { 139 | "id": "tfsec.helpview", 140 | "name": "Findings Help", 141 | "type": "webview", 142 | "contextualTitle": "Findings Help" 143 | } 144 | ] 145 | }, 146 | "viewsWelcome": [ 147 | { 148 | "view": "tfsec.issueview", 149 | "contents": "No issues are found.\n[Run tfsec now](command:tfsec.run)" 150 | }, 151 | { 152 | "view": "tfsec.helpview", 153 | "contents": "No check selected. Run tfsec and choose a failed check from the explorer" 154 | } 155 | ], 156 | "menus": { 157 | "explorer/context": [ 158 | { 159 | "command": "tfsec.ignorePath", 160 | "when": "resourceExtname == .tf || explorerResourceIsFolder" 161 | } 162 | ], 163 | "commandPalette": [ 164 | { 165 | "command": "tfsec.ignore", 166 | "when": "false" 167 | } 168 | ], 169 | "view/title": [ 170 | { 171 | "command": "tfsec.run", 172 | "when": "view == tfsec.issueview", 173 | "group": "navigation@1" 174 | }, 175 | { 176 | "command": "tfsec.updatebinary", 177 | "when": "view == tfsec.issueview", 178 | "group": "navigation@2" 179 | }, 180 | { 181 | "command": "tfsec.version", 182 | "when": "view == tfsec.issueview", 183 | "group": "navigation@2" 184 | } 185 | ], 186 | "view/item/context": [ 187 | { 188 | "command": "tfsec.ignore", 189 | "when": "view == tfsec.issueview && viewItem == TFSEC_FILE_LOCATION" 190 | }, 191 | { 192 | "command": "tfsec.ignoreAll", 193 | "when": "view == tfsec.issueview && viewItem == TFSEC_CODE" 194 | }, 195 | { 196 | "command": "tfsec.ignoreSeverity", 197 | "when": "view == tfsec.issueview && viewItem == TFSEC_SEVERITY" 198 | } 199 | ] 200 | }, 201 | "snippets": [ 202 | { 203 | "language": "yaml", 204 | "path": "./src/snippets/custom_checks.json" 205 | } 206 | ] 207 | }, 208 | "scripts": { 209 | "vscode:prepublish": "npm run esbuild-base -- --minify", 210 | "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node", 211 | "esbuild": "npm run esbuild-base -- --sourcemap", 212 | "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", 213 | "test-compile": "tsc -p ./", 214 | "compile": "tsc -p ./", 215 | "pretest": "npm run compile && npm run lint", 216 | "lint": "eslint src --ext ts" 217 | }, 218 | "devDependencies": { 219 | "@types/mocha": "^8.0.4", 220 | "@types/node": "^12.20.13", 221 | "@types/semver": "^7.3.6", 222 | "@types/vscode": "^1.54.0", 223 | "@typescript-eslint/eslint-plugin": "^4.25.0", 224 | "@typescript-eslint/parser": "^4.25.0", 225 | "esbuild": "^0.14.11", 226 | "eslint": "^7.27.0", 227 | "mocha": "^8.4.0", 228 | "typescript": "^4.5.4", 229 | "vscode-test": "^1.5.0" 230 | }, 231 | "repository": { 232 | "url": "https://github.com/aquasecurity/vscode-tfsec" 233 | }, 234 | "dependencies": { 235 | "@types/uuid": "^8.3.4", 236 | "semver": "^7.3.5", 237 | "typescipt": "^1.0.0", 238 | "uuid": "^8.3.2", 239 | "vsce": "^2.10.0" 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /resources/critical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 68 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /resources/dark/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/dark/run.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/dark/tfsec.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 54 | 56 | 58 | 60 | 66 | 67 | 68 | 74 | 80 | 86 | 92 | 93 | 99 | 105 | 106 | -------------------------------------------------------------------------------- /resources/high.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 68 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /resources/light/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /resources/light/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/light/run.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/light/tfsec.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 54 | 56 | 58 | 60 | 66 | 67 | 68 | 74 | 80 | 86 | 92 | 93 | 99 | 105 | 106 | -------------------------------------------------------------------------------- /resources/low.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 68 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /resources/medium.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 68 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/check_manager.ts: -------------------------------------------------------------------------------- 1 | export class CheckManager { 2 | private static instance: CheckManager; 3 | private loadedCodes = new Map([ 4 | ["AWS001", "S3 Bucket has an ACL defined which allows public access."], 5 | ["AWS002", "S3 Bucket does not have logging enabled."], 6 | ["AWS003", "AWS Classic resource usage."], 7 | ["AWS004", "Use of plain HTTP."], 8 | ["AWS005", "Load balancer is exposed to the internet."], 9 | ["AWS006", "An ingress security group rule allows traffic from /0."], 10 | ["AWS007", "An egress security group rule allows traffic to /0."], 11 | ["AWS008", "An inline ingress security group rule allows traffic from /0."], 12 | ["AWS009", "An inline egress security group rule allows traffic to /0."], 13 | ["AWS010", "An outdated SSL policy is in use by a load balancer."], 14 | ["AWS011", "A database resource is marked as publicly accessible."], 15 | ["AWS012", "A resource has a public IP address."], 16 | ["AWS013", "Task definition defines sensitive environment variable(s)."], 17 | ["AWS014", "Launch configuration with unencrypted block device."], 18 | ["AWS015", "Unencrypted SQS queue."], 19 | ["AWS016", "Unencrypted SNS topic."], 20 | ["AWS017", "Unencrypted S3 bucket."], 21 | ["AWS018", "Missing description for security group/security group rule."], 22 | ["AWS019", "A KMS key is not configured to auto-rotate."], 23 | ["AWS020", "CloudFront distribution allows unencrypted (HTTP) communications."], 24 | ["AWS021", "CloudFront distribution uses outdated SSL/TLS protocols."], 25 | ["AWS022", "A MSK cluster allows unencrypted data in transit."], 26 | ["AWS023", "ECR repository has image scans disabled."], 27 | ["AWS024", "Kinesis stream is unencrypted."], 28 | ["AWS025", "API Gateway domain name uses outdated SSL/TLS protocols."], 29 | ["AWS031", "Elasticsearch domain isn't encrypted at rest."], 30 | ["AWS032", "Elasticsearch domain uses plaintext traffic for node to node communication."], 31 | ["AWS033", "Elasticsearch doesn't enforce HTTPS traffic."], 32 | ["AWS034", "Elasticsearch domain endpoint is using outdated TLS policy."], 33 | ["AWS035", "Unencrypted Elasticache Replication Group."], 34 | ["AWS036", "Elasticache Replication Group uses unencrypted traffic."], 35 | ["AWS037", "IAM Password policy should prevent password reuse."], 36 | ["AWS038", "IAM Password policy should have expiry less than or equal to 90 days."], 37 | ["AWS039", "IAM Password policy should have minimum password length of 14 or more characters."], 38 | ["AWS040", "IAM Password policy should have requirement for at least one symbol in the password."], 39 | ["AWS041", "IAM Password policy should have requirement for at least one number in the password."], 40 | ["AWS042", "IAM Password policy should have requirement for at least one lowercase character."], 41 | ["AWS043", "IAM Password policy should have requirement for at least one uppercase character."], 42 | ["AWS044", "AWS provider has access credentials specified."], 43 | ["AWS045", "CloudFront distribution does not have a WAF in front."], 44 | ["AWS046", "AWS IAM policy document has wildcard action statement."], 45 | ["AWS047", "AWS SQS policy document has wildcard action statement."], 46 | ["AWS048", "EFS Encryption has not been enabled"], 47 | ["AWS049", "An ingress Network ACL rule allows specific ports from /0."], 48 | ["AWS050", "An ingress Network ACL rule allows ALL ports from /0."], 49 | ["AWS051", "There is no encryption specified or encryption is disabled on the RDS Cluster."], 50 | ["AWS052", "RDS encryption has not been enabled at a DB Instance level."], 51 | ["AWS053", "Encryption for RDS Perfomance Insights should be enabled."], 52 | ["AWS057", "Domain logging should be enabled for Elastic Search domains"], 53 | ["AWS058", "Ensure that lambda function permission has a source arn specified"], 54 | ["AWS059", "Athena databases and workgroup configurations are created unencrypted at rest by default, they should be encrypted"], 55 | ["AWS060", "Athena workgroups should enforce configuration to prevent client disabling encryption"], 56 | ["AWS061", "API Gateway stages for V1 and V2 should have access logging enabled"], 57 | ["AWS062", "User data for EC2 instances must not contain sensitive AWS keys"], 58 | ["AWS063", "Cloudtrail should be enabled in all regions regardless of where your AWS resources are generally homed"], 59 | ["AWS064", "Cloudtrail log validation should be enabled to prevent tampering of log data"], 60 | ["AWS065", "Cloudtrail should be encrypted at rest to secure access to sensitive trail data"], 61 | ["AWS066", "EKS should have the encryption of secrets enabled"], 62 | ["AWS067", "EKS Clusters should have cluster control plane logging turned on"], 63 | ["AWS068", "EKS cluster should not have open CIDR range for public access"], 64 | ["AWS069", "EKS Clusters should have the public access disabled"], 65 | ["AWS070", "AWS ES Domain should have logging enabled"], 66 | ["AWS071", "Cloudfront distribution should have Access Logging configured"], 67 | ["AWS072", "Viewer Protocol Policy in Cloudfront Distribution Cache should always be set to HTTPS"], 68 | ["AWS073", "S3 Access Block should Ignore Public Acl"], 69 | ["AWS074", "S3 Access block should block public ACL"], 70 | ["AWS075", "S3 Access block should restrict public bucket to limit access"], 71 | ["AWS076", "S3 Access block should block public policy"], 72 | ["AWS077", "S3 Data should be versioned"], 73 | ["AWS078", "ECR images tags shouldn't be mutable."], 74 | ["AWS079", "aws_instance should activate session tokens for Instance Metadata Service."], 75 | ["AWS080", "CodeBuild Project artifacts encryption should not be disabled"], 76 | ["AWS081", "DAX Cluster should always encrypt data at rest"], 77 | ["AWS082", "It is AWS best practice to not use the default VPC for workflows"], 78 | ["AWS083", "Load balancers should drop invalid headers"], 79 | ["AWS084", "Root and user volumes on Workspaces should be encrypted"], 80 | ["AWS085", "Config configuration aggregator should be using all regions for source"], 81 | ["AWS086", "Point in time recovery should be enabled to protect DynamoDB table"], 82 | ["AWS087", "Redshift cluster should be deployed into a specific VPC"], 83 | ["AWS088", "Redis cluster should be backup retention turned on"], 84 | ["AWS089", "CloudWatch log groups should be encrypted"], 85 | ["AWS090", "ECS clusters should have container insights enabled"], 86 | ["AWS091", "RDS Cluster and RDS instance should have backup retention longer than default 1 day"], 87 | ["AWS092", "DynamoDB tables should use at rest encyption with a Customer Managed Key"], 88 | ["AWS093", "ECR Repository should use customer managed keys to allow more control"], 89 | ["AWS094", "Redshift clusters should use at rest encryption"], 90 | ["AWS095", "Secrets Manager should use customer managed keys"], 91 | ["AWS096", "ECS Task Definitions with EFS volumes should use in-transit encryption"], 92 | ["AWS097", "Document DB should be encrypted using customer managed keys"], 93 | ["AWS098", "Where supported use EBS optimization"], 94 | ["AZU001", "An inbound network security rule allows traffic from /0."], 95 | ["AZU002", "An outbound network security rule allows traffic to /0."], 96 | ["AZU003", "Unencrypted managed disk."], 97 | ["AZU004", "Unencrypted data lake storage."], 98 | ["AZU005", "Password authentication in use instead of SSH keys."], 99 | ["AZU006", "Ensure AKS cluster has Network Policy configured"], 100 | ["AZU007", "Ensure RBAC is enabled on AKS clusters"], 101 | ["AZU008", "Ensure AKS has an API Server Authorized IP Ranges enabled"], 102 | ["AZU009", "Ensure AKS logging to Azure Monitoring is Configured"], 103 | ["AZU010", "Ensure HTTPS is enabled on Azure Storage Account"], 104 | ["AZU011", "Storage containers in blob storage mode should not have public access"], 105 | ["AZU012", "The default action on Storage account network rules should be set to deny"], 106 | ["AZU013", "Trusted Microsoft Services should have bypass access to Storage accounts"], 107 | ["AZU014", "Storage accounts should be configured to only accept transfers that are over secure connections"], 108 | ["AZU015", "The minimum TLS version for Storage Accounts should be TLS1_2"], 109 | ["AZU016", "When using Queue Services for a storage account, logging should be enabled."], 110 | ["AZU017", "SSH access should not be accessible from the Internet, should be blocked on port 22"], 111 | ["AZU018", "Auditing should be enabled on Azure SQL Databases"], 112 | ["AZU019", "Database auditing rentention period should be longer than 90 days"], 113 | ["AZU020", "Key vault should have the network acl block specified"], 114 | ["AZU021", "Key vault should have purge protection enabled"], 115 | ["AZU022", "Key vault Secret should have a content type set"], 116 | ["AZU023", "Key Vault Secret should have an expiration date set"], 117 | ["AZU024", "RDP access should not be accessible from the Internet, should be blocked on port 3389"], 118 | ["AZU025", "Data Factory should have public access disabled, the default is enabled."], 119 | ["AZU026", "Ensure that the expiration date is set on all keys"], 120 | ["AZU027", "Synapse Workspace should have managed virtual network enabled, the default is disabled."], 121 | ["AZU028", "Ensure the Function App can only be accessed via HTTPS. The default is false."], 122 | ["GCP001", "Unencrypted compute disk."], 123 | ["GCP003", "An inbound firewall rule allows traffic from /0."], 124 | ["GCP004", "An outbound firewall rule allows traffic to /0."], 125 | ["GCP005", "Legacy ABAC permissions are enabled."], 126 | ["GCP006", "Node metadata value disables metadata concealment."], 127 | ["GCP007", "Legacy metadata endpoints enabled."], 128 | ["GCP008", "Legacy client authentication methods utilized."], 129 | ["GCP009", "Pod security policy enforcement not defined."], 130 | ["GCP010", "Shielded GKE nodes not enabled."], 131 | ["GCP011", "IAM granted directly to user."], 132 | ["GCP012", "Checks for service account defined for GKE nodes"], 133 | ["GEN001", "Potentially sensitive data stored in \"default \" value of variable."], 134 | ["GEN002", "Potentially sensitive data stored in local value."], 135 | ["GEN003", "Potentially sensitive data stored in block attribute."], 136 | ["GEN004", "Github repository shouldn't be public."] 137 | ]); 138 | 139 | private constructor() { }; 140 | 141 | static getInstance(): CheckManager { 142 | if (!CheckManager.instance) { 143 | CheckManager.instance = new CheckManager(); 144 | } 145 | 146 | return CheckManager.instance; 147 | }; 148 | 149 | get(code: string): string | undefined { 150 | return this.loadedCodes.get(code); 151 | }; 152 | }; -------------------------------------------------------------------------------- /src/explorer/check_helpview.ts: -------------------------------------------------------------------------------- 1 | import { CancellationToken, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext } from "vscode"; 2 | import { CheckResult, CheckSeverity } from "./check_result"; 3 | import { TfsecTreeItem } from "./tfsec_treeitem"; 4 | 5 | export class TfsecHelpProvider implements WebviewViewProvider { 6 | private view: Webview | undefined; 7 | 8 | resolveWebviewView(webviewView: WebviewView, _context: WebviewViewResolveContext, _token: CancellationToken): void | Thenable { 9 | this.view = webviewView.webview; 10 | this.update(null); 11 | } 12 | 13 | update(item: TfsecTreeItem | null) { 14 | if (this.view === undefined) { 15 | return; 16 | } 17 | if (item === null) { 18 | return; 19 | } 20 | const codeData = item.check; 21 | if (codeData === undefined) { 22 | this.view.html = ` 23 |

No check data available

24 | This check may no longer be valid. Check your tfsec is the latest version. 25 | `; 26 | return; 27 | } 28 | 29 | if (item.contextValue === 'TFSEC_CODE') { 30 | this.view.html = ` 31 |

Select a specific instance for more details

32 | For more information about the issue found, select a specific instance. 33 | `; 34 | return; 35 | } 36 | this.view.html = getHtml(codeData); 37 | } 38 | } 39 | 40 | function getHtml(codeData: CheckResult | CheckSeverity | undefined): string { 41 | if (codeData === undefined || codeData instanceof CheckSeverity) { 42 | return ""; 43 | } 44 | return ` 45 |

${codeData?.codeDescription}

46 | ${codeData?.summary} 47 | 48 |

ID

49 | ${codeData?.code} 50 | 51 |

Severity

52 | ${codeData?.severity} 53 | 54 |

Impact

55 | ${codeData?.impact} 56 | 57 |

Resolution

58 | ${codeData?.resolution} 59 | 60 |

Filename

61 | ${codeData?.filename} 62 | 63 |

More Information

64 | ${codeData?.docUrl} 65 | `; 66 | } 67 | -------------------------------------------------------------------------------- /src/explorer/check_result.ts: -------------------------------------------------------------------------------- 1 | import { capitalize } from './utils'; 2 | 3 | 4 | export class CheckResult { 5 | public code: string; 6 | public provider: string; 7 | public codeDescription: string; 8 | public filename: string; 9 | public startLine: number; 10 | public endLine: number; 11 | public severity: string; 12 | public summary: string; 13 | public impact: string; 14 | public resolution: string; 15 | public docUrl: any; 16 | constructor( 17 | result: any 18 | ) { 19 | this.code = result.long_id ?? result.rule_id; 20 | this.provider = result.rule_provider; 21 | this.codeDescription = result.rule_description; 22 | this.summary = result.description; 23 | this.impact = result.impact; 24 | this.resolution = result.resolution; 25 | this.filename = result.location.filename; 26 | this.startLine = result.location.start_line; 27 | this.endLine = result.location.end_line; 28 | this.severity = capitalize(result.severity); 29 | 30 | if (result.links.length > 0) { 31 | this.docUrl = result.links[0]; 32 | } 33 | } 34 | } 35 | 36 | export class CheckSeverity { 37 | public severity: string; 38 | constructor(result: any) { 39 | this.severity = capitalize(result.severity); 40 | } 41 | } -------------------------------------------------------------------------------- /src/explorer/issues_treeview.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import { sortByCode, sortBySeverity, sortResults, uniqueLocations } from './utils'; 5 | import { CheckResult, CheckSeverity } from './check_result'; 6 | import { TfsecTreeItem, TfsecTreeItemType } from './tfsec_treeitem'; 7 | import { checkServerIdentity } from 'tls'; 8 | 9 | export class TfsecIssueProvider implements vscode.TreeDataProvider { 10 | 11 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 12 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 13 | public resultData: CheckResult[] = []; 14 | private taintResults: boolean = true; 15 | public rootpath: string = ""; 16 | private storagePath: string = ""; 17 | public readonly resultsStoragePath: string = ""; 18 | private diagCollection: vscode.DiagnosticCollection; 19 | 20 | constructor(context: vscode.ExtensionContext, diagCollection: vscode.DiagnosticCollection) { 21 | if (context.storageUri) { 22 | this.storagePath = context.storageUri.fsPath; 23 | console.log(`storage path is ${this.storagePath}`); 24 | if (!fs.existsSync(this.storagePath)) { 25 | fs.mkdirSync(context.storageUri.fsPath); 26 | } 27 | this.resultsStoragePath = path.join(context.storageUri.fsPath, '/.tfsec/'); 28 | if (!fs.existsSync(this.resultsStoragePath)) { 29 | fs.mkdirSync(this.resultsStoragePath); 30 | } 31 | } 32 | 33 | this.diagCollection = diagCollection; 34 | } 35 | 36 | refresh(): void { 37 | this.taintResults = true; 38 | this.loadResultData(); 39 | } 40 | 41 | // when there is a tfsec output file, load the results 42 | async loadResultData() { 43 | var _self = this; 44 | _self.resultData = []; 45 | if (this.resultsStoragePath !== "" && vscode.workspace && vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]) { 46 | this.rootpath = vscode.workspace.workspaceFolders[0].uri.fsPath; 47 | var files = fs.readdirSync(this.resultsStoragePath).filter(fn => fn.endsWith('_results.json') || fn.endsWith('_results.json.json')); 48 | Promise.resolve(files.forEach(file => { 49 | const resultFile = path.join(this.resultsStoragePath, file); 50 | if (fs.existsSync(resultFile)) { 51 | let content = fs.readFileSync(resultFile, 'utf8'); 52 | 53 | let diagnostics = new Map(); 54 | 55 | try { 56 | const data = JSON.parse(content); 57 | if (data === null || data.results === null) { 58 | return; 59 | } 60 | let results = data.results.sort(sortResults); 61 | for (let i = 0; i < results.length; i++) { 62 | const element = results[i]; 63 | let result = new CheckResult(element); 64 | _self.resultData.push(result); 65 | 66 | if (diagnostics.get(result.filename) === undefined) { 67 | diagnostics.set(result.filename, []); 68 | } 69 | diagnostics.get(result.filename)?.push(this.processProblem(result)); 70 | } 71 | } 72 | catch { 73 | console.debug(`Error loading results file ${file}`); 74 | } 75 | 76 | for (let [key, value] of diagnostics) { 77 | this.diagCollection.set(vscode.Uri.file(key), value) 78 | } 79 | } 80 | })).then(() => { 81 | _self.taintResults = !_self.taintResults; 82 | _self._onDidChangeTreeData.fire(); 83 | }); 84 | } else { 85 | vscode.window.showInformationMessage("No workspace detected to load tfsec results from"); 86 | } 87 | this.taintResults = false; 88 | } 89 | 90 | getTreeItem(element: TfsecTreeItem): vscode.TreeItem { 91 | return element; 92 | } 93 | 94 | getChildren(element?: TfsecTreeItem): Thenable { 95 | // if this is refresh then get the top level codes 96 | let items: TfsecTreeItem[] = []; 97 | if (!element) { 98 | items = this.getCurrentTfsecSeverities(); 99 | } else if (element.treeItemType === TfsecTreeItemType.issueSeverity) { 100 | items = this.getCurrentTfsecIssues(element.severity); 101 | } else { 102 | items = this.getIssuesLocationsByCode(element.code); 103 | } 104 | return Promise.resolve(items); 105 | } 106 | 107 | private getCurrentTfsecSeverities(): TfsecTreeItem[] { 108 | var results: TfsecTreeItem[] = []; 109 | var resolvedSeverities: string[] = []; 110 | 111 | for (let index = 0; index < this.resultData.length; index++) { 112 | const result = this.resultData[index]; 113 | if (result === undefined) { 114 | continue; 115 | } 116 | 117 | if (resolvedSeverities.includes(result.severity)) { 118 | continue; 119 | } 120 | resolvedSeverities.push(result.severity); 121 | results.push(new TfsecTreeItem(result.severity, new CheckSeverity(result), vscode.TreeItemCollapsibleState.Collapsed)); 122 | } 123 | return results.sort(sortBySeverity); 124 | } 125 | 126 | 127 | private getCurrentTfsecIssues(severity: string): TfsecTreeItem[] { 128 | var results: TfsecTreeItem[] = []; 129 | var resolvedCodes: string[] = []; 130 | 131 | 132 | for (let index = 0; index < this.resultData.length; index++) { 133 | const result = this.resultData[index]; 134 | 135 | if (result === undefined) { 136 | continue; 137 | } 138 | if (resolvedCodes.includes(result.code) || result.severity !== severity) { 139 | continue; 140 | } 141 | resolvedCodes.push(result.code); 142 | results.push(new TfsecTreeItem(result.code, result, vscode.TreeItemCollapsibleState.Collapsed)); 143 | } 144 | return results.sort(sortByCode); 145 | } 146 | 147 | getIssuesLocationsByCode(code: string): TfsecTreeItem[] { 148 | var results: TfsecTreeItem[] = []; 149 | 150 | const filtered = this.resultData.filter(c => c.code === code); 151 | for (let index = 0; index < filtered.length; index++) { 152 | const result = filtered[index]; 153 | 154 | if (result === undefined) { 155 | continue; 156 | } 157 | if (result.code !== code) { 158 | continue; 159 | } 160 | const filename = path.relative(this.rootpath, result.filename); 161 | const cmd = this.createFileOpenCommand(result); 162 | var item = new TfsecTreeItem(`${filename}:${result.startLine}`, result, vscode.TreeItemCollapsibleState.None, cmd); 163 | results.push(item); 164 | } 165 | return uniqueLocations(results); 166 | } 167 | 168 | 169 | private createFileOpenCommand(result: CheckResult) { 170 | const issueRange = new vscode.Range(new vscode.Position(result.startLine - 1, 0), new vscode.Position(result.endLine, 0)); 171 | 172 | const pathToOpen = path.join(result.filename); 173 | 174 | return { 175 | command: "vscode.open", 176 | title: "", 177 | arguments: [ 178 | vscode.Uri.file(pathToOpen), 179 | { 180 | selection: issueRange, 181 | } 182 | ] 183 | }; 184 | } 185 | 186 | private processProblem(check: CheckResult): vscode.Diagnostic { 187 | let severity = check.severity === "Critical" || check.severity === "High" || check.severity === "Medium" ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Warning 188 | return new vscode.Diagnostic(new vscode.Range(new vscode.Position(check.startLine -1, 0), new vscode.Position(check.endLine -1, 0)), check.summary, severity); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/explorer/tfsec_treeitem.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | import { CheckResult, CheckSeverity } from './check_result'; 4 | 5 | export class TfsecTreeItem extends vscode.TreeItem { 6 | 7 | treeItemType: TfsecTreeItemType; 8 | code: string; 9 | provider: string; 10 | startLineNumber: number; 11 | endLineNumber: number; 12 | filename: string; 13 | severity: string; 14 | contextValue = ''; 15 | 16 | constructor( 17 | public readonly title: string, 18 | public readonly check: CheckResult | CheckSeverity, 19 | public collapsibleState: vscode.TreeItemCollapsibleState, 20 | public command?: vscode.Command, 21 | ) { 22 | super(title, collapsibleState); 23 | this.severity = check.severity; 24 | this.code = ""; 25 | this.provider = ""; 26 | this.startLineNumber = 0; 27 | this.endLineNumber = 0; 28 | this.filename = ""; 29 | 30 | if (check instanceof CheckResult) { 31 | this.code = check.code; 32 | this.provider = check.provider; 33 | if (collapsibleState === vscode.TreeItemCollapsibleState.None) { 34 | this.treeItemType = TfsecTreeItemType.issueLocation; 35 | this.contextValue = "TFSEC_FILE_LOCATION"; 36 | this.startLineNumber = check.startLine; 37 | this.endLineNumber = check.endLine; 38 | this.filename = check.filename; 39 | this.iconPath = vscode.ThemeIcon.File; 40 | this.resourceUri = vscode.Uri.parse(check.filename); 41 | } else { 42 | this.treeItemType = TfsecTreeItemType.issueCode; 43 | this.contextValue = "TFSEC_CODE"; 44 | this.tooltip = `${check.codeDescription}`; 45 | this.iconPath = { 46 | light: path.join(__filename, '..', '..', 'resources', 'light', 'tfsec.svg'), 47 | dark: path.join(__filename, '..', '..', 'resources', 'dark', 'tfsec.svg') 48 | }; 49 | } 50 | } else { 51 | this.treeItemType = TfsecTreeItemType.issueSeverity; 52 | this.contextValue = "TFSEC_SEVERITY"; 53 | this.iconPath = { 54 | light: path.join(__filename, '..', '..', 'resources', this.severityIcon(this.severity)), 55 | dark: path.join(__filename, '..', '..', 'resources', this.severityIcon(this.severity)) 56 | }; 57 | } 58 | } 59 | 60 | severityIcon = (severity: string): string => { 61 | switch (severity) { 62 | case "Critical": 63 | return 'critical.svg'; 64 | case "High": 65 | return 'high.svg'; 66 | case "Medium": 67 | return 'medium.svg'; 68 | case "Low": 69 | return 'low.svg'; 70 | } 71 | return 'unknown.svg'; 72 | }; 73 | } 74 | 75 | export enum TfsecTreeItemType { 76 | issueCode = 0, 77 | issueLocation = 1, 78 | issueSeverity = 2, 79 | } -------------------------------------------------------------------------------- /src/explorer/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | import { TfsecTreeItem } from './tfsec_treeitem'; 3 | 4 | 5 | function getSeverityPosition(severity: string): number { 6 | switch (severity) { 7 | case 'Critical': 8 | return 0; 9 | case 'High': 10 | return 1; 11 | case 'Medium': 12 | return 2; 13 | case 'Low': 14 | return 3; 15 | default: 16 | return -1; 17 | } 18 | } 19 | 20 | const sortByCode = (a: TfsecTreeItem, b: TfsecTreeItem): number => { 21 | if (a.code 22 | > b.code) { 23 | return 1; 24 | } else if (a.code < b.code) { 25 | return -1; 26 | } 27 | return 0; 28 | }; 29 | 30 | const sortResults = (a: any, b: any): number => { 31 | if (a.filename > b.filename) { 32 | return 1; 33 | } else if (a.filename < b.filename) { 34 | return -1; 35 | } 36 | return 0; 37 | }; 38 | 39 | const sortBySeverity = (a: TfsecTreeItem, b: TfsecTreeItem): number => { 40 | if (getSeverityPosition(a.severity) > getSeverityPosition(b.severity)) { 41 | return 1; 42 | } else if (getSeverityPosition(a.severity) < getSeverityPosition(b.severity)) { 43 | return -1; 44 | } 45 | 46 | return 0; 47 | }; 48 | 49 | const sortByFilename = (a: TfsecTreeItem, b: TfsecTreeItem): number => { 50 | if (a.filename > b.filename) { 51 | return 1; 52 | } else if (a.filename < b.filename) { 53 | return -1; 54 | } 55 | return 0; 56 | }; 57 | 58 | const sortByLineNumber = (a: TfsecTreeItem, b: TfsecTreeItem): number => { 59 | if (a.startLineNumber > b.startLineNumber) { 60 | return 1; 61 | } else if (a.startLineNumber < b.startLineNumber) { 62 | return -1; 63 | } 64 | return 0; 65 | }; 66 | 67 | const uniqueLocations = (input: TfsecTreeItem[]): TfsecTreeItem[] => { 68 | 69 | if (input.length === 0) { 70 | return input; 71 | } 72 | input.sort(sortByLineNumber); 73 | let output: TfsecTreeItem[] = []; 74 | let last = input[0]; 75 | if (last === undefined) { 76 | return []; 77 | } 78 | output.push(last); 79 | 80 | for (let index = 1; index < input.length; index++) { 81 | const element = input[index]; 82 | if (element === undefined) { 83 | continue; 84 | } 85 | if (last?.code !== element?.code || last?.filename !== element?.filename || last?.startLineNumber !== element?.startLineNumber) { 86 | output.push(element); 87 | last = element; 88 | } 89 | } 90 | 91 | return output.sort(sortByFilename); 92 | }; 93 | 94 | 95 | 96 | const capitalize = (s: string) => (s && s[0] && s[0].toUpperCase() + s.slice(1).toLowerCase()) || ''; 97 | 98 | export { sortByCode, sortBySeverity, sortResults, uniqueLocations, capitalize }; -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { ignoreAllInstances, ignoreInstance, ingorePath, triggerDecoration } from './ignore'; 3 | import { TfsecIssueProvider } from './explorer/issues_treeview'; 4 | import { TfsecTreeItem } from './explorer/tfsec_treeitem'; 5 | import { TfsecHelpProvider } from './explorer/check_helpview'; 6 | 7 | import { extname } from 'path'; 8 | import { TfsecWrapper } from './tfsec_wrapper'; 9 | 10 | 11 | // this method is called when vs code is activated 12 | export function activate(context: vscode.ExtensionContext) { 13 | let activeEditor = vscode.window.activeTextEditor; 14 | var outputChannel = vscode.window.createOutputChannel("tfsec"); 15 | let diagCollection = vscode.languages.createDiagnosticCollection(); 16 | 17 | const helpProvider = new TfsecHelpProvider(); 18 | const issueProvider = new TfsecIssueProvider(context, diagCollection); 19 | const tfsecWrapper = new TfsecWrapper(outputChannel, issueProvider.resultsStoragePath); 20 | 21 | // creating the issue tree explicitly to allow access to events 22 | let issueTree = vscode.window.createTreeView("tfsec.issueview", { 23 | treeDataProvider: issueProvider, 24 | }); 25 | 26 | issueTree.onDidChangeSelection(function (event) { 27 | const treeItem = event.selection[0]; 28 | if (treeItem) { 29 | helpProvider.update(treeItem); 30 | } 31 | }); 32 | 33 | context.subscriptions.push(vscode.window.registerWebviewViewProvider("tfsec.helpview", helpProvider)); 34 | context.subscriptions.push(vscode.commands.registerCommand('tfsec.refresh', () => issueProvider.refresh())); 35 | context.subscriptions.push(vscode.commands.registerCommand('tfsec.version', () => tfsecWrapper.showCurrentTfsecVersion())); 36 | context.subscriptions.push(vscode.commands.registerCommand('tfsec.ignore', (element: TfsecTreeItem) => ignoreInstance(element, outputChannel))); 37 | context.subscriptions.push(vscode.commands.registerCommand('tfsec.ignoreAll', (element: TfsecTreeItem) => ignoreAllInstances(element, issueProvider, outputChannel))); 38 | context.subscriptions.push(vscode.commands.registerCommand('tfsec.ignoreSeverity', (element: TfsecTreeItem) => ignoreAllInstances(element, issueProvider, outputChannel))); 39 | context.subscriptions.push(vscode.commands.registerCommand("tfsec.run", () => tfsecWrapper.run())); 40 | context.subscriptions.push(vscode.commands.registerCommand("tfsec.updatebinary", () => tfsecWrapper.updateBinary())); 41 | context.subscriptions.push(vscode.commands.registerCommand('tfsec.ignorePath', (element: any) => ingorePath(element))); 42 | 43 | context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => { 44 | // only act if this is a terraform file 45 | if (editor && extname(editor.document.fileName) !== '.tf') { 46 | return; 47 | } 48 | activeEditor = editor; 49 | triggerDecoration(); 50 | }, null, context.subscriptions)); 51 | 52 | context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(event => { 53 | // only act if this is a terraform file 54 | if (extname(event.document.fileName) !== '.tf') { 55 | return; 56 | } 57 | if (activeEditor && event.document === activeEditor.document) { 58 | triggerDecoration(); 59 | } 60 | }, null, context.subscriptions)); 61 | 62 | if (activeEditor && extname(activeEditor.document.fileName) !== '.tf') { 63 | triggerDecoration(); 64 | }; 65 | }; 66 | -------------------------------------------------------------------------------- /src/ignore.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { CheckManager } from './check_manager'; 3 | import { TfsecIssueProvider } from './explorer/issues_treeview'; 4 | import { TfsecTreeItem, TfsecTreeItemType } from './explorer/tfsec_treeitem'; 5 | 6 | let timeout: NodeJS.Timer | undefined = undefined; 7 | let activeEditor = vscode.window.activeTextEditor; 8 | import * as path from 'path'; 9 | 10 | class IgnoreDetails { 11 | public readonly code: string; 12 | public readonly startLine: number; 13 | public readonly endLine: number; 14 | public constructor(code: string, startLine: number, endLine: number) { 15 | this.code = code; 16 | this.startLine = startLine; 17 | this.endLine = endLine; 18 | } 19 | } 20 | 21 | class FileIgnores { 22 | public constructor(public readonly filename: string, public readonly ignores: IgnoreDetails[]) { } 23 | } 24 | 25 | const tfsecIgnoreDecoration = vscode.window.createTextEditorDecorationType({ 26 | fontStyle: 'italic', 27 | color: new vscode.ThemeColor("editorGutter.commentRangeForeground"), 28 | after: { 29 | margin: '0 0 0 1em', 30 | textDecoration: 'none', 31 | }, 32 | rangeBehavior: vscode.DecorationRangeBehavior.ClosedOpen, 33 | }); 34 | 35 | function triggerDecoration() { 36 | const config = vscode.workspace.getConfiguration('tfsec'); 37 | if (!config.get('resolveIgnoreCodes', true)) { 38 | return; 39 | } 40 | if (timeout) { 41 | clearTimeout(timeout); 42 | timeout = undefined; 43 | } 44 | timeout = setTimeout(updateTfsecIgnoreDecorators, 500); 45 | } 46 | 47 | function updateTfsecIgnoreDecorators() { 48 | if (!activeEditor) { 49 | return; 50 | } 51 | const regEx = /tfsec:ignore:([A-Z]+?\d{3})/g; 52 | const text = activeEditor.document.getText(); 53 | const tfsecIgnores: vscode.DecorationOptions[] = []; 54 | 55 | let match; 56 | while ((match = regEx.exec(text))) { 57 | if (match[1] === undefined || match[0] === undefined) { break; } 58 | const startPos = activeEditor.document.positionAt(match.index); 59 | const endPos = activeEditor.document.positionAt(match.index + match[0].length); 60 | const message = getTfsecDescription(match[1]); 61 | const decoration = { range: new vscode.Range(startPos, endPos), renderOptions: { after: { fontStyle: 'italic', contentText: message, color: new vscode.ThemeColor("editorGutter.commentRangeForeground") } } }; 62 | tfsecIgnores.push(decoration); 63 | } 64 | activeEditor.setDecorations(tfsecIgnoreDecoration, tfsecIgnores); 65 | } 66 | 67 | function getTfsecDescription(tfsecCode: string) { 68 | var check = CheckManager.getInstance().get(tfsecCode); 69 | if (check === undefined) { 70 | return "[Uknown tfsec code]"; 71 | } 72 | return check; 73 | } 74 | 75 | function rerunIfRequired() { 76 | const config = vscode.workspace.getConfiguration('tfsec'); 77 | var reRunOnIgnore = config.get('runOnIgnore', true); 78 | if (reRunOnIgnore) { 79 | setTimeout(() => { vscode.commands.executeCommand("tfsec.run"); }, 1000); 80 | } else { 81 | vscode.window.showInformationMessage("You should refresh the treeview after ignoring"); 82 | }; 83 | } 84 | 85 | async function addIgnore(filename: string, ignores: IgnoreDetails[], outputChannel: vscode.OutputChannel): Promise { 86 | if (!filename.endsWith(".tf")) { 87 | outputChannel.appendLine(`${filename} is not a tf file`); 88 | return Promise.resolve(); 89 | } 90 | 91 | ignores = ignores.sort(function (a: IgnoreDetails, b: IgnoreDetails): number { 92 | if (a.startLine > b.startLine) { 93 | return 1; 94 | } else if (a.startLine < b.startLine) { 95 | return -1; 96 | } 97 | return 0; 98 | }); 99 | 100 | 101 | await vscode.window.showTextDocument(vscode.Uri.file(filename)).then(e => { 102 | e.edit(edit => { 103 | for (let index = 0; index < ignores.length; index++) { 104 | let element = ignores[index]; 105 | 106 | if (element === undefined) { continue; } 107 | const ignoreCode = `#tfsec:ignore:${element.code}`; 108 | outputChannel.appendLine(`Adding ignore for ${ignoreCode}`); 109 | var ignoreLine: vscode.TextLine | undefined; 110 | var startPos: number | undefined; 111 | if (element.startLine === element.endLine) { 112 | let errorLine = vscode.window.activeTextEditor?.document.lineAt(element.startLine); 113 | if (errorLine !== null && errorLine !== undefined) { 114 | let ignoreLinePos = element.startLine; 115 | ignoreLine = vscode.window.activeTextEditor?.document.lineAt(ignoreLinePos); 116 | startPos = ignoreLine?.text.length; 117 | } 118 | } else { 119 | let ignoreLinePos = element.startLine; 120 | ignoreLine = vscode.window.activeTextEditor?.document.lineAt(ignoreLinePos); 121 | } 122 | if (ignoreLine === undefined || ignoreLine.text.includes(ignoreCode)) { 123 | continue; 124 | } 125 | if (ignoreLine !== undefined && ignoreLine.text !== undefined && ignoreLine.text.includes('tfsec:')) { 126 | edit.insert(new vscode.Position(ignoreLine.lineNumber - 1, 0), `${ignoreCode}\n`); 127 | } else { 128 | if (startPos === undefined) { 129 | startPos = 0; 130 | } 131 | edit.insert(new vscode.Position(ignoreLine.lineNumber - 1, 0), `${ignoreCode}\n`); 132 | } 133 | } 134 | }); 135 | e.document.save(); 136 | }); 137 | }; 138 | 139 | const ignoreInstance = (element: TfsecTreeItem, outputChannel: vscode.OutputChannel) => { 140 | const details = [new IgnoreDetails(element.code, element.startLineNumber, element.endLineNumber)]; 141 | addIgnore(element.filename, details, outputChannel); 142 | 143 | rerunIfRequired(); 144 | }; 145 | 146 | 147 | const ingorePath = (element: any) => { 148 | 149 | if (vscode.workspace && vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]) { 150 | const rootpath = vscode.workspace.workspaceFolders[0].uri.fsPath; 151 | const config = vscode.workspace.getConfiguration("tfsec"); 152 | let excludedPaths = config.get("excludedPaths"); 153 | 154 | var filepath = element.fsPath; 155 | filepath = path.relative(rootpath, filepath); 156 | 157 | excludedPaths?.push(filepath); 158 | excludedPaths = [...new Set(excludedPaths?.map(obj => obj))]; 159 | 160 | config.update("excludedPaths", excludedPaths, false); 161 | } 162 | }; 163 | 164 | const ignoreAllInstances = async (element: TfsecTreeItem, issueProvider: TfsecIssueProvider, outputChannel: vscode.OutputChannel) => { 165 | outputChannel.show(); 166 | outputChannel.appendLine("\nSetting ignores - "); 167 | 168 | var seenIgnores: string[] = []; 169 | var ignoreMap = new Map(); 170 | 171 | let severityIgnore = element.treeItemType === TfsecTreeItemType.issueSeverity; 172 | for (let index = 0; index < issueProvider.resultData.length; index++) { 173 | var r = issueProvider.resultData[index]; 174 | if (r === undefined) { 175 | continue; 176 | } 177 | let ignores = ignoreMap.get(r.filename); 178 | if (!ignores) { 179 | ignores = []; 180 | } 181 | 182 | if (severityIgnore && r.severity !== element.severity) { continue; } 183 | if (!severityIgnore && r.code !== element.code) { continue; } 184 | 185 | let ingoreKey = `${r.filename}:${r.code}:${r.startLine}:${r.endLine}`; 186 | if (seenIgnores.includes(ingoreKey)) { 187 | continue; 188 | } 189 | seenIgnores.push(ingoreKey); 190 | ignores.push(new IgnoreDetails(r.code, r.startLine, r.endLine)); 191 | ignoreMap.set(r.filename, ignores); 192 | } 193 | 194 | var edits: FileIgnores[] = []; 195 | ignoreMap.forEach((ignores: IgnoreDetails[], filename: string) => { 196 | edits.push(new FileIgnores(filename, ignores)); 197 | }); 198 | 199 | await edits.reduce( 200 | (p, x) => 201 | p.then(_ => addIgnore(x.filename, x.ignores, outputChannel)), 202 | Promise.resolve() 203 | ).then(() => { 204 | outputChannel.appendLine("Checking if re-run is enabled...."); 205 | rerunIfRequired(); 206 | }); 207 | }; 208 | 209 | 210 | export { ignoreAllInstances, ignoreInstance, ingorePath, triggerDecoration, IgnoreDetails, FileIgnores }; -------------------------------------------------------------------------------- /src/snippets/custom_checks.json: -------------------------------------------------------------------------------- 1 | { 2 | "custom_checks_yaml": { 3 | "prefix": [ 4 | "tfsec-check-file" 5 | ], 6 | "body": [ 7 | "---", 8 | "checks:", 9 | " - code: ${1}", 10 | " description: ${2}", 11 | " requiredTypes:", 12 | " - ${3:resource}", 13 | " requiredLabels:", 14 | " - ${4}", 15 | " severity: ${5:MEDIUM}", 16 | " matchSpec:", 17 | " name: ${6}", 18 | " action: ${7}", 19 | " value: ${8}", 20 | " errorMessage: ${9}", 21 | " relatedLinks:", 22 | " - ${10}" 23 | ], 24 | "description": "Add the body for custom checks file." 25 | }, 26 | "custom_check": { 27 | "prefix": [ 28 | "tfsec-custom-check" 29 | ], 30 | "body": [ 31 | " - code: ${1}", 32 | " description: ${2}", 33 | " requiredTypes:", 34 | " - ${3:resource}", 35 | " requiredLabels:", 36 | " - ${4}", 37 | " severity: ${5:MEDIUM}", 38 | " matchSpec:", 39 | " name: ${6}", 40 | " action: ${7}", 41 | " value: ${8}", 42 | " errorMessage: ${9}", 43 | " relatedLinks:", 44 | " - ${10}" 45 | ], 46 | "description": "Add the body for an individual custom check." 47 | } 48 | } -------------------------------------------------------------------------------- /src/tfsec_wrapper.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as child from 'child_process'; 3 | import * as semver from 'semver'; 4 | import { v4 as uuid } from 'uuid'; 5 | import * as path from 'path'; 6 | import { unlinkSync, readdirSync } from 'fs'; 7 | 8 | export class TfsecWrapper { 9 | private workingPath: string[] = []; 10 | constructor( 11 | private outputChannel: vscode.OutputChannel, 12 | private readonly resultsStoragePath: string) { 13 | if (!vscode.workspace || !vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length <= 0) { 14 | return; 15 | } 16 | const folders = vscode.workspace.workspaceFolders; 17 | for (let i = 0; i < folders.length; i++) { 18 | if (folders[i]) { 19 | const workspaceFolder = folders[i]; 20 | if (!workspaceFolder) { 21 | continue; 22 | } 23 | this.workingPath.push(workspaceFolder.uri.fsPath); 24 | } 25 | } 26 | } 27 | 28 | run() { 29 | let outputChannel = this.outputChannel; 30 | this.outputChannel.appendLine(""); 31 | this.outputChannel.appendLine("Running tfsec to update results"); 32 | 33 | if (!this.checkTfsecInstalled()) { 34 | return; 35 | } 36 | 37 | var files = readdirSync(this.resultsStoragePath).filter(fn => fn.endsWith('_results.json') || fn.endsWith('_results.json.json')); 38 | files.forEach(file => { 39 | let deletePath = path.join(this.resultsStoragePath, file); 40 | unlinkSync(deletePath); 41 | }); 42 | 43 | const binary = this.getBinaryPath(); 44 | 45 | this.workingPath.forEach(workingPath => { 46 | let command = this.buildCommand(workingPath); 47 | this.outputChannel.appendLine(`command: ${command}`); 48 | 49 | var execution = child.spawn(binary, command); 50 | 51 | execution.stdout.on('data', function (data) { 52 | outputChannel.appendLine(data.toString()); 53 | }); 54 | 55 | execution.stderr.on('data', function (data) { 56 | outputChannel.appendLine(data.toString()); 57 | }); 58 | 59 | execution.on('exit', function (code) { 60 | if (code !== 0) { 61 | vscode.window.showErrorMessage("tfsec failed to run"); 62 | outputChannel.show(); 63 | return; 64 | }; 65 | vscode.window.showInformationMessage('tfsec ran successfully, updating results'); 66 | outputChannel.appendLine('Reloading the Findings Explorer content'); 67 | setTimeout(() => { vscode.commands.executeCommand("tfsec.refresh"); }, 250); 68 | }); 69 | }); 70 | 71 | } 72 | 73 | 74 | updateBinary() { 75 | this.outputChannel.show(); 76 | this.outputChannel.appendLine(""); 77 | this.outputChannel.appendLine("Checking the current version"); 78 | 79 | if (!this.checkTfsecInstalled()) { 80 | return; 81 | } 82 | 83 | const currentVersion = this.getInstalledTfsecVersion(); 84 | if (currentVersion.includes("running a locally built version")) { 85 | this.outputChannel.appendLine("You are using a locally built version which cannot be updated"); 86 | } 87 | 88 | if (semver.lt(currentVersion, "0.39.39")) { 89 | vscode.window.showInformationMessage(`Self updating was not introduced till v0.39.39 and you are running ${currentVersion}. Pleae update manually to at least v0.39.39`); 90 | } 91 | this.outputChannel.appendLine("Attempting to download the latest version"); 92 | var binary = this.getBinaryPath(); 93 | try { 94 | let result: Buffer = child.execSync(binary + " --update --verbose"); 95 | this.outputChannel.appendLine(result.toLocaleString()); 96 | } catch (err) { 97 | vscode.window.showErrorMessage("There was a problem with the update, check the output window"); 98 | let errMsg = err as Error; 99 | this.outputChannel.appendLine(errMsg.message); 100 | } 101 | } 102 | 103 | showCurrentTfsecVersion() { 104 | const currentVersion = this.getInstalledTfsecVersion(); 105 | if (currentVersion) { 106 | vscode.window.showInformationMessage(`Current tfsec version is ${currentVersion}`); 107 | } 108 | } 109 | 110 | private getBinaryPath() { 111 | const config = vscode.workspace.getConfiguration('tfsec'); 112 | var binary = config.get('binaryPath', 'tfsec'); 113 | if (binary === "") { 114 | binary = "tfsec"; 115 | } 116 | 117 | return binary; 118 | }; 119 | 120 | private checkTfsecInstalled(): boolean { 121 | const binaryPath = this.getBinaryPath(); 122 | 123 | var command = []; 124 | command.push(binaryPath); 125 | command.push('--help'); 126 | try { 127 | child.execSync(command.join(' ')); 128 | } 129 | catch (err) { 130 | this.outputChannel.show(); 131 | this.outputChannel.appendLine(`tfsec not found. Check the tfsec extension settings to ensure the path is correct. [${binaryPath}]`); 132 | return false; 133 | } 134 | return true; 135 | }; 136 | 137 | private getInstalledTfsecVersion(): string { 138 | 139 | if (!this.checkTfsecInstalled) { 140 | vscode.window.showErrorMessage("tfsec could not be found, check Output window"); 141 | return ""; 142 | } 143 | 144 | let binary = this.getBinaryPath(); 145 | 146 | var command = []; 147 | command.push(binary); 148 | command.push('--version'); 149 | const getVersion = child.execSync(command.join(' ')); 150 | return getVersion.toString(); 151 | }; 152 | 153 | 154 | private buildCommand(workingPath: string): string[] { 155 | const config = vscode.workspace.getConfiguration('tfsec'); 156 | var command = []; 157 | 158 | if (config.get('fullDepthSearch')) { 159 | command.push('--force-all-dirs'); 160 | } 161 | if (config.get('ignoreDownloadedModules')) { 162 | command.push('--exclude-downloaded-modules'); 163 | } 164 | 165 | if (config.get('debug')) { 166 | command.push('--verbose'); 167 | } 168 | 169 | const excludes = config.get("excludedPaths"); 170 | if (excludes && excludes.length > 0) { 171 | excludes.forEach((element: string) => { 172 | command.push(`--exclude-path=${element}`); 173 | }); 174 | } 175 | 176 | 177 | // add soft fail for exit code 178 | command.push('--soft-fail'); 179 | command.push('--format=json'); 180 | const resultsPath = path.join(this.resultsStoragePath, `${uuid()}_results.json`); 181 | command.push(`--out=${resultsPath}`); 182 | command.push(workingPath); 183 | 184 | return command; 185 | } 186 | 187 | } 188 | 189 | 190 | -------------------------------------------------------------------------------- /tfsec-explorer-usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquasecurity/vscode-tfsec/55b8d7ae7be501d6c23a77d0519ab7a001543a63/tfsec-explorer-usage.gif -------------------------------------------------------------------------------- /tfsec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aquasecurity/vscode-tfsec/55b8d7ae7be501d6c23a77d0519ab7a001543a63/tfsec.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es6", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "outDir": "./out", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | // "noUncheckedIndexedAccess": true, 14 | // "noPropertyAccessFromIndexSignature": true, 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true 18 | } 19 | } --------------------------------------------------------------------------------