├── .github └── workflows │ ├── analyze.yml │ ├── compile.yml │ └── lint.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md └── src └── python └── detectors ├── api_logging_disabled_cdk ├── api_logging_disabled_cdk_compliant.py └── api_logging_disabled_cdk_noncompliant.py ├── aws_insecure_transmission_cdk ├── aws_insecure_transmission_cdk_compliant.py └── aws_insecure_transmission_cdk_noncompliant.py ├── aws_kms_reencryption └── aws_kms_reencryption.py ├── aws_logged_credentials └── aws_logged_credentials.py ├── aws_missing_encryption_cdk ├── aws_missing_encryption_cdk_compliant.py └── aws_missing_encryption_cdk_noncompliant.py ├── aws_polling_instead_of_waiter └── aws_polling_instead_of_waiter.py ├── aws_unchecked_batch_failures └── aws_unchecked_batch_failures.py ├── bad_exception_handling_practices └── bad_exception_handling_practices.py ├── catch_and_rethrow_exception └── catch_and_rethrow_exception.py ├── clear_text_credentials ├── clear_text_credentials_compliant.py └── clear_text_credentials_non_compliant.py ├── code_injection └── code_injection.py ├── code_readability └── code_readability.py ├── cross_site_request_forgery └── cross_site_request_forgery.py ├── cross_site_scripting └── cross_site_scripting.py ├── dangerous_global_variables └── dangerous_global_variables.py ├── default_argument_mutable_objects └── default_argument_mutable_objects.py ├── deprecated_method └── deprecated_method.py ├── detect_activated_debug_feature └── detect_activated_debug_feature.py ├── dict_get_method └── dict_get_method.py ├── do_not_auto_add_or_warning_missing_hostkey_policy └── do_not_auto_add_or_warning_missing_hostkey_policy.py ├── docker_arbitrary_container_run └── docker_arbitrary_container_run.py ├── equality_vs_identity └── equality_vs_identity.py ├── exposure_of_sensitive_information_cdk ├── exposure_of_sensitive_information_cdk_compliant.py └── exposure_of_sensitive_information_cdk_noncompliant.py ├── hardcoded_credentials └── hardcoded_credentials.py ├── hardcoded_ip_address └── hardcoded_ip_address.py ├── hashlib_constructor └── hashlib_constructor.py ├── improper_authentication └── improper_authentication.py ├── improper_certificate_validation └── improper_certificate_validation.py ├── improper_error_handling └── improper_error_handling.py ├── improper_input_validation └── improper_input_validation.py ├── improper_privilege_management └── improper_privilege_management.py ├── incorrect_usage_of_process_terminate_api └── incorrect_usage_of_process_terminate_api.py ├── insecure_connection └── insecure_connection.py ├── insecure_cookie └── insecure_cookie.py ├── insecure_cors_policy ├── insecure_cors_policy_compliant.py └── insecure_cors_policy_noncompliant.py ├── insecure_cryptography └── insecure_cryptography.py ├── insecure_hashing └── insecure_hashing.py ├── insecure_socket_bind └── insecure_socket_bind.py ├── insecure_temp_file └── insecure_temp_file.py ├── integer_overflow └── integer_overflow.py ├── iterating_sequence_modification └── iterating_sequence_modification.py ├── lambda_client_reuse └── lambda_client_reuse.py ├── lambda_override_reserved └── lambda_override_reserved.py ├── ldap_authentication └── ldap_authentication.py ├── ldap_injection └── ldap_injection.py ├── leaky_subprocess_timeout └── leaky_subprocess_timeout.py ├── log_injection └── log_injection.py ├── loose_file_permissions └── loose_file_permissions.py ├── missing_authentication_for_critical_function_cdk ├── missing_authentication_for_critical_function_cdk_compliant.py └── missing_authentication_for_critical_function_cdk_noncompliant.py ├── missing_none_check └── missing_none_check.py ├── missing_pagination └── missing_pagination.py ├── module_injection └── module_injection.py ├── multidimension_list_using_replication └── multidimension_list_using_replication.py ├── multiple_values_in_return └── multiple_values_in_return.py ├── multiprocessing_deadlock_prevention └── multiprocessing_deadlock_prevention.py ├── multiprocessing_garbage_collection_prevention └── multiprocessing_garbage_collection_prevention.py ├── mutually_exclusive_calls_found └── mutually_exclusive_calls_found.py ├── naive_datatime_time_zone_issues └── naive_datatime_time_zone_issues.py ├── not_recommended_apis ├── not_recommended_apis_compliant.py └── not_recommended_apis_noncompliant.py ├── object_dict_modification └── object_dict_modification.py ├── os_command_injection └── os_command_injection.py ├── path_traversal └── path_traversal.py ├── pep8_recommendations └── pep8_recommendations.py ├── pytorch-disable-gradient-calculation └── pytorch-disable-gradient-calculation.py ├── pytorch_assign_in_place_mod └── pytorch_assign_in_place_mod.py ├── pytorch_avoid_softmax_with_nllloss_rule └── pytorch_avoid_softmax_with_nllloss_rule.py ├── pytorch_create_tensors_directly_on_device └── pytorch_create_tensors_directly_on_device.py ├── pytorch_data_loader_with_multiple_workers └── pytorch_data_loader_with_multiple_workers.py ├── pytorch_miss_call_to_eval └── pytorch_miss_call_to_eval.py ├── pytorch_miss_call_to_zero_grad └── pytorch_miss_call_to_zero_grad.py ├── pytorch_redundant_softmax └── pytorch_redundant_softmax.py ├── pytorch_sigmoid_before_bceloss └── pytorch_sigmoid_before_bceloss.py ├── pytorch_use_nondeterministic_algorithm └── pytorch_use_nondeterministic_algorithm.py ├── resource_leak └── resource_leak.py ├── s3_partial_encrypt_cdk ├── s3_partial_encrypt_cdk_compliant.py └── s3_partial_encrypt_cdk_noncompliant.py ├── s3_verify_bucket_owner └── s3_verify_bucket_owner.py ├── semaphore_overflow_prevention └── semaphore_overflow_prevention.py ├── sns_no_bind_subscribe_publish_rule └── sns_no_bind_subscribe_publish_rule.py ├── sns_set_return_subscription_arn └── sns_set_return_subscription_arn.py ├── sns_unauthenticated_unsubscribe └── sns_unauthenticated_unsubscribe.py ├── socket_close_platform_compatibility └── socket_close_platform_compatibility.py ├── socket_connection_timeout └── socket_connection_timeout.py ├── sql_injection └── sql_injection.py ├── stack_trace_exposure └── stack_trace_exposure.py ├── string_concatenation └── string_concatenation.py ├── subprocess_correct_api └── subprocess_correct_api.py ├── swallow_exceptions └── swallow_exceptions.py ├── tensorflow_avoid_using_nondeterministic_api └── tensorflow_avoid_using_nondeterministic_api.py ├── tensorflow_control_sources_of_randomness └── tensorflow_control_sources_of_randomness.py ├── tensorflow_enable_op_determinism └── tensorflow_enable_op_determinism.py ├── tensorflow_redundant_softmax └── tensorflow_redundant_softmax.py ├── unnecessary_iteration └── unnecessary_iteration.py ├── unrestricted_file_upload └── unrestricted_file_upload.py ├── untrusted_ami_images └── untrusted_ami_images.py ├── untrusted_deserialization └── untrusted_deserialization.py ├── use_of_default_credentials_cdk ├── use_of_default_credentials_cdk_compliant.py └── use_of_default_credentials_cdk_noncompliant.py ├── use_of_inefficient_api └── use_of_inefficient_api.py ├── weak_obfuscation_of_request └── weak_obfuscation_of_request.py ├── xml_external_entity └── xml_external_entity.py ├── xpath_injection └── xpath_injection.py └── zip_bomb_attack └── zip_bomb_attack.py /.github/workflows/analyze.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | name: CodeGuru Reviewer 7 | 8 | on: 9 | # On-demand analysis via "Run workflow" button. 10 | workflow_dispatch: 11 | inputs: 12 | logLevel: 13 | description: 'Log level' 14 | required: true 15 | default: 'debug' 16 | tags: 17 | description: 'Test scenario tags' 18 | # GitHub disables any scheduled analyses by default when you fork a repo. 19 | # Still, please remove these two lines if your fork doesn't need scheduled runs. 20 | schedule: 21 | - cron: '0 0/6 * * *' 22 | 23 | permissions: 24 | id-token: write 25 | contents: read 26 | security-events: write 27 | 28 | jobs: 29 | analyze: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Assume IAM role 33 | id: assume-iam-role 34 | continue-on-error: true 35 | uses: aws-actions/configure-aws-credentials@v1 36 | with: 37 | role-to-assume: arn:aws:iam::048169001733:role/GuruGitHubCICDRole 38 | aws-region: us-west-2 39 | 40 | - name: Check out repository 41 | uses: actions/checkout@v2 42 | if: steps.assume-iam-role.outcome == 'success' 43 | with: 44 | fetch-depth: 0 45 | 46 | - name: Create empty directory for build artifacts 47 | run: mkdir -p build/libs 48 | 49 | - name: Run CodeGuru Reviewer 50 | id: analysis 51 | uses: aws-actions/codeguru-reviewer@v1.1 52 | if: steps.assume-iam-role.outcome == 'success' 53 | continue-on-error: false 54 | with: 55 | s3_bucket: codeguru-reviewer-build-artifacts-048169001733-us-west-2 56 | build_path: ./build/libs 57 | 58 | - name: Upload SARIF file to workflow artifacts 59 | if: steps.assume-iam-role.outcome == 'success' && steps.analysis.outcome == 'success' 60 | uses: actions/upload-artifact@v2 61 | with: 62 | name: codeguru-results.sarif.json 63 | path: codeguru-results.sarif.json 64 | 65 | # 66 | # Upload the SARIF analysis results to GitHub so that they appear on the GitHub UX. 67 | # 68 | # Note: This step will fail if your GitHub repo is private (unless you buy GitHub Advanced Security). 69 | # 70 | 71 | - name: Upload SARIF file to GitHub Security Scans UX 72 | if: steps.assume-iam-role.outcome == 'success' && steps.analysis.outcome == 'success' 73 | uses: github/codeql-action/upload-sarif@v2 74 | with: 75 | sarif_file: codeguru-results.sarif.json 76 | 77 | # 78 | # The following steps are optional. 79 | # 80 | # Create a sorted summary of recommendations in CSV format (detector,filePath,lineNumber). 81 | # We do this because analyzing this repo (by design) produces lots of recommendations! 82 | # 83 | # This summary omits most of the useful information in the full results: recommendation text, 84 | # mitigations, "Learn more" links, etcetera. But its compact format (one line per finding) 85 | # makes it useful as an overview, and to compute diffs between two analysis runs. 86 | # 87 | 88 | - name: Save a summary of the results to a file on local disk 89 | if: steps.assume-iam-role.outcome == 'success' && steps.analysis.outcome == 'success' 90 | run: | 91 | echo "detector,filePath,lineNumber" > summary-of-results.csv 92 | jq -r '.runs[0].results[] | {ruleId: .ruleId, firstFile: .locations[0].physicalLocation.artifactLocation.uri, firstLine: .locations[0].physicalLocation.region.startLine} | join(",")' \ 93 | codeguru-results.sarif.json | sort >> summary-of-results.csv 94 | 95 | - name: Upload results summary to workflow artifacts 96 | if: steps.assume-iam-role.outcome == 'success' && steps.analysis.outcome == 'success' 97 | uses: actions/upload-artifact@v2 98 | with: 99 | name: summary-of-results.csv 100 | path: summary-of-results.csv 101 | 102 | - name: Print CSV summary of analysis results to the action log 103 | if: steps.assume-iam-role.outcome == 'success' && steps.analysis.outcome == 'success' 104 | run: cat summary-of-results.csv 105 | -------------------------------------------------------------------------------- /.github/workflows/compile.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | name: Compile (py_compile) 7 | 8 | on: 9 | push: 10 | branches: [ main ] 11 | pull_request: 12 | branches: [ main ] 13 | 14 | jobs: 15 | compile: 16 | 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ ubuntu-latest, macos-latest ] 21 | 22 | steps: 23 | - run: echo "The job was automatically triggered by a ${{ github.event_name }} event." 24 | - run: echo "This job is now running on a ${{ runner.os }} server hosted by GitHub!" 25 | - run: echo "The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 26 | - name: Check out repository code 27 | uses: actions/checkout@v2 28 | - name: Set up Python 29 | uses: actions/setup-python@v2 30 | with: 31 | python-version: '3.x' 32 | - name: Compile with py_compile 33 | run: | 34 | # Run "python -m py_compile" on every .py file under src 35 | find ./src -name '*.py' | xargs python -m py_compile 36 | - run: echo "This job's status is ${{ job.status }}." -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # 5 | 6 | name: Lint (flake8) 7 | 8 | on: 9 | push: 10 | branches: [ main ] 11 | pull_request: 12 | branches: [ main ] 13 | 14 | jobs: 15 | lint: 16 | 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ ubuntu-latest, windows-latest, macos-latest ] 21 | 22 | steps: 23 | - run: echo "The job was automatically triggered by a ${{ github.event_name }} event." 24 | - run: echo "This job is now running on a ${{ runner.os }} server hosted by GitHub!" 25 | - run: echo "The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 26 | - name: Check out repository code 27 | uses: actions/checkout@v2 28 | - name: Set up Python 29 | uses: actions/setup-python@v2 30 | with: 31 | python-version: '3.x' 32 | - name: Install dependencies 33 | run: | 34 | python -m pip install --upgrade pip 35 | pip install flake8 36 | pip install pep8-naming 37 | - name: Lint with flake8 38 | run: flake8 --extend-ignore=F,C,E402 --per-file-ignores='src/python/detectors/pep8_recommendations/pep8_recommendations.py:E714 src/python/detectors/equality_vs_identity/equality_vs_identity.py:E711 src/python/detectors/xpath_injection/xpath_injection.py:N817' --show-source src 39 | - run: echo "This job's status is ${{ job.status }}." -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. and its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # IntelliJ 5 | /.idea/ 6 | *.iml 7 | 8 | build/ 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | amazon-codeguru-reviewer-java-detectors 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Amazon CodeGuru Reviewer Python Detector Examples 2 | 3 | Amazon CodeGuru Reviewer is an AWS service that uses program analysis and machine learning to detect potential defects that are difficult for developers to find and offers suggestions for improvement. 4 | 5 | CodeGuru Reviewer finds defects in Java and Python code. For more information about how to set up and use CodeGuru Reviewer, see the [Amazon CodeGuru Reviewer User Guide](https://docs.aws.amazon.com/codeguru/latest/reviewer-ug/welcome.html). 6 | 7 | This repo demonstrates some of CodeGuru Reviewer's Python detectors. For more descriptions of each detector, see our [Detector Library](https://docs.aws.amazon.com/codeguru/detector-library/index.html). To see the Java code examples repo, click [here](https://github.com/aws-samples/amazon-codeguru-reviewer-java-detectors). 8 | 9 | ## Try out the CodeGuru Reviewer GitHub Action on this repo 10 | 11 | You can use this code repository to try out CodeGuru Reviewer using your AWS credentials. 12 | 13 | ### Prerequisites 14 | 15 | To use the CodeGuru Reviewer GitHub Action to scan a fork of this repo, you will first need to create a suitable Role, S3 Bucket, and Policy in your AWS account. You can do this automatically by following [these instructions](https://github.com/aws-samples/aws-codeguru-reviewer-cicd-cdk-sample#cdk-typescript-project-to-set-up-the-codeguru-reviewer-cicd-integration). 16 | 17 | ### Setup 18 | A CodeGuru Reviewer GitHub Action workflow template has already been added to this repo. To see CodeGuru Reviewer in action: 19 | 20 | 1. Fork this repo. 21 | 2. In `.github/workflows/analyze.yml`, replace the following three fields with the values obtained from the prerequisites step above: your Role ARN (`role-to-assume`), your Region (`aws-region`), and your S3 bucket name (`s3_bucket`). 22 | 3. Click on the Actions tab (next to pull requests). 23 | 4. Click on the CodeGuru Reviewer Workflow. 24 | 5. Click "Run workflow". 25 | 6. Navigate to the Security tab to see results (it should take 5-10 min). GitHub only enables the security tab for free on public repositories. 26 | 27 | ## Try out the CodeGuru Reviewer GitHub Action on your own repo 28 | 29 | You can copy the CodeGuru Reviewer GitHub Action `analyze.yml` that you made in the Setup step to your own personal repo. 30 | 31 | If you do not have GitHub Advanced Security, you will still be able to view your findings within the AWS Console. You can also use tools like `jq` within your workflow to postprocess the findings. If you print some of the findings to stdout, you will see them in your workflow's output log. 32 | 33 | ## Getting Help 34 | 35 | Use the community resources below for getting help with AWS CodeGuru Reviewer. 36 | 37 | - Use GitHub issues to report bugs and request features. 38 | - Open a support ticket with [AWS Support](https://docs.aws.amazon.com/awssupport/latest/user/getting-started.html). 39 | - For contributing guidelines, refer to [CONTRIBUTING](https://github.com/aws-samples/amazon-codeguru-reviewer-python-detectors/blob/main/CONTRIBUTING.md). 40 | 41 | ## Contributing 42 | 43 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 44 | 45 | ## License 46 | 47 | This project is licensed under the Apache-2.0 License. See the [LICENSE](LICENSE) file. 48 | -------------------------------------------------------------------------------- /src/python/detectors/api_logging_disabled_cdk/api_logging_disabled_cdk_compliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=api-logging-disabled-cdk@v1.0 defects=0} 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_apigatewayv2 7 | 8 | 9 | class APILoggingDisabled(cdk.Stack): 10 | 11 | def api_logging_disabled_compliant(self): 12 | # Compliant: logging present 13 | aws_apigatewayv2.CfnStage(self, 'rStage', 14 | access_log_settings=aws_apigatewayv2 15 | .CfnStage.access_log_settingsProperty( 16 | destination_arn='foo', 17 | format='$context.requestId'), 18 | api_id='bar', 19 | stage_name='baz') 20 | # {/fact} 21 | -------------------------------------------------------------------------------- /src/python/detectors/api_logging_disabled_cdk/api_logging_disabled_cdk_noncompliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=api-logging-disabled-cdk@v1.0 defects=1} 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_apigatewayv2 7 | 8 | 9 | class APILoggingDisabled(cdk.Stack): 10 | 11 | def api_logging_disabled_noncompliant(self): 12 | # Noncompliant: logging disabled 13 | aws_apigatewayv2.CfnStage(self, 'rHttpApiDefaultStage', 14 | api_id='foo', stage_name='$default', 15 | auto_deploy=True) 16 | # {/fact} 17 | -------------------------------------------------------------------------------- /src/python/detectors/aws_insecure_transmission_cdk/aws_insecure_transmission_cdk_compliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=aws-insecure-transmission-cdk@v1.0 defects=0} 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_s3 as s3 7 | 8 | 9 | class BucketEnforceSSL(cdk.Stack): 10 | 11 | def aws_insecure_transmission_cdk_compliant(self): 12 | # Compliant: SSL configuration present 13 | bucket = s3.Bucket(self, "s3-bucket", enforce_ssl=True) 14 | # {/fact} 15 | -------------------------------------------------------------------------------- /src/python/detectors/aws_insecure_transmission_cdk/aws_insecure_transmission_cdk_noncompliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=aws-insecure-transmission-cdk@v1.0 defects=1} 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_s3 as s3 7 | 8 | 9 | class BucketEnforceSSL(cdk.Stack): 10 | 11 | def aws_insecure_transmission_cdk_noncompliant(self): 12 | # Noncompliant: SSL configuration missing 13 | bucket = s3.Bucket(self, "s3-bucket-bad") 14 | # {/fact} 15 | -------------------------------------------------------------------------------- /src/python/detectors/aws_kms_reencryption/aws_kms_reencryption.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=aws-kms-reencryption@v1.0 defects=1} 5 | def kms_reencrypt_noncompliant(): 6 | import boto3 7 | import base64 8 | client = boto3.client('kms') 9 | plaintext = client.decrypt( 10 | CiphertextBlob=bytes(base64.b64decode("secret")) 11 | ) 12 | # Noncompliant: decrypt is immediately followed by encrypt. 13 | response = client.encrypt( 14 | KeyId='string', 15 | Plaintext=plaintext 16 | ) 17 | return response 18 | # {/fact} 19 | 20 | 21 | # {fact rule=aws-kms-reencryption@v1.0 defects=0} 22 | def kms_reencrypt_compliant(): 23 | import boto3 24 | import base64 25 | client = boto3.client('kms') 26 | # Compliant: server-side reencryption. 27 | response = client.re_encrypt( 28 | CiphertextBlob=bytes(base64.b64decode("secret")), 29 | DestinationKeyId="string", 30 | ) 31 | return response 32 | # {/fact} 33 | -------------------------------------------------------------------------------- /src/python/detectors/aws_logged_credentials/aws_logged_credentials.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=aws-logged-credentials@v1.0 defects=1} 5 | def log_credentials_noncompliant(): 6 | import boto3 7 | import logging 8 | session = boto3.Session() 9 | credentials = session.get_credentials() 10 | credentials = credentials.get_frozen_credentials() 11 | access_key = credentials.access_key 12 | secret_key = credentials.secret_key 13 | # Noncompliant: credentials are written to the logger. 14 | logging.info('Access key: ', access_key) 15 | logging.info('secret access key: ', secret_key) 16 | # {/fact} 17 | 18 | 19 | # {fact rule=aws-logged-credentials@v1.0 defects=0} 20 | def log_credentials_compliant(): 21 | import boto3 22 | session = boto3.Session() 23 | credentials = session.get_credentials() 24 | credentials = credentials.get_frozen_credentials() 25 | access_key = credentials.access_key 26 | secret_key = credentials.secret_key 27 | # Compliant: avoids writing credentials to the logger. 28 | session = boto3.Session( 29 | aws_access_key_id=access_key, 30 | aws_secret_access_key=secret_key 31 | ) 32 | # {/fact} 33 | -------------------------------------------------------------------------------- /src/python/detectors/aws_missing_encryption_cdk/aws_missing_encryption_cdk_compliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=aws-missing-encryption-cdk@v1.0 defects=0} 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_sqs as sqs 7 | 8 | 9 | class Stack(cdk.Stack): 10 | 11 | def missing_encryption_compliant(self): 12 | # Compliant: encryption present 13 | encrypted_queue = sqs.Queue(self, 'encrypted_queue', 14 | encryption=sqs.QueueEncryption.KMS_MANAGED) 15 | # {/fact} 16 | -------------------------------------------------------------------------------- /src/python/detectors/aws_missing_encryption_cdk/aws_missing_encryption_cdk_noncompliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=aws-missing-encryption-cdk@v1.0 defects=1} 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_sqs as sqs 7 | 8 | 9 | class Stack(cdk.Stack): 10 | 11 | def missing_encryption_noncompliant(self): 12 | # Noncompliant: missing encryption 13 | unencrypted_queue = sqs.Queue(self, 'unencrypted_queue') 14 | # {/fact} 15 | -------------------------------------------------------------------------------- /src/python/detectors/aws_polling_instead_of_waiter/aws_polling_instead_of_waiter.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=aws-polling-instead-of-waiter@v1.0 defects=1} 5 | def polling_vs_waiters_noncompliant(response): 6 | import boto3 7 | ec2_client = boto3.client('ec2', region_name='us-east-1') 8 | ec2_instance_id = response['Instances'][0]['InstanceId'] 9 | 10 | attempts = 0 11 | while True: 12 | print("Waiting for EC2 instance to be up") 13 | # Noncompliant: uses custom polling instead of waiters feature. 14 | rsp = ec2_client.describe_instance_status( 15 | InstanceIds=[ 16 | str(ec2_instance_id) 17 | ], 18 | IncludeAllInstances=True 19 | ) 20 | 21 | instance_status = rsp['Statuses'][0]['InstanceStatus']['Status'] 22 | system_status = rsp['Statuses'][0]['SystemStatus']['Status'] 23 | 24 | if str(instance_status) == 'ok' and str(system_status) == 'ok': 25 | break 26 | if str(instance_status) == 'impaired' or \ 27 | str(instance_status) == 'insufficient-data' or \ 28 | str(system_status) == 'failed' or \ 29 | str(system_status) == 'insufficient-data': 30 | print('Instance status is ' + str(instance_status)) 31 | print('System status is ' + str(system_status)) 32 | tear_down() 33 | exit(1) 34 | 35 | attempts = attempts + 1 36 | if attempts >= MAX_ATTEMPTS: 37 | print("MAX wait time for EC2 instance to be up reached.") 38 | print("Tearing down") 39 | tear_down() 40 | exit(1) 41 | 42 | time.sleep(10) 43 | # {/fact} 44 | 45 | 46 | # {fact rule=aws-polling-instead-of-waiter@v1.0 defects=0} 47 | def polling_vs_waiters_compliant(): 48 | import boto3 49 | client = boto3.client('kinesis', region_name='us-east-1') 50 | # Setup the Kinesis with 1 shard. 51 | stream_name = "tf_kinesis_test_1" 52 | client.create_stream(StreamName=stream_name, ShardCount=1) 53 | # Wait until stream exists, default is 10 * 18 seconds. 54 | # Compliant: uses waiters feature. 55 | client.get_waiter('stream_exists').wait(StreamName=stream_name) 56 | for i in range(10): 57 | data = "D" + str(i) 58 | client.put_record( 59 | StreamName=stream_name, 60 | Data=data, 61 | PartitionKey="TensorFlow" + str(i) 62 | ) 63 | # {/fact} 64 | -------------------------------------------------------------------------------- /src/python/detectors/aws_unchecked_batch_failures/aws_unchecked_batch_failures.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=aws-unchecked-batch-failures@v1.0 defects=1} 5 | def write_itemsin_batch_noncompliant(self, request_items): 6 | import boto3 7 | self.dynamodb = boto3.client('dynamodb') 8 | batch_list = self.dynamodb_conn.new_batch_write_list() 9 | batch_list.add_batch(dynamodb_table, puts=items) 10 | response = self.dynamodb_conn.batch_write_item(batch_list) 11 | # Noncompliant: unprocessed items not checked. 12 | return response 13 | # {/fact} 14 | 15 | 16 | # {fact rule=aws-unchecked-batch-failures@v1.0 defects=0} 17 | def write_itemsin_batch_compliant(self, request_items): 18 | import boto3 19 | self.dynamodb = boto3.client('dynamodb') 20 | batch_list = self.dynamodb_conn.new_batch_write_list() 21 | batch_list.add_batch(dynamodb_table, puts=items) 22 | response = self.dynamodb_conn.batch_write_item(batch_list) 23 | # Compliant: has checks for unprocessed items. 24 | unprocessed = response.get('UnprocessedItems', None) 25 | return response, unprocessed 26 | # {/fact} 27 | -------------------------------------------------------------------------------- /src/python/detectors/bad_exception_handling_practices/bad_exception_handling_practices.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=bad-exception-handling-practices@v1.0 defects=1} 5 | def exception_handling_noncompliant(parameter): 6 | if not isinstance(parameter, int): 7 | # Noncompliant: a generic exception is thrown. 8 | raise Exception("param should be an integer") 9 | # {/fact} 10 | 11 | 12 | # {fact rule=bad-exception-handling-practices@v1.0 defects=0} 13 | def exception_handling_compliant(parameter): 14 | if not isinstance(parameter, int): 15 | # Compliant: specific exception is thrown. 16 | raise TypeError("param should be an integer") 17 | # {/fact} 18 | -------------------------------------------------------------------------------- /src/python/detectors/catch_and_rethrow_exception/catch_and_rethrow_exception.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=catch-and-rethrow-exception@v1.0 defects=1} 5 | def nested_noncompliant(): 6 | try: 7 | try_something() 8 | except KeyError as e: 9 | try: 10 | catch_and_try_something() 11 | # Noncompliant: unnecessary `except` clause. 12 | except ValueError: 13 | raise 14 | raise e 15 | # {/fact} 16 | 17 | 18 | # {fact rule=catch-and-rethrow-exception@v1.0 defects=0} 19 | def nested_compliant(): 20 | try: 21 | try_something() 22 | except KeyError as e: 23 | try: 24 | catch_and_try_something() 25 | except ValueError: 26 | # Compliant: operation in `except` clause. 27 | rethrow_exception_something() 28 | raise 29 | raise e 30 | # {/fact} 31 | -------------------------------------------------------------------------------- /src/python/detectors/clear_text_credentials/clear_text_credentials_compliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=clear-text-credentials@v1.0 defects=0} 5 | PASSWORD_HASHERS = [ 6 | # Compliant: uses standard and secure hashers. 7 | 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', 8 | 'django.contrib.auth.hashers.BCryptPasswordHasher', 9 | 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 10 | 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 11 | 'django.contrib.auth.hashers.Argon2PasswordHasher' 12 | ] 13 | # {/fact} 14 | -------------------------------------------------------------------------------- /src/python/detectors/clear_text_credentials/clear_text_credentials_non_compliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=clear-text-credentials@v1.0 defects=1} 5 | PASSWORD_HASHERS = [ 6 | # Noncompliant: uses non-standard or insecure password hashers. 7 | "django.contrib.auth.hashers.MD5PasswordHasher", 8 | "django.contrib.auth.hashers.PBKDF2PasswordHasher" 9 | ] 10 | # {/fact} 11 | -------------------------------------------------------------------------------- /src/python/detectors/code_injection/code_injection.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # {fact rule=code-injection@v1.0 defects=1} 6 | from flask import app 7 | 8 | 9 | @app.route('/') 10 | def execute_input_noncompliant(): 11 | from flask import request 12 | module_version = request.args.get("module_version") 13 | # Noncompliant: executes unsanitized inputs. 14 | exec("import urllib%s as urllib" % module_version) 15 | # {/fact} 16 | 17 | 18 | # {fact rule=code-injection@v1.0 defects=0} 19 | from flask import app 20 | 21 | 22 | @app.route('/') 23 | def execute_input_compliant(): 24 | from flask import request 25 | module_version = request.args.get("module_version") 26 | # Compliant: executes sanitized inputs. 27 | exec("import urllib%d as urllib" % int(module_version)) 28 | # {/fact} 29 | -------------------------------------------------------------------------------- /src/python/detectors/code_readability/code_readability.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=code-readability@v1.0 defects=1} 5 | def avoid_complex_comprehension_noncompliant(): 6 | text = [['bar', 'pie', 'line'], 7 | ['Rome', 'Madrid', 'Houston'], 8 | ['aa', 'bb', 'cc', 'dd']] 9 | # Noncompliant: list comprehensions with more than two control 10 | # sub expressions are hard to read and maintain. 11 | text_3 = [y.upper() for x in text if len(x) == 3 for y in x 12 | if y.startswith('f')] 13 | # {/fact} 14 | 15 | 16 | # {fact rule=code-readability@v1.0 defects=0} 17 | def avoid_complex_comprehension_compliant(): 18 | text = [['bar', 'pie', 'line'], 19 | ['Rome', 'Madrid', 'Houston'], 20 | ['aa', 'bb', 'cc', 'dd']] 21 | text_1 = [] 22 | # Compliant: easy to read and maintain. 23 | for x in text: 24 | if len(x) > 3: 25 | for y in x: 26 | text_1.append(y) 27 | # {/fact} 28 | -------------------------------------------------------------------------------- /src/python/detectors/cross_site_request_forgery/cross_site_request_forgery.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=cross-site-request-forgery@v1.0 defects=1} 5 | def csrf_protection_noncompliant(): 6 | from flask import Flask 7 | app = Flask(__name__) 8 | # Noncompliant: disables CSRF protection. 9 | app.config['WTF_CSRF_ENABLED'] = False 10 | # {/fact} 11 | 12 | 13 | # {fact rule=cross-site-request-forgery@v1.0 defects=0} 14 | def csrf_protection_compliant(): 15 | from flask_wtf.csrf import CsrfProtect 16 | from flask import Flask 17 | csrf = CsrfProtect() 18 | app = Flask(__name__) 19 | # Compliant: enables CSRF protection. 20 | csrf.init_app(app) 21 | # {/fact} 22 | -------------------------------------------------------------------------------- /src/python/detectors/cross_site_scripting/cross_site_scripting.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=cross-site-scripting@v1.0 defects=1} 5 | from flask import app 6 | 7 | 8 | @app.route('/redirect') 9 | def redirect_url_noncompliant(): 10 | from flask import request, redirect 11 | endpoint = request.args['url'] 12 | # Noncompliant: redirect to a user-supplied URL without sanitization. 13 | return redirect(endpoint) 14 | # {/fact} 15 | 16 | 17 | # {fact rule=cross-site-scripting@v1.0 defects=0} 18 | from flask import app 19 | 20 | 21 | @app.route('/redirect') 22 | def redirect_url_compliant(): 23 | from flask import request, url_for, redirect 24 | endpoint = request.args['url'] 25 | # Compliant: user-supplied URL is sanitized before redirecting to it. 26 | return redirect(url_for(endpoint)) 27 | # {/fact} 28 | -------------------------------------------------------------------------------- /src/python/detectors/dangerous_global_variables/dangerous_global_variables.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=dangerous-global-variables@v1.0 defects=1} 5 | def dangerous_global_noncompliant(w): 6 | # Noncompliant: uses global variable, which can be accessed 7 | # from multiple sections. 8 | global width 9 | width = w 10 | # {/fact} 11 | 12 | 13 | # {fact rule=dangerous-global-variables@v1.0 defects=0} 14 | def dangerous_global_compliant(w): 15 | # Compliant: avoids using global variables, restricting 16 | # the scope to this method. 17 | width = w 18 | return width 19 | # {/fact} 20 | -------------------------------------------------------------------------------- /src/python/detectors/default_argument_mutable_objects/default_argument_mutable_objects.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=default-argument-mutable-objects@v1.0 defects=1} 5 | # Noncompliant: default arguments have mutable objects. 6 | def with_complex_default_value_noncompliant(self, index=1, mydict={}, 7 | mylist=[]): 8 | mydict[index] = mylist[index] 9 | return mydict 10 | # {/fact} 11 | 12 | 13 | # {fact rule=default-argument-mutable-objects@v1.0 defects=0} 14 | # Compliant: default arguments are assigned to None or unmutable objects. 15 | def with_unmutable_objects_compliant(mydict=None, number=1, str="hi", 16 | mytuple=(1, 2)): 17 | print(mydict, number, str, mytuple) 18 | # {/fact} 19 | -------------------------------------------------------------------------------- /src/python/detectors/deprecated_method/deprecated_method.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=deprecated-method@v1.0 defects=1} 5 | def deprecated_method_noncompliant(url): 6 | import botocore.vendored.requests as requests 7 | # Noncompliant: uses the deprecated botocore vendored method. 8 | return requests.get(url) 9 | # {/fact} 10 | 11 | 12 | # {fact rule=deprecated-method@v1.0 defects=0} 13 | def deprecated_method_compliant(url, sigv4auth): 14 | import requests 15 | # Compliant: avoids using the deprecated methods. 16 | return requests.get(url, auth=sigv4auth).text 17 | # {/fact} 18 | -------------------------------------------------------------------------------- /src/python/detectors/detect_activated_debug_feature/detect_activated_debug_feature.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=detect-activated-debug-feature@v1.0 defects=1} 5 | def detect_activated_debug_feature_noncompliant(): 6 | from django.conf import settings 7 | # Noncompliant: The debug feature is enabled. 8 | settings.configure(DEBUG=True) 9 | # {/fact} 10 | 11 | 12 | # {fact rule=detect-activated-debug-feature@v1.0 defects=0} 13 | def detect_activated_debug_feature_compliant(): 14 | from django.conf import settings 15 | import os 16 | # Compliant: The debug feature is set through the environment variable. 17 | settings.configure(DEBUG=os.environ['DEBUG']) 18 | # {/fact} 19 | -------------------------------------------------------------------------------- /src/python/detectors/dict_get_method/dict_get_method.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=dict-get-method@v1.0 defects=1} 5 | def keyerror_noncompliant(): 6 | mydict = {1: 1, 2: 2, 3: 3} 7 | key = 5 8 | try: 9 | # Noncompliant: uses [] which causes exception when key is not found. 10 | count = mydict[key] 11 | except KeyError: 12 | count = 0 13 | return count 14 | # {/fact} 15 | 16 | 17 | # {fact rule=dict-get-method@v1.0 defects=0} 18 | def keyerror_compliant(): 19 | mydict = {1: 1, 2: 2, 3: 3} 20 | key = 5 21 | # Compliant: uses get() with a default value. 22 | return mydict.get(key, 0) 23 | # {/fact} 24 | -------------------------------------------------------------------------------- /src/python/detectors/do_not_auto_add_or_warning_missing_hostkey_policy/do_not_auto_add_or_warning_missing_hostkey_policy.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=do-not-auto-add-or-warning-missing-hostkey-policy@v1.0 defects=1} 5 | def do_not_auto_add_or_warning_missing_hostkey_policy_noncompliant(): 6 | from paramiko import AutoAddPolicy 7 | from paramiko.client import SSHClient 8 | ssh_client = SSHClient() 9 | # Noncompliant: Insecure `AutoAddPolicy` is used as missing hostkey policy. 10 | ssh_client.set_missing_host_key_policy(policy=AutoAddPolicy) 11 | # {/fact} 12 | 13 | 14 | # {fact rule=do-not-auto-add-or-warning-missing-hostkey-policy@v1.0 defects=0} 15 | def do_not_auto_add_or_warning_missing_hostkey_policy_compliant(): 16 | from paramiko import RejectPolicy 17 | from paramiko.client import SSHClient 18 | ssh_client = SSHClient() 19 | # Compliant: Secure `RejectPolicy` is used as missing hostkey policy. 20 | ssh_client.set_missing_host_key_policy(RejectPolicy) 21 | # {/fact} 22 | -------------------------------------------------------------------------------- /src/python/detectors/docker_arbitrary_container_run/docker_arbitrary_container_run.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import os 5 | from os import name 6 | from flask import request, Flask 7 | import docker 8 | app = Flask(name) 9 | 10 | 11 | # {fact rule=docker-arbitrary-container-run@v1.0 defects=1} 12 | @app.route('/someUrl') 13 | def docker_arbitrary_container_run_noncompliant(): 14 | client = docker.from_env() 15 | img = request.args.get("image") 16 | # Noncompliant: Unsanitised user input is passed to `run`. 17 | client.containers.run(img, 'echo non compliant') 18 | # {/fact} 19 | 20 | 21 | # {fact rule=docker-arbitrary-container-run@v1.0 defects=0} 22 | @app.route('/someUrl') 23 | def docker_arbitrary_container_run_compliant(): 24 | client = docker.from_env() 25 | img = os.environ["image"] 26 | # Compliant: Input from environment variable is passed to `run`. 27 | client.containers.run(img, 'echo hello world') 28 | # {/fact} 29 | -------------------------------------------------------------------------------- /src/python/detectors/equality_vs_identity/equality_vs_identity.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=equality-vs-identity@v1.0 defects=1} 5 | def notequals_operator_noncompliant(): 6 | phrase = "Thisisstring" 7 | # Noncompliant: uses checks for equality instead of identity. 8 | if phrase != None: 9 | print(True) 10 | # {/fact} 11 | 12 | 13 | # {fact rule=equality-vs-identity@v1.0 defects=0} 14 | def isnot_operator_compliant(): 15 | phrase = "Thisisstring" 16 | # Compliant: uses the correct mechanism for checking the identity. 17 | if phrase is not None: 18 | print(True) 19 | # {/fact} 20 | -------------------------------------------------------------------------------- /src/python/detectors/exposure_of_sensitive_information_cdk/exposure_of_sensitive_information_cdk_compliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=exposure-of-sensitive-information-cdk@v1.0 defects=0} 5 | import aws_cdk as cdk 6 | from aws_cdk.aws_ec2 import CfnSecurityGroupIngress 7 | 8 | 9 | class SelectivePorts(cdk.Stack): 10 | 11 | def exposure_of_sensitive_information_compliant(self): 12 | # Compliant: 0.0.0.0/0 range is not used 13 | CfnSecurityGroupIngress(cdk.Stack, 'rIngress', 14 | ip_protocol='tcp', 15 | cidr_ip='1.2.3.4/32') 16 | 17 | # {/fact} 18 | -------------------------------------------------------------------------------- /src/python/detectors/exposure_of_sensitive_information_cdk/exposure_of_sensitive_information_cdk_noncompliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=exposure-of-sensitive-information-cdk@v1.0 defects=1} 5 | import aws_cdk as cdk 6 | from aws_cdk.aws_ec2 import CfnSecurityGroupIngress 7 | 8 | 9 | class SelectivePorts(cdk.Stack): 10 | 11 | def exposure_of_sensitive_information_noncompliant(self): 12 | # Noncompliant: 0.0.0.0/0 range is used 13 | CfnSecurityGroupIngress(cdk.Stack, 'rIngress', 14 | ip_protocol='tcp', 15 | cidr_ip='0.0.0.0/0') 16 | 17 | # {/fact} 18 | -------------------------------------------------------------------------------- /src/python/detectors/hardcoded_credentials/hardcoded_credentials.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=hardcoded-credentials@v1.0 defects=1} 5 | def create_session_noncompliant(): 6 | import boto3 7 | # Noncompliant: uses hardcoded secret access key. 8 | sample_key = "AjWnyxxxxx45xxxxZxxxX7ZQxxxxYxxx1xYxxxxx" 9 | boto3.session.Session(aws_secret_access_key=sample_key) 10 | # {/fact} 11 | 12 | 13 | # {fact rule=hardcoded-credentials@v1.0 defects=0} 14 | def create_session_compliant(): 15 | import boto3 16 | import os 17 | # Compliant: uses environment variable for secret access key. 18 | sample_key = os.environ.get("AWS_SECRET_ACCESS_KEY") 19 | boto3.session.Session(aws_secret_access_key=sample_key) 20 | # {/fact} 21 | -------------------------------------------------------------------------------- /src/python/detectors/hardcoded_ip_address/hardcoded_ip_address.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from socket import AF_INET, SOCK_STREAM, socket 5 | 6 | 7 | # {fact rule=hardcoded-ip-address@v1.0 defects=1} 8 | def hardcoded_ip_address_noncompliant(): 9 | sock = socket(AF_INET, SOCK_STREAM) 10 | # Noncompliant: IP address is hardcoded. 11 | sock.bind(('193.168.14.31', 80)) 12 | # {/fact} 13 | 14 | 15 | # {fact rule=hardcoded-ip-address@v1.0 defects=0} 16 | def hardcoded_ip_address_compliant(ip_add=None): 17 | sock = socket(AF_INET, SOCK_STREAM) 18 | # Compliant: IP address is not hardcoded. 19 | sock.bind((ip_add, 5080)) 20 | # {/fact} 21 | -------------------------------------------------------------------------------- /src/python/detectors/hashlib_constructor/hashlib_constructor.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=hashlib-constructor@v1.0 defects=1} 5 | def constructor_noncompliant(): 6 | import hashlib 7 | 8 | text = "ExampleString" 9 | 10 | # Noncompliant: uses the new() constructor instead of the hashlib 11 | # constructor, which is slower. 12 | result = hashlib.new('sha256', text.encode()) 13 | 14 | print("The hexadecimal equivalent of SHA256 is : ") 15 | print(result.hexdigest()) 16 | # {/fact} 17 | 18 | 19 | # {fact rule=hashlib-constructor@v1.0 defects=0} 20 | def constructor_compliant(): 21 | import hashlib 22 | 23 | text = "ExampleString" 24 | 25 | # Compliant: uses the hashlib constructor over the new(), which is faster. 26 | result = hashlib.sha256(text.encode()) 27 | 28 | print("The hexadecimal equivalent of SHA256 is : ") 29 | print(result.hexdigest()) 30 | # {/fact} 31 | -------------------------------------------------------------------------------- /src/python/detectors/improper_authentication/improper_authentication.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=improper-authentication@v1.0 defects=1} 5 | def improper_authentication_noncompliant(token): 6 | import jwt 7 | # Noncompliant: The verify flag is set to false. 8 | jwt.decode(token, verify=False) 9 | # {/fact} 10 | 11 | 12 | # {fact rule=improper-authentication@v1.0 defects=0} 13 | def improper_authentication_compliant(token): 14 | import jwt 15 | # Compliant: The verify flag is set to true. 16 | jwt.decode(token, verify=True) 17 | # {/fact} 18 | -------------------------------------------------------------------------------- /src/python/detectors/improper_certificate_validation/improper_certificate_validation.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=improper-certificate-validation@v1.0 defects=1} 5 | def create_connection_noncompliant(): 6 | import socket 7 | import ssl 8 | host, port = 'example.com', 443 9 | with socket.socket(socket.AF_INET) as sock: 10 | context = ssl.SSLContext() 11 | # Noncompliant: security certificate validation disabled. 12 | context.verify_mode = ssl.CERT_NONE 13 | conn = context.wrap_socket(sock, server_hostname=host) 14 | try: 15 | conn.connect((host, port)) 16 | handle(conn) 17 | finally: 18 | conn.close() 19 | # {/fact} 20 | 21 | 22 | # {fact rule=improper-certificate-validation@v1.0 defects=0} 23 | def create_connection_compliant(): 24 | import socket 25 | import ssl 26 | host, port = 'example.com', 443 27 | with socket.socket(socket.AF_INET) as sock: 28 | context = ssl.SSLContext() 29 | # Compliant: security certificate validation enabled. 30 | context.verify_mode = ssl.CERT_REQUIRED 31 | conn = context.wrap_socket(sock, server_hostname=host) 32 | try: 33 | conn.connect((host, port)) 34 | handle(conn) 35 | finally: 36 | conn.close() 37 | # {/fact} 38 | -------------------------------------------------------------------------------- /src/python/detectors/improper_error_handling/improper_error_handling.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=improper-error-handling@v1.0 defects=1} 5 | def error_handling_pass_noncompliant(): 6 | number = input("Enter number:\n") 7 | try: 8 | int(number) 9 | except Exception: 10 | # Noncompliant: has improper error handling. 11 | pass 12 | # {/fact} 13 | 14 | 15 | # {fact rule=improper-error-handling@v1.0 defects=1} 16 | def error_handling_continue_noncompliant(): 17 | number = input("Enter number:\n") 18 | for i in range(10): 19 | try: 20 | int(number) 21 | except Exception: 22 | # Noncompliant: has improper error handling. 23 | continue 24 | # {/fact} 25 | 26 | 27 | # {fact rule=improper-error-handling@v1.0 defects=0} 28 | def error_handling_compliant(): 29 | number = input("Enter number:\n") 30 | try: 31 | int(number) 32 | except ValueError: 33 | # Compliant: has proper error handling. 34 | print(number, "is not an integer.") 35 | # {/fact} 36 | -------------------------------------------------------------------------------- /src/python/detectors/improper_input_validation/improper_input_validation.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=improper-input-validation@v1.0 defects=1} 5 | def yaml_load_noncompliant(): 6 | import json 7 | import yaml 8 | response = yaml.dump({'a': 1, 'b': 2, 'c': 3}) 9 | # Noncompliant: uses unsafe yaml load. 10 | result = yaml.load(response) 11 | yaml.dump(result) 12 | # {/fact} 13 | 14 | 15 | # {fact rule=improper-input-validation@v1.0 defects=0} 16 | def yaml_load_compliant(): 17 | import json 18 | import yaml 19 | response = yaml.dump({'a': 1, 'b': 2, 'c': 3}) 20 | # Compliant: uses safe yaml load. 21 | result = yaml.load(response, Loader=yaml.CSafeLoader) 22 | yaml.dump(result) 23 | # {/fact} 24 | -------------------------------------------------------------------------------- /src/python/detectors/improper_privilege_management/improper_privilege_management.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=improper-privilege-management@v1.0 defects=1} 5 | def set_user_noncompliant(): 6 | import os 7 | root = 0 8 | # Noncompliant: the process user is set to root. 9 | os.setuid(root) 10 | # {/fact} 11 | 12 | 13 | # {fact rule=improper-privilege-management@v1.0 defects=0} 14 | def set_user_compliant(): 15 | import os 16 | root = 4 17 | # Compliant: the process user is set to userid 4. 18 | os.setuid(root) 19 | # {/fact} 20 | -------------------------------------------------------------------------------- /src/python/detectors/incorrect_usage_of_process_terminate_api/incorrect_usage_of_process_terminate_api.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=incorrect-usage-of-process-terminate-api@v1.0 defects=1} 5 | def put_object_to_queue_noncompliant(queue): 6 | try: 7 | queue.put([42, None, 'hello']) 8 | finally: 9 | queue.task_done() 10 | 11 | 12 | def shared_queue_noncompliant(): 13 | from multiprocessing.context import Process 14 | from multiprocessing.queues import Queue 15 | queue = Queue() 16 | process = Process(target=put_object_to_queue_noncompliant, args=(queue,)) 17 | process.start() 18 | print(queue.get()) # prints "[42, None, 'hello']" 19 | # Noncompliant: uses 'Process.terminate' API on shared resources making 20 | # queue liable to become corrupted and may become unusable by other process 21 | process.terminate() 22 | # trying to access corrupt queue 23 | queue.put([50, None, 'hello']) 24 | # {/fact} 25 | 26 | 27 | # {fact rule=incorrect-usage-of-process-terminate-api@v1.0 defects=0} 28 | def put_object_to_queue_compliant(queue): 29 | try: 30 | queue.put([42, None, 'hello']) 31 | finally: 32 | queue.task_done() 33 | 34 | 35 | def shared_queue_compliant(): 36 | from multiprocessing.context import Process 37 | from multiprocessing.queues import Queue 38 | queue = Queue() 39 | process = Process(target=put_object_to_queue_compliant, args=(queue,)) 40 | process.start() 41 | print(queue.get()) # prints "[42, None, 'hello']" 42 | # Compliant: avoids using 'Process.terminate' API on shared resources 43 | # making the queue accessible. 44 | queue.join() 45 | # accessing safe queue object 46 | queue.put([50, None, 'hello']) 47 | # {/fact} 48 | -------------------------------------------------------------------------------- /src/python/detectors/insecure_connection/insecure_connection.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=insecure-connection@v1.0 defects=1} 5 | def ftp_connection_noncompliant(): 6 | import ftplib 7 | # Noncompliant: insecure ftp used. 8 | cnx = ftplib.FTP("ftp://anonymous@example.com") 9 | # {/fact} 10 | 11 | 12 | # {fact rule=insecure-connection@v1.0 defects=0} 13 | def ftp_connection_compliant(): 14 | import ftplib 15 | # Compliant: secure ftp_tls used. 16 | cnx = ftplib.FTP_TLS("ftp.example.com") 17 | # {/fact} 18 | -------------------------------------------------------------------------------- /src/python/detectors/insecure_cookie/insecure_cookie.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=insecure-cookie@v1.0 defects=1} 5 | def secure_cookie_noncompliant(): 6 | from http.cookies import SimpleCookie 7 | cookie = SimpleCookie() 8 | cookie['sample'] = "sample_value" 9 | # Noncompliant: the cookie is insecure. 10 | cookie['sample']['secure'] = 0 11 | print(cookie) 12 | # {/fact} 13 | 14 | 15 | # {fact rule=insecure-cookie@v1.0 defects=0} 16 | def secure_cookie_compliant(): 17 | from http.cookies import SimpleCookie 18 | cookie = SimpleCookie() 19 | cookie['sample'] = "sample_value" 20 | # Compliant: the cookie is secure. 21 | cookie['sample']['secure'] = True # compliant 22 | print(cookie) 23 | # {/fact} 24 | -------------------------------------------------------------------------------- /src/python/detectors/insecure_cors_policy/insecure_cors_policy_compliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=insecure-cors-policy@v1.0 defects=0} 5 | from flask import app, request 6 | from flask import Flask 7 | from flask_cors import CORS 8 | 9 | app = Flask(__name__) 10 | # Compliant: the send_wildcard is set to allow only a specific list of 11 | # trusted domains. 12 | CORS(app, send_wildcard=False) 13 | # {/fact} 14 | -------------------------------------------------------------------------------- /src/python/detectors/insecure_cors_policy/insecure_cors_policy_noncompliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=insecure-cors-policy@v1.0 defects=1} 5 | from flask import app, request 6 | from flask import Flask 7 | from flask_cors import CORS 8 | 9 | 10 | app = Flask(__name__) 11 | # Noncompliant: the send_wildcard is set to allow any domain. 12 | CORS(app, send_wildcard=True) 13 | # {/fact} 14 | -------------------------------------------------------------------------------- /src/python/detectors/insecure_cryptography/insecure_cryptography.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=insecure-cryptography@v1.0 defects=1} 5 | def cryptography_noncompliant(): 6 | from cryptography.hazmat.primitives import hashes, hmac 7 | import secrets 8 | # Noncompliant: keysize too small for this algorithm. 9 | key = secrets.token_bytes(12) 10 | hash_key = hmac.HMAC(key, algorithm=hashes.SHA512_224()) 11 | # {/fact} 12 | 13 | 14 | # {fact rule=insecure-cryptography@v1.0 defects=0} 15 | def cryptography_compliant(): 16 | from cryptography.hazmat.primitives import hashes, hmac 17 | import secrets 18 | # Compliant: keysize sufficient for this algorithm. 19 | key = secrets.token_bytes(48) 20 | hash_key = hmac.HMAC(key, algorithm=hashes.SHA512_224()) 21 | # {/fact} 22 | -------------------------------------------------------------------------------- /src/python/detectors/insecure_hashing/insecure_hashing.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=insecure-hashing@v1.0 defects=1} 5 | def hashing_noncompliant(): 6 | import hashlib 7 | from hashlib import pbkdf2_hmac 8 | # Noncompliant: insecure hashing algorithm used. 9 | derivedkey = hashlib.pbkdf2_hmac('sha224', password, salt, 100000) 10 | derivedkey.hex() 11 | # {/fact} 12 | 13 | 14 | # {fact rule=insecure-hashing@v1.0 defects=0} 15 | def hashing_compliant(): 16 | import hashlib 17 | from hashlib import pbkdf2_hmac 18 | # Compliant: secure hashing algorithm used. 19 | derivedkey = hashlib.pbkdf2_hmac('sha256', password, salt, 100000) 20 | derivedkey.hex() 21 | # {/fact} 22 | -------------------------------------------------------------------------------- /src/python/detectors/insecure_socket_bind/insecure_socket_bind.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=insecure-socket-bind@v1.0 defects=1} 5 | def insecure_socket_bind_noncompliant(): 6 | import socket 7 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 8 | # Noncompliant: Empty IP Address is passed when binding to a socket. 9 | s.bind(('', 0)) 10 | # {/fact} 11 | 12 | 13 | # {fact rule=insecure-socket-bind@v1.0 defects=0} 14 | def insecure_socket_bind_compliant(): 15 | import socket 16 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 17 | # Compliant: Non-empty IP Address is passed when binding to a socket. 18 | s.bind(('192.168.1.1', 0)) 19 | # {/fact} 20 | -------------------------------------------------------------------------------- /src/python/detectors/insecure_temp_file/insecure_temp_file.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=insecure-temp-file@v1.0 defects=1} 5 | def create_file_noncompliant(results): 6 | import tempfile 7 | # Noncompliant: uses a temporary file path to create a temporary file. 8 | filename = tempfile.mktemp() 9 | with open(filename, "w+") as f: 10 | f.write(results) 11 | print("Results written to", filename) 12 | # {/fact} 13 | 14 | 15 | # {fact rule=insecure-temp-file@v1.0 defects=0} 16 | def create_temp_file_compliant(results): 17 | import tempfile 18 | # Compliant: uses the correct mechanism to create a temporary file. 19 | with tempfile.NamedTemporaryFile(mode="w+", delete=False) as f: 20 | f.write(results) 21 | print("Results written to", f.name) 22 | # {/fact} 23 | -------------------------------------------------------------------------------- /src/python/detectors/integer_overflow/integer_overflow.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import numpy as np 5 | # {fact rule=integer-overflow@v1.0 defects=1} 6 | 7 | 8 | def integer_overflow_noncompliant(): 9 | # Noncompliant: Number larger than limit of the datatype is stored. 10 | arr = np.array([[100000000]], dtype=np.int8) 11 | # {/fact} 12 | 13 | 14 | # {fact rule=integer-overflow@v1.0 defects=0} 15 | def integer_overflow_compliant(self, request_items): 16 | # Compliant: Number stored is within the limits of the specified datatype. 17 | arr = np.array([100000000], dtype=np.int32) 18 | # {/fact} 19 | -------------------------------------------------------------------------------- /src/python/detectors/iterating_sequence_modification/iterating_sequence_modification.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=iterating-sequence-modification@v1.0 defects=1} 5 | def modifying_list_noncompliant(): 6 | words = ['cat', 'window', 'defenestrate'] 7 | # Noncompliant: modifies the same list while iterating over it. 8 | for word in words: 9 | if len(word) > 6: 10 | words.insert(0, word) 11 | # {/fact} 12 | 13 | 14 | # {fact rule=iterating-sequence-modification@v1.0 defects=0} 15 | def modifying_list_splice_compliant(): 16 | words = ['cat', 'window', 'defenestrate'] 17 | # Compliant: creates new list using splicing, hence loops iterate on one 18 | # list, modifying a different list. 19 | for word in words[:]: 20 | if len(word) > 6: 21 | words.insert(0, word) 22 | # {/fact} 23 | -------------------------------------------------------------------------------- /src/python/detectors/lambda_client_reuse/lambda_client_reuse.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=lambda-client-reuse@v1.0 defects=1} 5 | def lambda_handler_noncompliant(event, context): 6 | import boto3 7 | # Noncompliant: recreates AWS clients in each lambda invocation. 8 | client = boto3.client('s3') 9 | response = client.list_buckets() 10 | # {/fact} 11 | 12 | 13 | # {fact rule=lambda-client-reuse@v1.0 defects=0} 14 | import boto3 15 | client = boto3.client('s3') 16 | 17 | 18 | def lambda_handler_compliant(event, context): 19 | # Compliant: uses the cached client. 20 | response = client.list_buckets() 21 | # {/fact} 22 | -------------------------------------------------------------------------------- /src/python/detectors/lambda_override_reserved/lambda_override_reserved.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=lambda-override-reserved@v1.0 defects=1} 5 | def create_variable_noncompliant(): 6 | import os 7 | # Noncompliant: overrides reserved environment variable names 8 | # in a Lambda function. 9 | os.environ['_HANDLER'] = "value" 10 | # {/fact} 11 | 12 | 13 | # {fact rule=lambda-override-reserved@v1.0 defects=0} 14 | def create_variable_compliant(): 15 | import os 16 | # Compliant: prevents overriding reserved environment variable names 17 | # in a Lambda function. 18 | os.environ['SOME_ENV_VAR'] = "value" 19 | # {/fact} 20 | -------------------------------------------------------------------------------- /src/python/detectors/ldap_authentication/ldap_authentication.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=ldap-authentication@v1.0 defects=1} 5 | def authenticate_connection_noncompliant(): 6 | import ldap 7 | import os 8 | connect = ldap.initialize('ldap://127.0.0.1:1389') 9 | connect.set_option(ldap.OPT_REFERRALS, 0) 10 | # Noncompliant: authentication disabled. 11 | connect.simple_bind('cn=root') 12 | # {/fact} 13 | 14 | 15 | # {fact rule=ldap-authentication@v1.0 defects=0} 16 | def authenticate_connection_compliant(): 17 | import ldap 18 | import os 19 | connect = ldap.initialize('ldap://127.0.0.1:1389') 20 | connect.set_option(ldap.OPT_REFERRALS, 0) 21 | # Compliant: simple security authentication used. 22 | connect.simple_bind('cn=root', os.environ.get('LDAP_PASSWORD')) 23 | # {/fact} 24 | -------------------------------------------------------------------------------- /src/python/detectors/ldap_injection/ldap_injection.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=ldap-injection@v1.0 defects=1} 5 | from flask import app 6 | 7 | 8 | @app.route('/getUsers') 9 | def get_users_noncompliant(): 10 | import ldap 11 | from flask import request 12 | username = request.args['username'] 13 | filter_string = '(uid=' + username + ')' 14 | ldap_conn = ldap.initialize('ldaps://ldap.amazon.com:636') 15 | # Noncompliant: user-supplied filter is not sanitized. 16 | result = ldap_conn.search_s('o=amazon.com', 17 | ldap.SCOPE_SUBTREE, 18 | filter_string) 19 | return result 20 | # {/fact} 21 | 22 | 23 | # {fact rule=ldap-injection@v1.0 defects=0} 24 | from flask import app 25 | 26 | 27 | @app.route('/getUsers') 28 | def get_users_compliant(request): 29 | import ldap 30 | import re 31 | from flask import request 32 | username = request.args['username'] 33 | # Compliant: user-supplied filter is checked for allowed characters. 34 | filter_string = "(uid=" + re.sub('[!@#$%^&*()_+-=]', '', username) + ")" 35 | ldap_conn = ldap.initialize('ldaps://ldap.amazon.com:636') 36 | result = ldap_conn.search('o=amazon.com', 37 | ldap.SCOPE_SUBTREE, 38 | filter_string) 39 | return result 40 | # {/fact} 41 | -------------------------------------------------------------------------------- /src/python/detectors/leaky_subprocess_timeout/leaky_subprocess_timeout.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=leaky-subprocess-timeout@v1.0 defects=1} 5 | def subprocess_timeout_noncompliant(): 6 | import subprocess 7 | process = subprocess.Popen("ls -al", 8 | bufsize=-1, 9 | stdout=subprocess.PIPE, 10 | stderr=subprocess.PIPE) 11 | try: 12 | # Noncompliant: fails to terminate the child process before 13 | # the timeout expires. 14 | outs, errs = process.communicate(timeout=15) 15 | except subprocess.TimeoutExpired: 16 | print("Timed out") 17 | # {/fact} 18 | 19 | 20 | # {fact rule=leaky-subprocess-timeout@v1.0 defects=0} 21 | def subprocess_timeout_compliant(): 22 | import subprocess 23 | process = subprocess.Popen("ls -al", 24 | bufsize=-1, 25 | stdout=subprocess.PIPE, 26 | stderr=subprocess.PIPE) 27 | try: 28 | # Compliant: makes sure to terminate the child process when 29 | # the timeout expires. 30 | outs, errs = process.communicate(timeout=15) 31 | except subprocess.TimeoutExpired: 32 | process.kill() 33 | outs, errs = process.communicate() 34 | # {/fact} 35 | -------------------------------------------------------------------------------- /src/python/detectors/log_injection/log_injection.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import logging 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | # {fact rule=log-injection@v1.0 defects=1} 9 | def logging_noncompliant(): 10 | filename = input("Enter a filename: ") 11 | # Noncompliant: unsanitized input is logged. 12 | logger.info("Processing %s", filename) 13 | # {/fact} 14 | 15 | 16 | # {fact rule=log-injection@v1.0 defects=0} 17 | def logging_compliant(): 18 | filename = input("Enter a filename: ") 19 | if filename.isalnum(): 20 | # Compliant: input is validated before logging. 21 | logger.info("Processing %s", filename) 22 | # {/fact} 23 | -------------------------------------------------------------------------------- /src/python/detectors/loose_file_permissions/loose_file_permissions.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=loose-file-permissions@v1.0 defects=1} 5 | def change_file_permissions_noncompliant(): 6 | import os 7 | import stat 8 | # Noncompliant: permissions assigned to all users. 9 | os.chmod("sample.txt", stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) 10 | # {/fact} 11 | 12 | 13 | # {fact rule=loose-file-permissions@v1.0 defects=0} 14 | def change_file_permissions_compliant(): 15 | import os 16 | import stat 17 | # Compliant: permissions assigned to owner and owner group. 18 | os.chmod("sample.txt", stat.S_IRWXU | stat.S_IRWXG) 19 | # {/fact} 20 | -------------------------------------------------------------------------------- /src/python/detectors/missing_authentication_for_critical_function_cdk/missing_authentication_for_critical_function_cdk_compliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=missing-authentication-for-critical-function-cdk@v1.0 defects=0} 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_s3 as s3 7 | 8 | 9 | class S3Stack(cdk.Stack): 10 | 11 | def missing_authentication_compliant(self): 12 | # Compliant: bucket is private 13 | public_bucket = s3.Bucket(self, 'bucket') 14 | # {/fact} 15 | -------------------------------------------------------------------------------- /src/python/detectors/missing_authentication_for_critical_function_cdk/missing_authentication_for_critical_function_cdk_noncompliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=missing-authentication-for-critical-function-cdk@v1.0 defects=1} 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_s3 as s3 7 | 8 | 9 | class S3Stack(cdk.Stack): 10 | 11 | def missing_authentication_noncompliant(self): 12 | # Noncompliant: bucket made public 13 | public_bucket = s3.Bucket(self, 'bucket') 14 | public_bucket.grant_public_access() 15 | # {/fact} 16 | -------------------------------------------------------------------------------- /src/python/detectors/missing_none_check/missing_none_check.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=missing-none-check@v1.0 defects=1} 5 | def none_check_noncompliant(): 6 | import boto3 7 | ddb_client = boto3.client('dynamodb') 8 | response = ddb_client.update_item() 9 | # Noncompliant: does not check to verify if the ResponseMetadata is None. 10 | return response.get("ResponseMetadata", {}) 11 | # {/fact} 12 | 13 | 14 | # {fact rule=missing-none-check@v1.0 defects=0} 15 | def none_check_compliant(self, record_dicts: List[Dict]) -> Dict: 16 | import boto3 17 | kinesis_client = boto3.client('kinesis') 18 | response = kinesis_client.put_records(record_dicts) 19 | # Compliant: checks to verify if the response metadata is None. 20 | response_metadata = response.get('ResponseMetadata', {}) 21 | if response_metadata is not None: 22 | return response_metadata 23 | else: 24 | return response 25 | # {/fact} 26 | -------------------------------------------------------------------------------- /src/python/detectors/missing_pagination/missing_pagination.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=missing-pagination@v1.0 defects=1} 5 | def s3_loop_noncompliant(s3bucket_name, s3prefix_name): 6 | import boto3 7 | 8 | s3_client = boto3.resource('s3').meta.client 9 | # Noncompliant: loops through the contents without checking whether 10 | # more requests are needed. 11 | list_object_response = s3_client.list_objects_v2(Bucket=s3bucket_name, 12 | Prefix=s3prefix_name) 13 | try: 14 | if 'Contents' in list_object_response: 15 | s3_deployment_folders = list_object_response['Contents'] 16 | return s3_deployment_folders 17 | 18 | except ListException: 19 | print("List objects in bucket {} with prefix {} " 20 | "failed with response {}".format(s3bucket_name, 21 | s3prefix_name, 22 | list_object_response)) 23 | # {/fact} 24 | 25 | 26 | # {fact rule=missing-pagination@v1.0 defects=0} 27 | def s3_recursion_compliant(self, s3bucket_name, s3prefix_name, token=None): 28 | import boto3 29 | 30 | s3_client = boto3.client('s3') 31 | list_object_response = s3_client.list_objects_v2( 32 | Bucket=s3bucket_name, 33 | Prefix=s3prefix_name, 34 | ContinuationToken=token 35 | ) if token else s3_client.list_objects_v2(Bucket=s3bucket_name, 36 | Prefix=s3prefix_name) 37 | 38 | s3_deployment_folders = list_object_response['Contents'] 39 | # Compliant: keeps requesting until no more requests are needed. 40 | if not list_object_response['IsTruncated']: 41 | return s3_deployment_folders 42 | 43 | next_response = self.s3_recursion_compliant(s3bucket_name, s3prefix_name, 44 | list_object_response 45 | ['NextContinuationToken']) 46 | s3_deployment_folders += next_response 47 | 48 | return s3_deployment_folders 49 | # {/fact} 50 | -------------------------------------------------------------------------------- /src/python/detectors/module_injection/module_injection.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=module-injection@v1.0 defects=1} 5 | def module_injection_noncompliant(): 6 | import importlib 7 | module_name = input('module name') 8 | # Noncompliant: Untrusted user input is being passed to `import_module`. 9 | importlib.import_module(module_name) 10 | # {/fact} 11 | 12 | 13 | # {fact rule=module-injection@v1.0 defects=0} 14 | def module_injection_compliant(): 15 | import importlib 16 | allowed_module_names_list = ['example_module1', 'example_module2'] 17 | module_name = input('module name') 18 | if module_name in allowed_module_names_list: 19 | # Compliant: User input is validated before using in `import_module()`. 20 | importlib.import_module(module_name) 21 | # {/fact} 22 | -------------------------------------------------------------------------------- /src/python/detectors/multidimension_list_using_replication/multidimension_list_using_replication.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=multidimension-list-using-replication@v1.0 defects=1} 5 | def error_prone_multidimensional_list_noncompliant(): 6 | # Noncompliant: initialises a multidimensional list using replication. 7 | multi_dimension_list = [[1]]*3 8 | # {/fact} 9 | 10 | 11 | # {fact rule=multidimension-list-using-replication@v1.0 defects=0} 12 | def error_prone_multidimensional_list_compliant(): 13 | # Compliant: avoids initialising a multidimensional list using replication. 14 | multi_dimension_list = [[1 for x in range(2)] for y in range(3)] 15 | # {/fact} 16 | -------------------------------------------------------------------------------- /src/python/detectors/multiple_values_in_return/multiple_values_in_return.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=multiple-values-in-return@v1.0 defects=1} 5 | def unpack_multiple_values_noncompliant(): 6 | # Noncompliant: uses larger number of return values 7 | # making it prone to errors. 8 | return 'a', 'abc', 100, [0, 1, 2] 9 | # {/fact} 10 | 11 | 12 | # {fact rule=multiple-values-in-return@v1.0 defects=0} 13 | def unpack_multiple_values_compliant(): 14 | # Compliant: avoids using larger number of return values 15 | # making it less prone to errors. 16 | return 'abc', 100, [0, 1, 2] 17 | # {/fact} 18 | -------------------------------------------------------------------------------- /src/python/detectors/multiprocessing_deadlock_prevention/multiprocessing_deadlock_prevention.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=multiprocessing-deadlock-prevention@v1.0 defects=1} 5 | def deadlock_prevention_noncompliant(): 6 | from subprocess import Popen, PIPE 7 | process = Popen('sh ~/example.sh', stdout=PIPE) 8 | # Noncompliant: uses the 'Popen.wait' with 'stdout=PIPE' or 'stderr=PIPE', 9 | # resulting in a potential deadlock and busy loop. 10 | process.wait() 11 | print(process.returncode) 12 | # {/fact} 13 | 14 | 15 | # {fact rule=multiprocessing-deadlock-prevention@v1.0 defects=0} 16 | def deadlock_prevention_compliant(): 17 | from subprocess import Popen, PIPE 18 | process = Popen('sh ~/example.sh', stdout=PIPE) 19 | # Compliant: uses 'Popen.communicate' method, avoiding a 20 | # potential deadlock and busy loop. 21 | process.communicate()[0] 22 | print(process.returncode) 23 | # {/fact} 24 | -------------------------------------------------------------------------------- /src/python/detectors/multiprocessing_garbage_collection_prevention/multiprocessing_garbage_collection_prevention.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=multiprocessing-garbage-collection-prevention@v1.0 defects=1} 5 | def garbage_collect_noncompliant(self): 6 | from multiprocessing import Pipe 7 | pipe = Pipe() 8 | try: 9 | # Trigger a refresh. 10 | self.assertFalse( 11 | client._MongoReplicaSetClient__monitor.isAlive()) 12 | 13 | client.disconnect() 14 | self.assertSoon( 15 | lambda: client._MongoReplicaSetClient__monitor.isAlive()) 16 | 17 | client.db.collection.find_one() 18 | except Exception: 19 | traceback.print_exc() 20 | pipe.send(True) 21 | 22 | 23 | def multiprocessing_noncompliant(): 24 | from multiprocessing import Process, Pipe 25 | parent_connection, child_connection = Pipe() 26 | # Noncompliant: fails to pass the parent process object to child processes. 27 | process = Process(target=garbage_collect_noncompliant) 28 | process.start() 29 | 30 | # {/fact} 31 | 32 | 33 | # {fact rule=multiprocessing-garbage-collection-prevention@v1.0 defects=0} 34 | def garbage_collect_compliant(self, pipe): 35 | try: 36 | # Trigger a refresh. 37 | self.assertFalse( 38 | client._MongoReplicaSetClient__monitor.isAlive()) 39 | 40 | client.disconnect() 41 | self.assertSoon( 42 | lambda: client._MongoReplicaSetClient__monitor.isAlive()) 43 | 44 | client.db.collection.find_one() 45 | except Exception: 46 | traceback.print_exc() 47 | pipe.send(True) 48 | 49 | 50 | def multiprocessing_compliant(): 51 | from multiprocessing import Process, Pipe 52 | parent_connection, child_connection = Pipe() 53 | # Compliant: parent process object is passed to its child processes. 54 | process = Process(target=garbage_collect_compliant, 55 | args=(child_connection,)) 56 | process.start() 57 | # {/fact} 58 | -------------------------------------------------------------------------------- /src/python/detectors/mutually_exclusive_calls_found/mutually_exclusive_calls_found.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=mutually-exclusive-calls-found@v1.0 defects=1} 5 | def get_metrics_noncompliant() -> None: 6 | import boto3 7 | client = boto3.client('cloudwatch', region_name='eu-west-1') 8 | datapoints = client.get_metric_statistics( 9 | Namespace='CloudWatchSdkTest', 10 | MetricName='PythonBotoTestMetric', 11 | Dimensions=[ 12 | { 13 | 'Name': 'DimensionName', 14 | 'Value': 'DimensionValue' 15 | }, 16 | ], 17 | EndTime=datetime.datetime.now(datetime.timezone.utc), 18 | StartTime=EndTime - datetime.timedelta(days=1), 19 | Period=300, 20 | # Noncompliant: calls mutually exclusive methods. 21 | Statistics=[ 22 | 'SampleCount', 'Average', 'Sum', 'Minimum', 'Maximum' 23 | ], 24 | ExtendedStatistics=[ 25 | 'p70' 26 | ] 27 | ) 28 | # {/fact} 29 | 30 | 31 | # {fact rule=mutually-exclusive-calls-found@v1.0 defects=0} 32 | def get_metrics_compliant() -> None: 33 | import boto3 34 | client = boto3.client('cloudwatch', region_name='eu-west-1') 35 | datapoints = client.get_metric_statistics( 36 | Namespace='CloudWatchSdkTest', 37 | MetricName='PythonBotoTestMetric', 38 | Dimensions=[ 39 | { 40 | 'Name': 'DimensionName', 41 | 'Value': 'DimensionValue' 42 | }, 43 | ], 44 | EndTime=datetime.datetime.now(datetime.timezone.utc), 45 | StartTime=EndTime - datetime.timedelta(days=1), 46 | Period=300, 47 | # Compliant: avoid calling mutually exclusive methods. 48 | ExtendedStatistics=[ 49 | 'p99', 50 | 'p100' 51 | ] 52 | ) 53 | # {/fact} 54 | -------------------------------------------------------------------------------- /src/python/detectors/naive_datatime_time_zone_issues/naive_datatime_time_zone_issues.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=naive-datatime-time-zone-issues@v1.0 defects=1} 5 | def datetime_noncompliant(): 6 | from datetime import datetime 7 | # Noncompliant: datetime method does not specify timezone, 8 | # resulting in time zone related issues. 9 | return datetime.now() 10 | # {/fact} 11 | 12 | 13 | # {fact rule=naive-datatime-time-zone-issues@v1.0 defects=0} 14 | def datetime_compliant(): 15 | from datetime import datetime, timezone 16 | # Compliant: datetime method specifies the time zone, 17 | # avoiding the time zone related issues. 18 | return datetime.now(tz=timezone.utc) 19 | # {/fact} 20 | -------------------------------------------------------------------------------- /src/python/detectors/not_recommended_apis/not_recommended_apis_compliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=not-recommended-apis@v1.0 defects=0} 5 | import xml 6 | import defusedxml.sax 7 | 8 | 9 | class ContentHandler(xml.sax.ContentHandler): 10 | def __init__(self): 11 | xml.sax.ContentHandler.__init__(self) 12 | 13 | def start_element(self, name, attributes): 14 | print('start:', name) 15 | 16 | def end_element(self, name): 17 | print('end:', name) 18 | 19 | def characters(self, characters): 20 | print('characters:', characters) 21 | 22 | 23 | def not_recommended_apis_compliant(): 24 | xml_string = "
XML_STRING" 25 | 26 | # Compliant: avoids using unrecommended APIs. 27 | defusedxml.sax.parseString(xml_string, ContentHandler()) 28 | 29 | 30 | if __name__ == "__main__": 31 | not_recommended_apis_compliant() 32 | # {/fact} 33 | -------------------------------------------------------------------------------- /src/python/detectors/not_recommended_apis/not_recommended_apis_noncompliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=not-recommended-apis@v1.0 defects=1} 5 | import xml.sax 6 | 7 | 8 | class ContentHandler(xml.sax.ContentHandler): 9 | def __init__(self): 10 | xml.sax.ContentHandler.__init__(self) 11 | 12 | def start_element(self, name, attributes): 13 | print('start:', name) 14 | 15 | def end_element(self, name): 16 | print('end:', name) 17 | 18 | def characters(self, characters): 19 | print('characters:', characters) 20 | 21 | 22 | def recommended_apis_noncompliant(): 23 | xml_string = "XML_STRING" 24 | 25 | # Noncompliant: uses xml.sax which is an unrecommended API. 26 | xml.sax.parseString(xml_string, ContentHandler()) 27 | 28 | 29 | if __name__ == "__main__": 30 | recommended_apis_noncompliant() 31 | # {/fact} 32 | -------------------------------------------------------------------------------- /src/python/detectors/object_dict_modification/object_dict_modification.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=object-dict-modification@v1.0 defects=1} 5 | def modify_dictionary_noncompliant(): 6 | import os 7 | # Noncompliant: modifies the __dict__ object directly. 8 | os.__dict__ = value 9 | # {/fact} 10 | 11 | 12 | # {fact rule=object-dict-modification@v1.0 defects=0} 13 | def modify_dictionary_compliant(test_args): 14 | from nettles_service.models.baseline import Baseline 15 | 16 | baseline = Baseline(**test_args) 17 | for arg, value in test_args.items(): 18 | # Compliant: avoids modifying the __dict__ object directly. 19 | assert baseline.__dict__[arg] == value 20 | # {/fact} 21 | -------------------------------------------------------------------------------- /src/python/detectors/os_command_injection/os_command_injection.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=os-command-injection@v1.0 defects=1} 5 | def exec_command_noncompliant(): 6 | from paramiko import client 7 | from flask import request 8 | address = request.args.get("address") 9 | cmd = "ping -c 1 %s" % address 10 | client = client.SSHClient() 11 | client.connect("ssh.samplehost.com") 12 | # Noncompliant: address argument is not sanitized. 13 | client.exec_command(cmd) 14 | # {/fact} 15 | 16 | 17 | # {fact rule=os-command-injection@v1.0 defects=0} 18 | def exec_command_compliant(): 19 | from paramiko import client 20 | from flask import request 21 | address = request.args.get("address") 22 | # Compliant: address argument is sanitized (shell-escaped). 23 | address = shlex.quote(request.args.get("address")) 24 | cmd = "ping -c 1 %s" % address 25 | client = client.SSHClient() 26 | client.connect("ssh.samplehost.com") 27 | client.exec_command(cmd) 28 | # {/fact} 29 | -------------------------------------------------------------------------------- /src/python/detectors/path_traversal/path_traversal.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=path-traversal@v1.0 defects=1} 5 | def verify_file_path_noncompliant(): 6 | from flask import request 7 | file_path = request.args["file"] 8 | # Noncompliant: user input file path is not sanitized. 9 | file = open(file_path) 10 | file.close() 11 | # {/fact} 12 | 13 | 14 | # {fact rule=path-traversal@v1.0 defects=0} 15 | def verify_file_path_compliant(): 16 | from flask import request 17 | base_path = "/var/data/images/" 18 | file_path = request.args["file"] 19 | allowed_path = ["example_path1", "example_path2"] 20 | # Compliant: user input file path is sanitized. 21 | if file_path in allowed_path: 22 | file = open(base_path + file_path) 23 | file.close() 24 | # {/fact} 25 | -------------------------------------------------------------------------------- /src/python/detectors/pep8_recommendations/pep8_recommendations.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=pep8-recommendations@v1.0 defects=1} 5 | def readable_noncompliant(): 6 | # Noncompliant: violates PEP8 programming recommendations, 7 | # making it difficult to read. 8 | if not a is None: 9 | print(a) 10 | # {/fact} 11 | 12 | 13 | # {fact rule=pep8-recommendations@v1.0 defects=0} 14 | def readable_compliant(): 15 | # Compliant: follows the PEP8 programming recommendations, 16 | # improving readability. 17 | if a is not None: 18 | print(a) 19 | # {/fact} 20 | -------------------------------------------------------------------------------- /src/python/detectors/pytorch-disable-gradient-calculation/pytorch-disable-gradient-calculation.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=pytorch-disable-gradient-calculation@v1.0 defects=1} 5 | def disable_gradient_calculation_noncompliant(): 6 | import torch 7 | # Noncompliant: disables gradient calculation using `torch.no_grad()`. 8 | with torch.no_grad(): 9 | model.eval() 10 | # {/fact} 11 | 12 | 13 | # {fact rule=pytorch-disable-gradient-calculation@v1.0 defects=0} 14 | def disable_gradient_calculation_compliant(): 15 | import torch 16 | # Compliant: disables gradient calculation using `torch.inference_mode()`. 17 | with torch.inference_mode(): 18 | model.eval() 19 | # {/fact} 20 | -------------------------------------------------------------------------------- /src/python/detectors/pytorch_assign_in_place_mod/pytorch_assign_in_place_mod.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=pytorch-assign-in-place-mod@v1.0 defects=1} 5 | def pytorch_assign_in_place_mod_noncompliant(): 6 | import torch 7 | x = torch.randn([2, 2]) 8 | y = torch.randn([2, 2]) 9 | # Noncompliant: in-place method is called as part of assignment statement. 10 | z = x.add_(y) 11 | # {/fact} 12 | 13 | 14 | # {fact rule=pytorch-assign-in-place-mod@v1.0 defects=0} 15 | def pytorch_assign_in_place_mod_compliant(): 16 | import torch 17 | x = torch.randn([2, 2]) 18 | y = torch.randn([2, 2]) 19 | # Compliant: in-place method is not called as part of assignment statement. 20 | x.add_(y) 21 | z = x 22 | # {/fact} 23 | -------------------------------------------------------------------------------- /src/python/detectors/pytorch_avoid_softmax_with_nllloss_rule/pytorch_avoid_softmax_with_nllloss_rule.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=pytorch-avoid-softmax-with-nllloss-rule@v1.0 defects=1} 5 | def pytorch_avoid_softmax_with_nllloss_rule_noncompliant(): 6 | import math 7 | import torch 8 | import torch.nn as nn 9 | # Noncompliant: `softmax` output is used directly with `NLLLoss`. 10 | m = nn.functional.softmax(dim=1) 11 | loss = nn.NLLLoss() 12 | input = torch.randn(3, 5, requires_grad=True) 13 | target = torch.tensor([1, 0, 4]) 14 | output = loss(m(input), target) 15 | # {/fact} 16 | 17 | 18 | # {fact rule=pytorch-avoid-softmax-with-nllloss-rule@v1.0 defects=0} 19 | def pytorch_avoid_softmax_with_nllloss_rule_compliant(): 20 | import math 21 | import torch 22 | import torch.nn as nn 23 | # Compliant: `LogSoftmax` is used with `NLLLoss`. 24 | m = nn.LogSoftmax(dim=1) 25 | loss = nn.NLLLoss() 26 | input = torch.randn(3, 5, requires_grad=True) 27 | target = torch.tensor([1, 0, 4]) 28 | output = loss(m(input), target) 29 | # {/fact} 30 | -------------------------------------------------------------------------------- /src/python/detectors/pytorch_create_tensors_directly_on_device/pytorch_create_tensors_directly_on_device.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=pytorch-create-tensors-directly-on-device@v1.0 defects=1} 5 | def pytorch_create_tensors_directly_on_device_noncompliant(): 6 | import torch 7 | t = torch.ones(list(range(1, 11)), dtype=torch.float64) 8 | # Noncompliant: tensor is created on cpu and then moved to device. 9 | t.cuda() 10 | # {/fact} 11 | 12 | 13 | # {fact rule=pytorch-create-tensors-directly-on-device@v1.0 defects=0} 14 | def pytorch_create_tensors_directly_on_device_compliant(): 15 | import torch 16 | # Compliant: tensor is directly created on device. 17 | t = torch.tensor([1, 2, 3, 4], device="cuda") 18 | # {/fact} 19 | -------------------------------------------------------------------------------- /src/python/detectors/pytorch_data_loader_with_multiple_workers/pytorch_data_loader_with_multiple_workers.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=pytorch-data-loader-with-multiple-workers@v1.0 defects=1} 5 | def pytorch_data_loader_with_multiple_workers_noncompliant(): 6 | import torch 7 | from torch.utils.data import DataLoader 8 | import numpy as np 9 | sampler = InfomaxNodeRecNeighborSampler(g, [fanout] * (n_layers), 10 | device=device, full_neighbor=True) 11 | pr_node_ids = list(sampler.hetero_map.keys()) 12 | pr_val_ind = list(np.random.choice(len(pr_node_ids), 13 | int(len(pr_node_ids) * val_pct), 14 | replace=False)) 15 | pr_train_ind = list(set(list(np.arange(len(pr_node_ids)))) 16 | .difference(set(pr_val_ind))) 17 | 18 | # Noncompliant: num_workers value is 8 and native python 'list' 19 | # is used here to store the dataset. 20 | loader = DataLoader(dataset=pr_train_ind, 21 | batch_size=batch_size, 22 | collate_fn=sampler.sample_blocks, 23 | shuffle=True, 24 | num_workers=8) 25 | 26 | optimizer = torch.optim.Adam(model.parameters(), 27 | lr=lr, 28 | weight_decay=l2norm) 29 | 30 | # training loop 31 | print("start training...") 32 | 33 | for epoch in range(n_epochs): 34 | model.train() 35 | # {/fact} 36 | 37 | 38 | # {fact rule=pytorch-data-loader-with-multiple-workers@v1.0 defects=0} 39 | def pytorch_data_loader_with_multiple_workers_compliant(args): 40 | import torch.optim 41 | import torchvision.datasets as datasets 42 | # Data loading code 43 | traindir = os.path.join(args.data, 'train') 44 | valdir = os.path.join(args.data, 'val') 45 | normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], 46 | std=[0.229, 0.224, 0.225]) 47 | 48 | train_dataset = datasets.ImageFolder(traindir, imagenet_transforms) 49 | train_sampler = torch.utils.data.distributed\ 50 | .DistributedSampler(train_dataset) 51 | 52 | # Compliant: args.workers value is assigned to num_workers, 53 | # but native python 'list/dict' is not used here to store the dataset. 54 | train_loader = torch.utils.data.DataLoader(train_dataset, 55 | batch_size=args.batch_size, 56 | shuffle=(train_sampler is None), 57 | num_workers=args.workers, 58 | pin_memory=True, 59 | sampler=train_sampler) 60 | 61 | # {/fact} 62 | -------------------------------------------------------------------------------- /src/python/detectors/pytorch_miss_call_to_eval/pytorch_miss_call_to_eval.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=pytorch-miss-call-to-eval@v1.0 defects=1} 5 | def pytorch_miss_call_to_eval_noncompliant(model): 6 | import torch 7 | # Noncompliant: miss call to `eval()` after load. 8 | model.load_state_dict(torch.load("model.pth")) 9 | # {/fact} 10 | 11 | 12 | # {fact rule=pytorch-miss-call-to-eval@v1.0 defects=0} 13 | def pytorch_miss_call_to_eval_compliant(model): 14 | model.load_state_dict(torch.load("model.pth")) 15 | # Compliant: `eval()` is called after load. 16 | model.eval() 17 | # {/fact} 18 | -------------------------------------------------------------------------------- /src/python/detectors/pytorch_miss_call_to_zero_grad/pytorch_miss_call_to_zero_grad.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=pytorch-miss-call-to-zero-grad@v1.0 defects=1} 5 | def pytorch_miss_call_to_zero_grad_noncompliant( 6 | model, dataloader, criterion, optimizer, i_epoch): 7 | model.train() 8 | avg_loss = 0 9 | true_pos = 0 10 | true_neg = 0 11 | false_pos = 0 12 | false_neg = 0 13 | 14 | for i_batch, (data, offset, label) in enumerate(dataloader): 15 | output = model(data, offset) 16 | loss = criterion(output, label) 17 | # Noncompliant: gradients are not set to 18 | # zero before doing a backward pass. 19 | loss.backward() 20 | optimizer.step() 21 | 22 | avg_loss += loss.item() 23 | # train_error += torch.sum((output > 0) != label) 24 | true_pos += torch.sum((output >= 0).float() * label) 25 | false_pos += torch.sum((output >= 0).float() * (1.0 - label)) 26 | true_neg += torch.sum((output < 0).float() * (1.0 - label)) 27 | false_neg += torch.sum((output < 0).float() * label) 28 | 29 | print(f'\rEpoch {i_epoch},\ 30 | Training {i_batch+1:3d}/{len(dataloader):3d} batch, ' 31 | f'loss {loss.item():0.6f} ', end='') 32 | 33 | avg_loss /= len(dataloader) 34 | tpr = float(true_pos) / float(true_pos + false_neg) 35 | fpr = float(false_pos) / float(false_pos + true_neg) 36 | return avg_loss, tpr, fpr 37 | # {/fact} 38 | 39 | 40 | # {fact rule=pytorch-miss-call-to-zero-grad@v1.0 defects=0} 41 | def pytorch_miss_call_to_zero_grad_compliant( 42 | model, dataloader, criterion, optimizer, i_epoch): 43 | model.train() 44 | avg_loss = 0 45 | true_pos = 0 46 | true_neg = 0 47 | false_pos = 0 48 | false_neg = 0 49 | 50 | for i_batch, (data, offset, label) in enumerate(dataloader): 51 | output = model(data, offset) 52 | loss = criterion(output, label) 53 | # Compliant: gradients are set to zero before doing a backward pass. 54 | optimizer.zero_grad() 55 | loss.backward() 56 | optimizer.step() 57 | 58 | avg_loss += loss.item() 59 | # train_error += torch.sum((output > 0) != label) 60 | true_pos += torch.sum((output >= 0).float() * label) 61 | false_pos += torch.sum((output >= 0).float() * (1.0 - label)) 62 | true_neg += torch.sum((output < 0).float() * (1.0 - label)) 63 | false_neg += torch.sum((output < 0).float() * label) 64 | 65 | print(f'\rEpoch {i_epoch},\ 66 | Training {i_batch+1:3d}/{len(dataloader):3d} batch, ' 67 | f'loss {loss.item():0.6f} ', end='') 68 | 69 | avg_loss /= len(dataloader) 70 | tpr = float(true_pos) / float(true_pos + false_neg) 71 | fpr = float(false_pos) / float(false_pos + true_neg) 72 | return avg_loss, tpr, fpr 73 | # {/fact} 74 | -------------------------------------------------------------------------------- /src/python/detectors/pytorch_redundant_softmax/pytorch_redundant_softmax.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=pytorch-redundant-softmax@v1.0 defects=1} 5 | def pytorch_redundant_softmax_noncompliant(): 6 | import torch 7 | from torch import nn 8 | from torch.utils.data import DataLoader 9 | from torchvision import datasets 10 | from torchvision.transforms import ToTensor 11 | 12 | training_data = datasets.FashionMNIST( 13 | root="data", 14 | train=True, 15 | download=True, 16 | transform=ToTensor() 17 | ) 18 | 19 | test_data = datasets.FashionMNIST( 20 | root="data", 21 | train=False, 22 | download=True, 23 | transform=ToTensor() 24 | ) 25 | 26 | train_dataloader = DataLoader(training_data, batch_size=64) 27 | test_dataloader = DataLoader(test_data, batch_size=64) 28 | 29 | class NeuralNetwork(nn.Module): 30 | def __init__(self): 31 | super().__init__() 32 | self.flatten = nn.Flatten() 33 | self.linear_relu_stack = nn.Sequential( 34 | nn.Linear(28 * 28, 512), 35 | nn.ReLU(), 36 | nn.Linear(512, 512), 37 | nn.ReLU(), 38 | nn.Linear(512, 10) 39 | ) 40 | 41 | def forward(self, x): 42 | x = self.flatten(x) 43 | logits = self.linear_relu_stack(x) 44 | # Noncompliant: Softmax used with CrossEntropyLoss. 45 | logits = nn.functional.softmax(logits) 46 | return logits 47 | 48 | model = NeuralNetwork() 49 | 50 | def train_loop(dataloader, model, loss_fn, optimizer): 51 | size = len(dataloader.dataset) 52 | for batch, (x, y) in enumerate(dataloader): 53 | # Compute prediction and loss 54 | pred = model(x) 55 | loss = loss_fn(pred, y) 56 | 57 | # Backpropagation 58 | optimizer.zero_grad() 59 | loss.backward() 60 | optimizer.step() 61 | 62 | if batch % 100 == 0: 63 | loss, current = loss.item(), (batch + 1) * len(x) 64 | print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") 65 | 66 | def test_loop(dataloader, model, loss_fn): 67 | size = len(dataloader.dataset) 68 | num_batches = len(dataloader) 69 | test_loss, correct = 0, 0 70 | 71 | with torch.no_grad(): 72 | for x, y in dataloader: 73 | pred = model(x) 74 | test_loss += loss_fn(pred, y).item() 75 | correct += (pred.argmax(1) == y).type(torch.float).sum().item() 76 | 77 | test_loss /= num_batches 78 | correct /= size 79 | print(f"Test Error: \n Accuracy: {(100 * correct):>0.1f}%, " 80 | f"Avg loss: {test_loss:>8f} \n") 81 | 82 | loss_fn = nn.CrossEntropyLoss() 83 | learning_rate = 0.05 84 | optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) 85 | 86 | epochs = 10 87 | for t in range(epochs): 88 | print(f"Model - Epoch {t + 1}\n-------------------------------") 89 | train_loop(train_dataloader, model, loss_fn, optimizer) 90 | test_loop(test_dataloader, model, loss_fn) 91 | 92 | print("Done!") 93 | # {/fact} 94 | 95 | 96 | # {fact rule=pytorch-redundant-softmax@v1.0 defects=0} 97 | def pytorch_redundant_softmax_compliant(): 98 | import torch 99 | import torch.nn as nn 100 | from transformers import BertModel, BertForSequenceClassification 101 | import default_constants 102 | 103 | class BERT(nn.Module): 104 | def __init__(self, tokenizer, bert_variant=default_constants.BERT): 105 | super(BERT, self).__init__() 106 | 107 | self.num_labels = default_constants.num_labels 108 | self.hidden_dim = default_constants.hidden_dim 109 | self.dropout_prob = default_constants.dropout_prob 110 | 111 | self.bert = BertModel.from_pretrained(bert_variant) 112 | self.bert.resize_token_embeddings(len(tokenizer)) 113 | 114 | self.dropout = nn.Dropout(self.dropout_prob).cuda() 115 | self.classifier = nn.Linear( 116 | self.hidden_dim, self.num_labels).cuda() 117 | torch.nn.init.xavier_uniform(self.classifier.weight) 118 | 119 | def forward( 120 | self, 121 | text, 122 | labels, 123 | attention_mask=None, 124 | token_type_ids=None 125 | ): 126 | outputs = self.bert( 127 | text, 128 | attention_mask=attention_mask, 129 | token_type_ids=token_type_ids 130 | ) 131 | 132 | pooled_output = outputs[1] 133 | 134 | pooled_output = self.dropout(pooled_output) 135 | logits = self.classifier(pooled_output) 136 | 137 | # Compliant: Softmax is not used with CrossEntropyLoss. 138 | loss_fct = nn.CrossEntropyLoss(weight=torch.Tensor( 139 | default_constants.class_weight)).cuda(default_constants.DEVICE) 140 | loss = loss_fct(logits.view(-1, self.num_labels), labels.view(-1)) 141 | 142 | return ( 143 | loss, 144 | logits, 145 | ) 146 | # {/fact} 147 | -------------------------------------------------------------------------------- /src/python/detectors/pytorch_sigmoid_before_bceloss/pytorch_sigmoid_before_bceloss.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=pytorch-sigmoid-before-bceloss@v1.0 defects=1} 5 | def pytorch_sigmoid_before_bceloss_noncompliant(): 6 | import torch 7 | import torch.nn as nn 8 | # Noncompliant: `Sigmoid` layer followed by `BCELoss` 9 | # is not numerically robust. 10 | m = nn.Sigmoid() 11 | loss = nn.BCELoss() 12 | 13 | input = torch.randn(3, requires_grad=True) 14 | target = torch.empty(3).random_(2) 15 | 16 | output = loss(m(input), target) 17 | output.backward() 18 | # {/fact} 19 | 20 | 21 | # {fact rule=pytorch-sigmoid-before-bceloss@v1.0 defects=0} 22 | def pytorch_sigmoid_before_bceloss_compliant(): 23 | import torch 24 | import torch.nn as nn 25 | # Compliant: `BCEWithLogitsLoss` function integrates a `Sigmoid` 26 | # layer and the `BCELoss` into one class 27 | # and is numerically robust. 28 | loss = nn.BCEWithLogitsLoss() 29 | 30 | input = torch.randn(3, requires_grad=True) 31 | target = torch.empty(3).random_(2) 32 | 33 | output = loss(input, target) 34 | output.backward() 35 | # {/fact} 36 | -------------------------------------------------------------------------------- /src/python/detectors/pytorch_use_nondeterministic_algorithm/pytorch_use_nondeterministic_algorithm.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | # {fact rule=pytorch-use-nondeterministic-algorithm@v1.0 defects=1} 4 | def pytorch_use_nondeterministic_algorithm_noncompliant(): 5 | import torch 6 | # Noncompliant: `torch.bmm` doesn't use deterministic algorithms 7 | # by default. 8 | torch.bmm(torch.randn(2, 2, 2).to_sparse().cuda(), 9 | torch.randn(2, 2, 2).cuda()) 10 | # {/fact} 11 | 12 | 13 | # {fact rule=pytorch-use-nondeterministic-algorithm@v1.0 defects=0} 14 | def pytorch_use_nondeterministic_algorithm_compliant(): 15 | import torch 16 | # Compliant: configure `torch.bmm` to use deterministic algorithms. 17 | torch.use_deterministic_algorithms(True) 18 | torch.bmm(torch.randn(2, 2, 2).to_sparse().cuda(), 19 | torch.randn(2, 2, 2).cuda()) 20 | # {/fact} 21 | -------------------------------------------------------------------------------- /src/python/detectors/resource_leak/resource_leak.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=resource-leak@v1.0 defects=1} 5 | def read_file_noncompliant(filename): 6 | file = open(filename, 'r') 7 | # Noncompliant: method returns without properly closing the file. 8 | return file.readlines() 9 | # {/fact} 10 | 11 | 12 | # {fact rule=resource-leak@v1.0 defects=0} 13 | def read_file_compliant(filename): 14 | # Compliant: file is declared using a `with` statement. 15 | with open(filename, 'r') as file: 16 | return file.readlines() 17 | # {/fact} 18 | -------------------------------------------------------------------------------- /src/python/detectors/s3_partial_encrypt_cdk/s3_partial_encrypt_cdk_compliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=s3-partial-encrypt-cdk@v1.0 defects=0} 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_s3 as s3 7 | 8 | 9 | class S3PartialEncrypt(cdk.Stack): 10 | 11 | def s3_partial_encrypt_compliant(self): 12 | # Compliant: S3_MANAGED encryption specified 13 | bucket = s3.Bucket(self, 's3-bucket', 14 | encryption=s3.BucketEncryption.S3_MANAGED) 15 | # {/fact} 16 | -------------------------------------------------------------------------------- /src/python/detectors/s3_partial_encrypt_cdk/s3_partial_encrypt_cdk_noncompliant.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=s3-partial-encrypt-cdk@v1.0 defects=1} 5 | import aws_cdk as cdk 6 | from aws_cdk import aws_s3 as s3 7 | 8 | 9 | class S3PartialEncrypt(cdk.Stack): 10 | 11 | def s3_partial_encrypt_noncompliant(self): 12 | # Noncompliant: No encryption specified 13 | bucket = s3.Bucket(self, 's3-bucket-bad') 14 | # {/fact} 15 | -------------------------------------------------------------------------------- /src/python/detectors/s3_verify_bucket_owner/s3_verify_bucket_owner.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=s3-verify-bucket-owner@v1.0 defects=1} 5 | def verify_s3bucket_owner_noncompliant(event): 6 | import boto3 7 | client = boto3.client('s3') 8 | # Noncompliant: missing S3 bucket owner condition 9 | # (ExpectedSourceBucketOwner). 10 | client.copy_object( 11 | Bucket=event["bucket"], 12 | CopySource=f"{event['bucket']}/{event['key']}", 13 | Key=event["key"], 14 | ExpectedBucketOwner=event["owner"], 15 | ) 16 | # {/fact} 17 | 18 | 19 | # {fact rule=s3-verify-bucket-owner@v1.0 defects=0} 20 | def verify_s3bucket_owner_compliant(event): 21 | import boto3 22 | client = boto3.client('s3') 23 | # Compliant: sets the S3 bucket owner condition(ExpectedSourceBucketOwner). 24 | client.copy_object( 25 | Bucket=event["bucket"], 26 | CopySource=f"{event['bucket']}/{event['key']}", 27 | Key=event["key"], 28 | ExpectedBucketOwner=event["owner"], 29 | ExpectedSourceBucketOwner=event["owner2"] 30 | ) 31 | # {/fact} 32 | -------------------------------------------------------------------------------- /src/python/detectors/semaphore_overflow_prevention/semaphore_overflow_prevention.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=semaphore-overflow-prevention@v1.0 defects=1} 5 | def post_tasks_noncompliant(jobs, es_url): 6 | import multiprocessing 7 | import requests 8 | jobs = multiprocessing.JoinableQueue() 9 | while True: 10 | try: 11 | # Noncompliant: fails to call JoinableQueue.task_done() 12 | # for each task removed from the JoinableQueue. 13 | image, image_name, tag = jobs.get() 14 | formatted_es_url = es_url.format(image_name) 15 | files = {'file': image.content, 'tag': tag} 16 | r = requests.post(formatted_es_url, files=files) 17 | finally: 18 | print("Task Done!!") 19 | # {/fact} 20 | 21 | 22 | # {fact rule=semaphore-overflow-prevention@v1.0 defects=0} 23 | def post_tasks_compliant(jobs, es_url): 24 | import multiprocessing 25 | import requests 26 | jobs = multiprocessing.JoinableQueue() 27 | while True: 28 | try: 29 | image, image_name, tag = jobs.get() 30 | formatted_es_url = es_url.format(image_name) 31 | files = {'file': image.content, 'tag': tag} 32 | r = requests.post(formatted_es_url, files=files) 33 | finally: 34 | # Compliant: calls JoinableQueue.task_done() 35 | # for each task removed from the JoinableQueue. 36 | jobs.task_done() 37 | # {/fact} 38 | -------------------------------------------------------------------------------- /src/python/detectors/sns_no_bind_subscribe_publish_rule/sns_no_bind_subscribe_publish_rule.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=sns-no-bind-subscribe-publish-rule@v1.0 defects=1} 5 | def sns_publish_noncompliant(self, sqs_arn: str, topic_arn: str) -> None: 6 | import boto3 7 | session = boto3.Session() 8 | sns_client = session.client('sns') 9 | sns_client.subscribe(TopicArn=topic_arn, Protocol='sqs', 10 | Endpoint=sqs_arn, 11 | ReturnSubscriptionArn=True) 12 | 13 | # Noncompliant: incorrect binding of SNS publish operations 14 | # with 'subscribe' or 'create_topic' operations. 15 | sns_client.publish(TopicArn=topic_arn, 16 | Message='test message for SQS', 17 | MessageAttributes={'attr1': { 18 | 'DataType': 'String', 19 | 'StringValue': "short_uid" 20 | } 21 | } 22 | ) 23 | # {/fact} 24 | 25 | 26 | # {fact rule=sns-no-bind-subscribe-publish-rule@v1.0 defects=0} 27 | def sns_publish_compliant(self, sqs_arn: str, topic_arn: str) -> None: 28 | import boto3 29 | session = boto3.Session() 30 | sns_client = session.client('sns') 31 | response = sns_client.subscribe(TopicArn=topic_arn, Protocol='sqs', 32 | Endpoint=sqs_arn, 33 | ReturnSubscriptionArn=True) 34 | # Compliant: avoids binding of SNS publish operations 35 | # with 'subscribe' or 'create_topic' operations. 36 | return response 37 | # {/fact} 38 | -------------------------------------------------------------------------------- /src/python/detectors/sns_set_return_subscription_arn/sns_set_return_subscription_arn.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=sns-set-return-subscription-arn@v1.0 defects=1} 5 | def set_return_subscription_noncompliant(self, 6 | sqs_arn: str, 7 | topic_arn: str) -> None: 8 | import botocore 9 | session = botocore.session.get_session() 10 | sns_client = session.create_client('sns', 'us-west-2') 11 | # Noncompliant: fails to set the 'ReturnSubscriptionArn' argument to 12 | # 'True' while returning the subscription ARN. 13 | sns_client.subscribe(TopicArn=topic_arn, Protocol='sqs', 14 | Endpoint=sqs_arn) 15 | # {/fact} 16 | 17 | 18 | # {fact rule=sns-set-return-subscription-arn@v1.0 defects=0} 19 | def set_return_subscription_compliant(self, 20 | sqs_arn: str, 21 | topic_arn: str) -> None: 22 | import botocore 23 | session = botocore.session.get_session() 24 | sns_client = session.create_client('sns', 'us-west-2') 25 | # Compliant: sets the 'ReturnSubscriptionArn' argument to 'True' 26 | # while returning the subscription ARN. 27 | sns_client.subscribe(TopicArn=topic_arn, Protocol='sqs', 28 | Endpoint=sqs_arn, ReturnSubscriptionArn=True) 29 | # {/fact} 30 | -------------------------------------------------------------------------------- /src/python/detectors/sns_unauthenticated_unsubscribe/sns_unauthenticated_unsubscribe.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=sns-unauthenticated-unsubscribe@v1.0 defects=1} 5 | def authenticate_on_subscribe_noncompliant(self, event) -> None: 6 | import boto3 7 | subscriptions_failed = 0 8 | for record in event["Records"]: 9 | message = record["body"] 10 | if message["Type"] == "SubscriptionConfirmation": 11 | try: 12 | topic_arn = message["TopicArn"] 13 | token = message["Token"] 14 | sns_client = boto3.client("sns", 15 | region_name=topic_arn.split(":")[3]) 16 | # Noncompliant: fails to set the 'AuthenticateOnUnsubscribe' 17 | # argument to 'True' while confirming an SNS subscription. 18 | sns_client.confirm_subscription(TopicArn=topic_arn, 19 | Token=token) 20 | except Exception: 21 | subscriptions_failed += 1 22 | # {/fact} 23 | 24 | 25 | # {fact rule=sns-unauthenticated-unsubscribe@v1.0 defects=0} 26 | def authenticate_on_subscribe_compliant(self, event) -> None: 27 | import boto3 28 | subscriptions_failed = 0 29 | for record in event["Records"]: 30 | message = record["body"] 31 | if message["Type"] == "SubscriptionConfirmation": 32 | try: 33 | topic_arn = message["TopicArn"] 34 | token = message["Token"] 35 | sns_client = boto3.client("sns", 36 | region_name=topic_arn.split(":")[3]) 37 | # Compliant: sets the 'AuthenticateOnUnsubscribe' argument to 38 | # 'True' while confirming an SNS subscription. 39 | sns_client.confirm_subscription( 40 | TopicArn=topic_arn, 41 | Token=token, 42 | AuthenticateOnUnsubscribe='True') 43 | except Exception: 44 | subscriptions_failed += 1 45 | # {/fact} 46 | -------------------------------------------------------------------------------- /src/python/detectors/socket_close_platform_compatibility/socket_close_platform_compatibility.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=socket-close-platform-compatibility@v1.0 defects=1} 5 | def create_socket_noncompliant(samplehost, sampleport, samplebuffersize): 6 | import socket 7 | socket.socket.settimeout(10.0) 8 | socket = socket.socket() 9 | socket.connect((samplehost, sampleport)) 10 | print(socket.recv(samplebuffersize)) 11 | # Noncompliant: socket.shutdown is not called before closing the socket. 12 | socket.close() 13 | # {/fact} 14 | 15 | 16 | # {fact rule=socket-close-platform-compatibility@v1.0 defects=0} 17 | def create_socket_compliant(samplehost, sampleport, samplebuffersize): 18 | import socket 19 | socket.socket.settimeout(10.0) 20 | socket = socket.socket() 21 | socket.connect((samplehost, sampleport)) 22 | try: 23 | print(socket.recv(samplebuffersize)) 24 | finally: 25 | # Compliant: socket.shutdown is called before closing the socket. 26 | socket.shutdown(socket.SHUT_WR) 27 | socket.close() 28 | # {/fact} 29 | -------------------------------------------------------------------------------- /src/python/detectors/socket_connection_timeout/socket_connection_timeout.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=socket-connection-timeout@v1.0 defects=1} 5 | def create_socket_noncompliant(samplehost, sampleport, samplebuffersize): 6 | import socket 7 | # Noncompliant: socket timeout is not set. 8 | socket = socket.create_connection((samplehost, sampleport)) 9 | try: 10 | print(socket.recv(samplebuffersize)) 11 | finally: 12 | socket.close() 13 | # {/fact} 14 | 15 | 16 | # {fact rule=socket-connection-timeout@v1.0 defects=0} 17 | def create_socket_compliant(samplehost, sampleport, samplebuffersize): 18 | import socket 19 | # Compliant: socket timeout is set. 20 | socket = socket.create_connection((samplehost, sampleport), timeout=10) 21 | try: 22 | print(socket.recv(samplebuffersize)) 23 | finally: 24 | socket.close() 25 | # {/fact} 26 | -------------------------------------------------------------------------------- /src/python/detectors/sql_injection/sql_injection.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # {fact rule=sql-injection@v1.0 defects=1} 5 | def execute_query_noncompliant(request): 6 | import sqlite3 7 | name = request.GET.get("name") 8 | query = "SELECT * FROM Users WHERE name = " + name + ";" 9 | with sqlite3.connect("example.db") as connection: 10 | cursor = connection.cursor() 11 | # Noncompliant: user input is used without sanitization. 12 | cursor.execute(query) 13 | connection.commit() 14 | connection.close() 15 | # {/fact} 16 | 17 | 18 | # {fact rule=sql-injection@v1.0 defects=0} 19 | def execute_query_compliant(request): 20 | import re 21 | import sqlite3 22 | name = request.GET.get("name") 23 | query = "SELECT * FROM Users WHERE name = " 24 | + re.sub('[^a-zA-Z]+', '', name) + ";" 25 | with sqlite3.connect("example.db") as connection: 26 | cursor = connection.cursor() 27 | # Compliant: user input is sanitized before use. 28 | cursor.execute(query) 29 | connection.commit() 30 | connection.close() 31 | # {/fact} 32 | -------------------------------------------------------------------------------- /src/python/detectors/stack_trace_exposure/stack_trace_exposure.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from flask import Flask, jsonify 5 | from werkzeug.exceptions import HTTPException 6 | import traceback 7 | 8 | app_flask = Flask(__name__) 9 | 10 | 11 | # {fact rule=stack-trace-exposure@v1.0 defects=1} 12 | @app_flask.route('/noncompliant/