diff --git a/.eslintrc.json b/.eslintrc.json index e19ce34..5bbebc5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,16 +1,14 @@ { - "env": { - "browser": true, - "node": true, - "es2021": true - }, - "extends": "eslint:recommended", - "overrides": [ - ], - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "rules": { - } + "env": { + "browser": true, + "es2021": true, + "node": true + }, + "extends": "eslint:recommended", + "overrides": [], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": {} } diff --git a/.gitignore b/.gitignore index a42e42f..049e205 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ ash_output *.bak src/automated_security_helper/models/*generated* !tests/test_data/aggregated_results.txt +refactoring/ +tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/cdk_nag_results_*/ +test_output.json +tests/pytest-temp/ ### macOS ### # General @@ -94,6 +98,7 @@ coverage.xml .hypothesis/ .pytest_cache/ cover/ +test-results/ # Translations *.mo diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5a91530 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: local + hooks: + - id: generate-schemas + name: Generate ASH JSON schemas + entry: poetry run schemagen + language: system + pass_filenames: false + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: pretty-format-json + args: + - "--autofix" + - "--indent=2" + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.11.2 + hooks: + # Run the linter. + - id: ruff + args: [--fix] + # Run the formatter. + - id: ruff-format diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..635917e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,43 @@ +{ + "configurations": [ + { + "args": [ + "--source", + "${workspaceFolder}", + "--output", + "${workspaceFolder}/ash_output", + "--verbose", + "--config", + "${workspaceFolder}/ash.yaml", + "--strategy", + "parallel", + "--scanners", + "cdknag" + ], + "console": "integratedTerminal", + "name": "ASH: Test Orchestrator", + "program": "./src/automated_security_helper/orchestrator.py", + "request": "launch", + "type": "debugpy" + }, + { + "args": [], + "console": "integratedTerminal", + "name": "ASH: Test CDK Nag Headless Wrapper", + "program": "./src/automated_security_helper/utils/cdk_nag_wrapper.py", + "request": "launch", + "type": "debugpy" + }, + { + "console": "integratedTerminal", + "env": { + "_PYTEST_RAISE": "1" + }, + "justMyCode": true, + "name": "Debug Tests", + "request": "launch", + "type": "debugpy" + } + ], + "version": "0.2.0" +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a22a78d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false +} diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..79e5970 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,95 @@ +# Local Development Setup Guide + +This guide will help you set up your local development environment for the Automated Security Helper project. + +## Prerequisites + +- Python 3.10 or later +- Poetry (Python package manager) + +## Setting up Poetry + +1. Install Poetry on your system: +```bash +curl -sSL https://install.python-poetry.org | python3 - +``` + +2. Verify Poetry installation: +```bash +poetry --version +``` + +## Project Setup + +1. Clone the repository: +```bash +git clone https://github.com/awslabs/automated-security-helper.git +cd automated-security-helper +``` + +2. Install project dependencies: +```bash +poetry install +``` +This command will: +- Create a virtual environment +- Install all dependencies from pyproject.toml +- Set up the project in development mode + +3. Activate the virtual environment: +```bash +poetry shell +``` + +## Testing + +Run the test suite: +```bash +pytest +``` + +## Development Commands + +- Format and lint code: +```bash +poetry run ruff . +``` + +- Run a specific script: +```bash +poetry run asharp +``` + +## Project Dependencies + +The project uses the following key dependencies: +- Python +- bandit +- checkov +- pydantic + +Development dependencies include: +- ruff +- pytest +- pytest-cov + +## Troubleshooting + +If you encounter any issues: + +1. Verify your Python version matches the required version (3.10+): +```bash +python --version +``` + +2. Try cleaning and rebuilding the environment: +```bash +poetry env remove python +poetry install +``` + +3. Update Poetry and dependencies: +```bash +poetry self update +poetry update +``` diff --git a/Dockerfile b/Dockerfile index 84bb7a5..d76a0af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,7 +57,7 @@ RUN apt-get update && \ rm -rf /var/lib/apt/lists/* # -# Install nodejs@18 using latest recommended method +# Install nodejs@20 using latest recommended method # RUN set -uex; \ apt-get update; \ @@ -65,13 +65,14 @@ RUN set -uex; \ mkdir -p /etc/apt/keyrings; \ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; \ - NODE_MAJOR=18; \ + NODE_MAJOR=20; \ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" \ > /etc/apt/sources.list.d/nodesource.list; \ apt-get -qy update; \ apt-get -qy install nodejs; + # -# Install and upgrade pip +# Python (no-op other than updating pip --- Python deps managed via Poetry @ pyproject.toml) # RUN wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py RUN python3 -m pip install --no-cache-dir --upgrade pip @@ -83,24 +84,17 @@ RUN git clone https://github.com/awslabs/git-secrets.git && \ cd git-secrets && \ make install -# -# Python -# -RUN python3 -m pip install --no-cache-dir \ - bandit \ - nbconvert \ - jupyterlab # # YAML (Checkov, cfn-nag) # RUN echo "gem: --no-document" >> /etc/gemrc && \ - python3 -m pip install checkov pathspec && \ gem install cfn-nag # -# JavaScript: (no-op --- node is already installed in the image, nothing else needed) +# JavaScript: # +RUN npm install -g npm pnpm yarn # # Grype/Syft/Semgrep - Also sets default location env vars for root user for CI compat @@ -115,8 +109,6 @@ RUN curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | RUN curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | \ sh -s -- -b /usr/local/bin -RUN python3 -m pip install semgrep - RUN set -uex; if [[ "${OFFLINE}" == "YES" ]]; then \ grype db update && \ mkdir -p ${SEMGREP_RULES_CACHE_DIR} && \ @@ -145,25 +137,22 @@ RUN mkdir -p /src && \ # # Install CDK Nag stub dependencies # -# Update NPM to latest COPY ./utils/cdk-nag-scan /ash/utils/cdk-nag-scan/ # Limit memory size available for Node to prevent segmentation faults during npm install ENV NODE_OPTIONS=--max_old_space_size=512 -RUN npm install -g npm pnpm yarn && \ - cd /ash/utils/cdk-nag-scan && \ +RUN cd /ash/utils/cdk-nag-scan && \ npm install --quiet # # COPY ASH source to /ash instead of / to isolate # -COPY ./utils/cfn-to-cdk /ash/utils/cfn-to-cdk/ +COPY --from=poetry-reqs /src/dist/*.whl . +COPY ./pyproject.toml /ash/pyproject.toml +RUN python3 -m pip install *.whl && rm *.whl + COPY ./utils/*.* /ash/utils/ COPY ./appsec_cfn_rules /ash/appsec_cfn_rules/ COPY ./ash-multi /ash/ash -COPY ./pyproject.toml /ash/pyproject.toml - -COPY --from=poetry-reqs /src/dist/*.whl . -RUN python3 -m pip install *.whl && rm *.whl # # Make sure the ash script is executable @@ -241,4 +230,4 @@ HEALTHCHECK --interval=12s --timeout=12s --start-period=30s \ CMD type ash || exit 1 ENTRYPOINT [ ] -CMD [ "ash" ] +CMD [ "ashv3", "--verbose", "--source", "/src", "--output","/out", "--strategy", "sequential", "--scanners", "bandit,cdknag" ] diff --git a/LICENSE b/LICENSE index 7a4a3ea..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -199,4 +199,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/NOTICE b/NOTICE index f48b352..616fc58 100644 --- a/NOTICE +++ b/NOTICE @@ -1 +1 @@ -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/appsec_cfn_rules/KeyPairAsCFnParameterRule.rb b/appsec_cfn_rules/KeyPairAsCFnParameterRule.rb index 6aa67dd..b207b3c 100755 --- a/appsec_cfn_rules/KeyPairAsCFnParameterRule.rb +++ b/appsec_cfn_rules/KeyPairAsCFnParameterRule.rb @@ -18,11 +18,11 @@ def rule_id def audit_impl(cfn_model) parameters = cfn_model.parameters.select do |name, properties| - - # TODO: find way to preserve the line number from properties.type["line"] + + # TODO: find way to preserve the line number from properties.type["line"] properties.type["value"] == "AWS::EC2::KeyPair::KeyName" end parameters.values.map(&:id) end -end \ No newline at end of file +end diff --git a/appsec_cfn_rules/ResourcePolicyStarAccessVerbPolicyRule.rb b/appsec_cfn_rules/ResourcePolicyStarAccessVerbPolicyRule.rb index de84b12..181c82e 100755 --- a/appsec_cfn_rules/ResourcePolicyStarAccessVerbPolicyRule.rb +++ b/appsec_cfn_rules/ResourcePolicyStarAccessVerbPolicyRule.rb @@ -39,4 +39,4 @@ def audit_impl(cfn_model) logical_resource_ids end -end \ No newline at end of file +end diff --git a/appsec_cfn_rules/StarResourceAccessPolicyRule.rb b/appsec_cfn_rules/StarResourceAccessPolicyRule.rb index d87487f..e931a95 100755 --- a/appsec_cfn_rules/StarResourceAccessPolicyRule.rb +++ b/appsec_cfn_rules/StarResourceAccessPolicyRule.rb @@ -38,4 +38,4 @@ def audit_impl(cfn_model) logical_resource_ids end -end \ No newline at end of file +end diff --git a/appsec_cfn_rules/beta/PasswordAsCFnParameterRule.rb b/appsec_cfn_rules/beta/PasswordAsCFnParameterRule.rb index 586b491..9eaa1ee 100755 --- a/appsec_cfn_rules/beta/PasswordAsCFnParameterRule.rb +++ b/appsec_cfn_rules/beta/PasswordAsCFnParameterRule.rb @@ -18,8 +18,8 @@ def rule_id def audit_impl(cfn_model) parameters = cfn_model.parameters.select do |name, properties| - - # TODO: find way to preserve the line number from properties.type["line"] + + # TODO: find way to preserve the line number from properties.type["line"] name.downcase.include?("password") and properties.type["value"] == "String" and properties.allowedValues != [true, false] and @@ -28,4 +28,4 @@ def audit_impl(cfn_model) parameters.values.map(&:id) end -end \ No newline at end of file +end diff --git a/ash b/ash index 8b0ce7c..ed54d6c 100755 --- a/ash +++ b/ash @@ -187,7 +187,7 @@ else ${MOUNT_OUTPUT_DIR} \ ${DOCKER_RUN_EXTRA_ARGS} \ ${ASH_IMAGE_NAME} \ - ash \ + ashv3 \ --source-dir /src \ ${OUTPUT_DIR_OPTION} \ $ASH_ARGS diff --git a/ash-multi b/ash-multi index 67968a4..9272e32 100755 --- a/ash-multi +++ b/ash-multi @@ -104,7 +104,7 @@ get_all_files() { pushd . >/dev/null 2>&1 # cd to the source directory as a starting point cd "${_ASH_SOURCE_DIR}" - src_files=$(python "${_ASH_UTILS_LOCATION}/get-scan-set.py" --source $(pwd) --output "${_ASH_OUTPUT_DIR}" ${ASH_SCANSET_ARGS}) + src_files=$(get-scan-set --source $(pwd) --output "${_ASH_OUTPUT_DIR}" ${ASH_SCANSET_ARGS}) popd >/dev/null 2>&1 all_files+=( "$src_files" ) @@ -202,6 +202,12 @@ validate_input() { rm -rf "${OUTPUT_DIR}/work" fi mkdir -p "${OUTPUT_DIR}/work" + + if [ -d "${OUTPUT_DIR}/scanners" ]; then + rm -rf "${OUTPUT_DIR}/scanners" + fi + mkdir -p "${OUTPUT_DIR}/scanners" + OUTPUT_DIR=$(cd "${OUTPUT_DIR}"; pwd) # Transform any relative path to absolute CFNRULES_LOCATION=$(cd "${CFNRULES_LOCATION}"; pwd) # Transform any relative path to absolute UTILS_LOCATION=$(cd "${UTILS_LOCATION}"; pwd) # Transform any relative path to absolute @@ -247,10 +253,7 @@ run_security_check() { # Invoke the resolved scanner script bash -C ${FULL_SCANNER_SCRIPT_PATH} else - echo -e "${LPURPLE}Found ${CYAN}one or more${LPURPLE} of: [ ${RED}""${ITEMS_TO_SCAN[*]}""${LPURPLE} ] items in source dir,${NC} ${CYAN}running${GREEN} ${DOCKERFILE_TO_EXECUTE} ...${NC}" - ${OCI_RUNNER} build -t "${RUNTIME_CONTAINER_NAME}" -f "${DOCKERFILE_LOCATION}"/"${DOCKERFILE_TO_EXECUTE}" ${DOCKER_EXTRA_ARGS} "${SOURCE_DIR}" > /dev/null - set +e # the scan will fail the command if it finds any finding. we don't want it to stop our script execution - ${OCI_RUNNER} run --name "${RUNTIME_CONTAINER_NAME}" -v "${CFNRULES_LOCATION}":/cfnrules:ro -v "${UTILS_LOCATION}":/utils:ro -v "${SOURCE_DIR}":/src:ro -v "${OUTPUT_DIR}":/out:rw "${RUNTIME_CONTAINER_NAME}" + echo -e "${$YELLOW}Multi-container architecture is no longer supported!" fi # # capture the return code of the command invoked through docker @@ -331,7 +334,6 @@ CFN_EXTENSIONS=("yaml" "yml" "json" "template") JS_EXTENSIONS=("js" "jsx" "ts" "tsx") GRYPE_EXTENSIONS=("js" "jsx" "ts" "tsx" "py" "java" "go" "cs" "sh" "war" "jar") -DOCKERFILE_LOCATION="$(dirname "${BASH_SOURCE[0]}")"/"helper_dockerfiles" UTILS_LOCATION="$(dirname "${BASH_SOURCE[0]}")"/"utils" CFNRULES_LOCATION="$(dirname "${BASH_SOURCE[0]}")"/"appsec_cfn_rules" @@ -501,7 +503,7 @@ IFS=$'\n' # Support directories with spaces, make the loop iterate over newline pushd . >/dev/null 2>&1 cd "${SOURCE_DIR}" # We do not include --output here explicitly to only yield the relative path -for zipfile in $(python "${_ASH_UTILS_LOCATION}/get-scan-set.py" --source . | grep '\.zip$'); +for zipfile in $(get-scan-set --source . | grep '\.zip$'); do tgt_dir=$(dirname "${OUTPUT_DIR}"/work/"${zipfile:2}")/"$(basename "${zipfile%.*}")" mkdir -p "${tgt_dir}" @@ -648,10 +650,10 @@ if [[ -n "${AGGREGATED_RESULTS_REPORT_FILENAME}" && -n "${OUTPUT_DIR}" && -f "${ fi # if an extension was not found, no report file will be in place, so skip the final report -if [[ $(find "${OUTPUT_DIR}/work" -iname "*_report_result.txt" | wc -l | awk '{print $1}') -gt 0 ]]; +if [[ $(find "${OUTPUT_DIR}/scanners/results" -iname "*_report_result.txt" | wc -l | awk '{print $1}') -gt 0 ]]; then # Aggregate the results output files - for result in "${OUTPUT_DIR}"/work/*_report_result.txt; + for result in "${OUTPUT_DIR}"/scanners/results/*_report_result.txt; do echo "#############################################" >> "${OUTPUT_DIR}"/"${AGGREGATED_RESULTS_REPORT_FILENAME}" echo "Start of ${result}" >> "${OUTPUT_DIR}"/"${AGGREGATED_RESULTS_REPORT_FILENAME}" diff --git a/ash.yaml b/ash.yaml new file mode 100644 index 0000000..fe641ae --- /dev/null +++ b/ash.yaml @@ -0,0 +1,89 @@ +# yaml-language-server: $schema=./src/automated_security_helper/schemas/ASHConfig.json +project_name: automated-security-helper +ash_image_tag: 'awslabs/ash:v3.0.0' +# Build overrides to customize the container build. +# Plugins and other settings set during build cannot be overridden at scan time unless +# they are explicitly allowed here in the build configuration. +# This only affects containerized ASH runs. +build: + mode: ASH_MODE_OFFLINE # Build image in offline mode + tool_install_scripts: + trivy: + - wget https://github.com/aquasecurity/trivy/releases/download/v0.61.0/trivy_0.61.0_Linux-64bit.deb + - dpkg -i trivy_0.61.0_Linux-64bit.deb + custom_scanners: + sast: + - name: trivy-sast + command: trivy + args: ["fs"] + output_format: sarif + output_stream: stdout + format_arg: '--format' + format_arg_value: sarif + format_arg_position: after_args + get_tool_version_command: + - trivy + - '--version' + scan_path_arg_position: after_args + type: SAST + sbom: + - name: trivy-sbom + command: trivy + args: ["fs"] + output_format: cyclonedx + output_stream: stdout + format_arg: '--format' + format_arg_value: cyclonedx + format_arg_position: after_args + get_tool_version_command: + - trivy + - '--version' + scan_path_arg_position: after_args + type: SBOM +# General scan settings +fail_on_findings: true +ignore_paths: + - 'tests/**' +output_dir: ash_output +# SAST scanner settings +sast: + output_formats: + - json + - csv + - junitxml + - html + scanners: + # Default scanners + bandit: true + cdknag: + enabled: true + options: + nag_packs: + AwsSolutionsChecks: true + HIPAASecurityChecks: false + NIST80053R4Checks: false + NIST80053R5Checks: false + PCIDSS321Checks: false + cfnnag: true + checkov: true + gitsecrets: true + grype: true + npmaudit: true + semgrep: true + # Custom scanner provided by plugin at build time, referencable by key + trivysast: + name: trivy-sast + type: SAST + enabled: true +# SBOM scanner settings +sbom: + output_formats: + - cyclonedx + - html + scanners: + syft: true + # Custom scanner provided by plugin at build time, referencable by key + trivysbom: + name: trivy-sbom + type: SBOM + enabled: true diff --git a/bandit.yaml b/bandit.yaml new file mode 100644 index 0000000..36e4604 --- /dev/null +++ b/bandit.yaml @@ -0,0 +1,5 @@ +# Do not check for assert_used in test files; +# they use `assert` intentionally, leading to B101 false positives. + +assert_used: + skips: ["*/test_*.py", "**/test_*.py"] diff --git a/devfile.yaml b/devfile.yaml new file mode 100644 index 0000000..4ff151f --- /dev/null +++ b/devfile.yaml @@ -0,0 +1,20 @@ +schemaVersion: 2.0.0 +metadata: + name: python-project +components: + - name: dev + container: + image: public.ecr.aws/aws-mde/universal-image:latest +commands: + - id: install + exec: + component: dev + commandLine: "pip install --user poetry && ~/.local/bin/poetry install" + - id: build + exec: + component: dev + commandLine: "~/.local/bin/poetry build" + - id: test + exec: + component: dev + commandLine: "~/.local/bin/poetry run pytest" \ No newline at end of file diff --git a/docs/content/faq.md b/docs/content/faq.md index 78fcc9c..0d5c7d2 100644 --- a/docs/content/faq.md +++ b/docs/content/faq.md @@ -27,4 +27,4 @@ You can configure the OCI compatible tool to use with by using the environment v Yes, `ash` will use a bandit configuration file if it is placed at the root of your project directory. It must be named `.bandit`, `bandit.yaml`, or `bandit.toml`. Configuration files must be formatted properly according to the [Bandit documentation](https://bandit.readthedocs.io/en/latest/config.html). -> Note: paths excluded in a Bandit configuration file must begin with a `/` because `ash` uses an absolute path when calling `bandit`. \ No newline at end of file +> Note: paths excluded in a Bandit configuration file must begin with a `/` because `ash` uses an absolute path when calling `bandit`. diff --git a/docs/content/index.md b/docs/content/index.md index 1fd7c10..011092d 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -260,7 +260,7 @@ OPTIONS: - Q: How to run `ash` in an environment without internet connectivity/with an airgap? - A: From your environment which does have internet connectivity, build the ASH image using `--offline` and `--offline-semgrep-rulesets` to specify what resources to package into the image. Environment variable `$ASH_IMAGE_NAME` controls the name of the image. After building, push to your container repository of choice which will be available within the airgapped environment. When you go to execute ASH in your offline environment, passing `--no-build` to `ash` alongside `--offline` and `--offline-semgrep-rulesets` will use your offline image and skip the build. Specify `$ASH_IMAGE_NAME` to override ASH's container image to the previously-built image available within your airgapped environment. + A: From your environment which does have internet connectivity, build the ASH image using `--offline` and `--offline-semgrep-rulesets` to specify what resources to package into the image. Environment variable `$ASH_IMAGE_NAME` controls the name of the image. After building, push to your container repository of choice which will be available within the airgapped environment. When you go to execute ASH in your offline environment, passing `--no-build` to `ash` alongside `--offline` and `--offline-semgrep-rulesets` will use your offline image and skip the build. Specify `$ASH_IMAGE_NAME` to override ASH's container image to the previously-built image available within your airgapped environment. ## Feedback diff --git a/helper_dockerfiles/Dockerfile-cdk b/helper_dockerfiles/Dockerfile-cdk deleted file mode 100644 index 02b2634..0000000 --- a/helper_dockerfiles/Dockerfile-cdk +++ /dev/null @@ -1,20 +0,0 @@ -# Get Ubuntu Image -FROM public.ecr.aws/docker/library/node:18.0.0 -ENV TZ=Europe/London -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -# Instal prerequisites -RUN apt-get update && \ - apt-get upgrade -y - -# -# Make sure the default dirs are initialized -# -RUN mkdir -p /src && \ - mkdir -p /out && \ - mkdir -p /run/scan/src \ - mkdir -p /ash - -WORKDIR /src - -CMD bash -C /utils/cdk-docker-execute.sh diff --git a/helper_dockerfiles/Dockerfile-git b/helper_dockerfiles/Dockerfile-git deleted file mode 100644 index 3795500..0000000 --- a/helper_dockerfiles/Dockerfile-git +++ /dev/null @@ -1,25 +0,0 @@ -# Get Ubuntu Image -FROM --platform=linux/amd64 public.ecr.aws/bitnami/python:3.11 - -# Install prerequisites -RUN apt-get update && \ - apt-get install -y git tree && \ - apt-get upgrade -y - -# Clone git-secrets directory -RUN git clone https://github.com/awslabs/git-secrets.git && \ - cd git-secrets && \ - make install - -# -# Make sure the default dirs are initialized -# -RUN mkdir -p /src && \ - mkdir -p /out && \ - mkdir -p /run/scan/src \ - mkdir -p /ash - -WORKDIR /src -VOLUME /src - -CMD bash -C /utils/git-docker-execute.sh diff --git a/helper_dockerfiles/Dockerfile-grype b/helper_dockerfiles/Dockerfile-grype deleted file mode 100644 index 62de096..0000000 --- a/helper_dockerfiles/Dockerfile-grype +++ /dev/null @@ -1,35 +0,0 @@ -# Get Python Image -FROM --platform=linux/amd64 public.ecr.aws/bitnami/python:3.11 -SHELL ["bash", "-c"] -ARG OFFLINE="NO" -ARG OFFLINE_SEMGREP_RULESETS="p/ci" - -ENV HOME="/root" -ENV OFFLINE="${OFFLINE}" -ENV GRYPE_DB_CACHE_DIR="${HOME}/.grype" -ENV SEMGREP_RULES_CACHE_DIR="${HOME}/.semgrep" - -# Instal prerequisites -RUN curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin && \ - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin && \ - python3 -m pip install semgrep - -RUN if [[ "$OFFLINE" == "YES" ]]; then \ - grype db update && \ - mkdir -p ${SEMGREP_RULES_CACHE_DIR} && \ - for i in $OFFLINE_SEMGREP_RULESETS; do curl "https://semgrep.dev/c/${i}" -o "${SEMGREP_RULES_CACHE_DIR}/$(basename "${i}").yml"; done \ - fi - - -# -# Make sure the default dirs are initialized -# -RUN mkdir -p /src && \ - mkdir -p /out && \ - mkdir -p /run/scan/src \ - mkdir -p /ash - -WORKDIR /src -VOLUME /src - -CMD bash -C /utils/grype-docker-execute.sh diff --git a/helper_dockerfiles/Dockerfile-js b/helper_dockerfiles/Dockerfile-js deleted file mode 100644 index 9c3297c..0000000 --- a/helper_dockerfiles/Dockerfile-js +++ /dev/null @@ -1,19 +0,0 @@ -# Get NPM Image -FROM public.ecr.aws/docker/library/node:18.0.0 -ARG OFFLINE="NO" -ENV BUILD_DATE_EPOCH="${BUILD_DATE_EPOCH}" - -ENV BUILD_DATE_EPOCH="${BUILD_DATE_EPOCH}" -ENV OFFLINE="${OFFLINE}" -# -# Make sure the default dirs are initialized -# -RUN mkdir -p /src && \ - mkdir -p /out && \ - mkdir -p /run/scan/src \ - mkdir -p /ash - -WORKDIR /src -VOLUME /src - -CMD bash -C /utils/js-docker-execute.sh diff --git a/helper_dockerfiles/Dockerfile-py b/helper_dockerfiles/Dockerfile-py deleted file mode 100644 index 5187a53..0000000 --- a/helper_dockerfiles/Dockerfile-py +++ /dev/null @@ -1,19 +0,0 @@ -# Get Python Image -FROM --platform=linux/amd64 public.ecr.aws/bitnami/python:3.11 - -# Instal prerequisites -RUN pip install --no-cache-dir --upgrade pip && \ - pip install --no-cache-dir bandit nbconvert jupyterlab - -# -# Make sure the default dirs are initialized -# -RUN mkdir -p /src && \ - mkdir -p /out && \ - mkdir -p /run/scan/src \ - mkdir -p /ash - -WORKDIR /src -VOLUME /src - -CMD bash -C /utils/py-docker-execute.sh diff --git a/helper_dockerfiles/Dockerfile-yaml b/helper_dockerfiles/Dockerfile-yaml deleted file mode 100644 index 0b49108..0000000 --- a/helper_dockerfiles/Dockerfile-yaml +++ /dev/null @@ -1,30 +0,0 @@ -# Get Ubuntu Image -FROM --platform=linux/amd64 public.ecr.aws/bitnami/python:3.11 -ARG OFFLINE="NO" -ENV BUILD_DATE_EPOCH="${BUILD_DATE_EPOCH}" - -ENV BUILD_DATE_EPOCH="${BUILD_DATE_EPOCH}" -ENV OFFLINE="${OFFLINE}" - -ENV TZ=Europe/London -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -# Instal prerequisites -RUN apt-get update && \ - apt-get upgrade -y && \ - apt-get install -y ruby-full && \ - rm -rf /var/lib/apt/lists/* - -RUN pip3 install -U checkov && gem install cfn-nag - -# -# Make sure the default dirs are initialized -# -RUN mkdir -p /src && \ - mkdir -p /out && \ - mkdir -p /run/scan/src \ - mkdir -p /ash - -WORKDIR /src - -CMD bash -C /utils/yaml-docker-execute.sh diff --git a/poetry.lock b/poetry.lock index 22e8b99..7afc73d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,166 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. + +[[package]] +name = "aiodns" +version = "3.2.0" +description = "Simple DNS resolver for asyncio" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "aiodns-3.2.0-py3-none-any.whl", hash = "sha256:e443c0c27b07da3174a109fd9e736d69058d808f144d3c9d56dbd1776964c5f5"}, + {file = "aiodns-3.2.0.tar.gz", hash = "sha256:62869b23409349c21b072883ec8998316b234c9a9e36675756e8e317e8768f72"}, +] + +[package.dependencies] +pycares = ">=4.0.0" + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.11.12" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohttp-3.11.12-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa8a8caca81c0a3e765f19c6953416c58e2f4cc1b84829af01dd1c771bb2f91f"}, + {file = "aiohttp-3.11.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84ede78acde96ca57f6cf8ccb8a13fbaf569f6011b9a52f870c662d4dc8cd854"}, + {file = "aiohttp-3.11.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:584096938a001378484aa4ee54e05dc79c7b9dd933e271c744a97b3b6f644957"}, + {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:392432a2dde22b86f70dd4a0e9671a349446c93965f261dbaecfaf28813e5c42"}, + {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:88d385b8e7f3a870146bf5ea31786ef7463e99eb59e31db56e2315535d811f55"}, + {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b10a47e5390c4b30a0d58ee12581003be52eedd506862ab7f97da7a66805befb"}, + {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b5263dcede17b6b0c41ef0c3ccce847d82a7da98709e75cf7efde3e9e3b5cae"}, + {file = "aiohttp-3.11.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50c5c7b8aa5443304c55c262c5693b108c35a3b61ef961f1e782dd52a2f559c7"}, + {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1c031a7572f62f66f1257db37ddab4cb98bfaf9b9434a3b4840bf3560f5e788"}, + {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7e44eba534381dd2687be50cbd5f2daded21575242ecfdaf86bbeecbc38dae8e"}, + {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:145a73850926018ec1681e734cedcf2716d6a8697d90da11284043b745c286d5"}, + {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2c311e2f63e42c1bf86361d11e2c4a59f25d9e7aabdbdf53dc38b885c5435cdb"}, + {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ea756b5a7bac046d202a9a3889b9a92219f885481d78cd318db85b15cc0b7bcf"}, + {file = "aiohttp-3.11.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:526c900397f3bbc2db9cb360ce9c35134c908961cdd0ac25b1ae6ffcaa2507ff"}, + {file = "aiohttp-3.11.12-cp310-cp310-win32.whl", hash = "sha256:b8d3bb96c147b39c02d3db086899679f31958c5d81c494ef0fc9ef5bb1359b3d"}, + {file = "aiohttp-3.11.12-cp310-cp310-win_amd64.whl", hash = "sha256:7fe3d65279bfbee8de0fb4f8c17fc4e893eed2dba21b2f680e930cc2b09075c5"}, + {file = "aiohttp-3.11.12-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87a2e00bf17da098d90d4145375f1d985a81605267e7f9377ff94e55c5d769eb"}, + {file = "aiohttp-3.11.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b34508f1cd928ce915ed09682d11307ba4b37d0708d1f28e5774c07a7674cac9"}, + {file = "aiohttp-3.11.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:936d8a4f0f7081327014742cd51d320296b56aa6d324461a13724ab05f4b2933"}, + {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de1378f72def7dfb5dbd73d86c19eda0ea7b0a6873910cc37d57e80f10d64e1"}, + {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9d45dbb3aaec05cf01525ee1a7ac72de46a8c425cb75c003acd29f76b1ffe94"}, + {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:930ffa1925393381e1e0a9b82137fa7b34c92a019b521cf9f41263976666a0d6"}, + {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8340def6737118f5429a5df4e88f440746b791f8f1c4ce4ad8a595f42c980bd5"}, + {file = "aiohttp-3.11.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4016e383f91f2814e48ed61e6bda7d24c4d7f2402c75dd28f7e1027ae44ea204"}, + {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c0600bcc1adfaaac321422d615939ef300df81e165f6522ad096b73439c0f58"}, + {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0450ada317a65383b7cce9576096150fdb97396dcfe559109b403c7242faffef"}, + {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:850ff6155371fd802a280f8d369d4e15d69434651b844bde566ce97ee2277420"}, + {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8fd12d0f989c6099e7b0f30dc6e0d1e05499f3337461f0b2b0dadea6c64b89df"}, + {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:76719dd521c20a58a6c256d058547b3a9595d1d885b830013366e27011ffe804"}, + {file = "aiohttp-3.11.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97fe431f2ed646a3b56142fc81d238abcbaff08548d6912acb0b19a0cadc146b"}, + {file = "aiohttp-3.11.12-cp311-cp311-win32.whl", hash = "sha256:e10c440d142fa8b32cfdb194caf60ceeceb3e49807072e0dc3a8887ea80e8c16"}, + {file = "aiohttp-3.11.12-cp311-cp311-win_amd64.whl", hash = "sha256:246067ba0cf5560cf42e775069c5d80a8989d14a7ded21af529a4e10e3e0f0e6"}, + {file = "aiohttp-3.11.12-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e392804a38353900c3fd8b7cacbea5132888f7129f8e241915e90b85f00e3250"}, + {file = "aiohttp-3.11.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8fa1510b96c08aaad49303ab11f8803787c99222288f310a62f493faf883ede1"}, + {file = "aiohttp-3.11.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dc065a4285307607df3f3686363e7f8bdd0d8ab35f12226362a847731516e42c"}, + {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddb31f8474695cd61fc9455c644fc1606c164b93bff2490390d90464b4655df"}, + {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dec0000d2d8621d8015c293e24589d46fa218637d820894cb7356c77eca3259"}, + {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3552fe98e90fdf5918c04769f338a87fa4f00f3b28830ea9b78b1bdc6140e0d"}, + {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfe7f984f28a8ae94ff3a7953cd9678550dbd2a1f9bda5dd9c5ae627744c78e"}, + {file = "aiohttp-3.11.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a481a574af914b6e84624412666cbfbe531a05667ca197804ecc19c97b8ab1b0"}, + {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1987770fb4887560363b0e1a9b75aa303e447433c41284d3af2840a2f226d6e0"}, + {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a4ac6a0f0f6402854adca4e3259a623f5c82ec3f0c049374133bcb243132baf9"}, + {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c96a43822f1f9f69cc5c3706af33239489a6294be486a0447fb71380070d4d5f"}, + {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a5e69046f83c0d3cb8f0d5bd9b8838271b1bc898e01562a04398e160953e8eb9"}, + {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:68d54234c8d76d8ef74744f9f9fc6324f1508129e23da8883771cdbb5818cbef"}, + {file = "aiohttp-3.11.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9fd9dcf9c91affe71654ef77426f5cf8489305e1c66ed4816f5a21874b094b9"}, + {file = "aiohttp-3.11.12-cp312-cp312-win32.whl", hash = "sha256:0ed49efcd0dc1611378beadbd97beb5d9ca8fe48579fc04a6ed0844072261b6a"}, + {file = "aiohttp-3.11.12-cp312-cp312-win_amd64.whl", hash = "sha256:54775858c7f2f214476773ce785a19ee81d1294a6bedc5cc17225355aab74802"}, + {file = "aiohttp-3.11.12-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:413ad794dccb19453e2b97c2375f2ca3cdf34dc50d18cc2693bd5aed7d16f4b9"}, + {file = "aiohttp-3.11.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a93d28ed4b4b39e6f46fd240896c29b686b75e39cc6992692e3922ff6982b4c"}, + {file = "aiohttp-3.11.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d589264dbba3b16e8951b6f145d1e6b883094075283dafcab4cdd564a9e353a0"}, + {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5148ca8955affdfeb864aca158ecae11030e952b25b3ae15d4e2b5ba299bad2"}, + {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:525410e0790aab036492eeea913858989c4cb070ff373ec3bc322d700bdf47c1"}, + {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bd8695be2c80b665ae3f05cb584093a1e59c35ecb7d794d1edd96e8cc9201d7"}, + {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0203433121484b32646a5f5ea93ae86f3d9559d7243f07e8c0eab5ff8e3f70e"}, + {file = "aiohttp-3.11.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40cd36749a1035c34ba8d8aaf221b91ca3d111532e5ccb5fa8c3703ab1b967ed"}, + {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7442662afebbf7b4c6d28cb7aab9e9ce3a5df055fc4116cc7228192ad6cb484"}, + {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8a2fb742ef378284a50766e985804bd6adb5adb5aa781100b09befdbfa757b65"}, + {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cee3b117a8d13ab98b38d5b6bdcd040cfb4181068d05ce0c474ec9db5f3c5bb"}, + {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f6a19bcab7fbd8f8649d6595624856635159a6527861b9cdc3447af288a00c00"}, + {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e4cecdb52aaa9994fbed6b81d4568427b6002f0a91c322697a4bfcc2b2363f5a"}, + {file = "aiohttp-3.11.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:30f546358dfa0953db92ba620101fefc81574f87b2346556b90b5f3ef16e55ce"}, + {file = "aiohttp-3.11.12-cp313-cp313-win32.whl", hash = "sha256:ce1bb21fc7d753b5f8a5d5a4bae99566386b15e716ebdb410154c16c91494d7f"}, + {file = "aiohttp-3.11.12-cp313-cp313-win_amd64.whl", hash = "sha256:f7914ab70d2ee8ab91c13e5402122edbc77821c66d2758abb53aabe87f013287"}, + {file = "aiohttp-3.11.12-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c3623053b85b4296cd3925eeb725e386644fd5bc67250b3bb08b0f144803e7b"}, + {file = "aiohttp-3.11.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67453e603cea8e85ed566b2700efa1f6916aefbc0c9fcb2e86aaffc08ec38e78"}, + {file = "aiohttp-3.11.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6130459189e61baac5a88c10019b21e1f0c6d00ebc770e9ce269475650ff7f73"}, + {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9060addfa4ff753b09392efe41e6af06ea5dd257829199747b9f15bfad819460"}, + {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34245498eeb9ae54c687a07ad7f160053911b5745e186afe2d0c0f2898a1ab8a"}, + {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dc0fba9a74b471c45ca1a3cb6e6913ebfae416678d90529d188886278e7f3f6"}, + {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a478aa11b328983c4444dacb947d4513cb371cd323f3845e53caeda6be5589d5"}, + {file = "aiohttp-3.11.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c160a04283c8c6f55b5bf6d4cad59bb9c5b9c9cd08903841b25f1f7109ef1259"}, + {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:edb69b9589324bdc40961cdf0657815df674f1743a8d5ad9ab56a99e4833cfdd"}, + {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ee84c2a22a809c4f868153b178fe59e71423e1f3d6a8cd416134bb231fbf6d3"}, + {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bf4480a5438f80e0f1539e15a7eb8b5f97a26fe087e9828e2c0ec2be119a9f72"}, + {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e6b2732ef3bafc759f653a98881b5b9cdef0716d98f013d376ee8dfd7285abf1"}, + {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f752e80606b132140883bb262a457c475d219d7163d996dc9072434ffb0784c4"}, + {file = "aiohttp-3.11.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ab3247d58b393bda5b1c8f31c9edece7162fc13265334217785518dd770792b8"}, + {file = "aiohttp-3.11.12-cp39-cp39-win32.whl", hash = "sha256:0d5176f310a7fe6f65608213cc74f4228e4f4ce9fd10bcb2bb6da8fc66991462"}, + {file = "aiohttp-3.11.12-cp39-cp39-win_amd64.whl", hash = "sha256:74bd573dde27e58c760d9ca8615c41a57e719bff315c9adb6f2a4281a28e8798"}, + {file = "aiohttp-3.11.12.tar.gz", hash = "sha256:7603ca26d75b1b86160ce1bbe2787a0b706e592af5b2504e12caa88a217767b0"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] + +[[package]] +name = "aiomultiprocess" +version = "0.9.1" +description = "AsyncIO version of the standard multiprocessing module" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "aiomultiprocess-0.9.1-py3-none-any.whl", hash = "sha256:3a7b3bb3c38dbfb4d9d1194ece5934b6d32cf0280e8edbe64a7d215bba1322c6"}, + {file = "aiomultiprocess-0.9.1.tar.gz", hash = "sha256:f0231dbe0291e15325d7896ebeae0002d95a4f2675426ca05eb35f24c60e495b"}, +] + +[package.extras] +dev = ["attribution (==1.7.1)", "black (==24.4.0)", "coverage (==7.4.4)", "flake8 (==7.0.0)", "flake8-bugbear (==24.4.21)", "flit (==3.9.0)", "mypy (==1.9.0)", "usort (==1.0.8.post1)", "uvloop (==0.19.0) ; sys_platform != \"win32\""] +docs = ["sphinx (==7.3.7)", "sphinx-mdinclude (==0.6.0)"] + +[[package]] +name = "aiosignal" +version = "1.3.2" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, + {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" [[package]] name = "annotated-types" @@ -6,352 +168,5225 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [[package]] -name = "black" -version = "24.4.2" -description = "The uncompromising code formatter." +name = "anyio" +version = "4.9.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, - {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, - {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, - {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, - {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, - {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, - {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, - {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, - {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, - {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, - {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, - {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, - {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, - {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, - {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, - {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, - {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, - {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, - {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, - {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, - {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, - {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, ] [package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] +trio = ["trio (>=0.26.1)"] [[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "platform_system == \"Darwin\"" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "argcomplete" +version = "3.6.1" +description = "Bash tab completion for argparse" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "argcomplete-3.6.1-py3-none-any.whl", hash = "sha256:cef54d7f752560570291214f0f1c48c3b8ef09aca63d65de7747612666725dbc"}, + {file = "argcomplete-3.6.1.tar.gz", hash = "sha256:927531c2fbaa004979f18c2316f6ffadcfc5cc2de15ae2624dfe65deaf60e14f"}, +] + +[package.extras] +test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +description = "Argon2 for Python" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, + {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, ] [package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["argon2-cffi[tests,typing]", "tox (>4)"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] +tests = ["hypothesis", "pytest"] +typing = ["mypy"] [[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +python-versions = ">=3.6" +groups = ["main"] files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, ] +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + [[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." +name = "arrow" +version = "1.3.0" +description = "Better dates & times for Python" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, + {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, ] +[package.dependencies] +python-dateutil = ">=2.7.0" +types-python-dateutil = ">=2.8.10" + +[package.extras] +doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] +test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] + [[package]] -name = "packaging" -version = "24.0" -description = "Core utilities for Python packages" +name = "asttokens" +version = "3.0.0" +description = "Annotate AST trees with source code positions" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, ] +[package.extras] +astroid = ["astroid (>=2,<4)"] +test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] + [[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." +name = "async-lru" +version = "2.0.5" +description = "Simple LRU cache for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943"}, + {file = "async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb"}, +] + +[package.dependencies] +typing_extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.10\"" files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, ] [[package]] -name = "platformdirs" -version = "4.2.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] -name = "pydantic" -version = "2.7.3" -description = "Data validation using Python type hints" +name = "aws-cdk-asset-awscli-v1" +version = "2.2.230" +description = "A library that contains the AWS CLI for use in Lambda Layers" +optional = false +python-versions = "~=3.9" +groups = ["main"] +files = [ + {file = "aws_cdk_asset_awscli_v1-2.2.230-py3-none-any.whl", hash = "sha256:e41bf095ca74af9924e9b2e3244091ba3298f40b938b2397634f551d6ec8a099"}, + {file = "aws_cdk_asset_awscli_v1-2.2.230.tar.gz", hash = "sha256:9e2281ce1ffe2cdb8d433bd26d3b2c5767eac282871064ab66de9a2ecc987fec"}, +] + +[package.dependencies] +jsii = ">=1.110.0,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<4.3.0" + +[[package]] +name = "aws-cdk-asset-node-proxy-agent-v6" +version = "2.1.0" +description = "@aws-cdk/asset-node-proxy-agent-v6" +optional = false +python-versions = "~=3.8" +groups = ["main"] +files = [ + {file = "aws_cdk.asset_node_proxy_agent_v6-2.1.0-py3-none-any.whl", hash = "sha256:24a388b69a44d03bae6dbf864c4e25ba650d4b61c008b4568b94ffbb9a69e40e"}, + {file = "aws_cdk_asset_node_proxy_agent_v6-2.1.0.tar.gz", hash = "sha256:1f292c0631f86708ba4ee328b3a2b229f7e46ea1c79fbde567ee9eb119c2b0e2"}, +] + +[package.dependencies] +jsii = ">=1.103.1,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<5.0.0" + +[[package]] +name = "aws-cdk-cloud-assembly-schema" +version = "41.2.0" +description = "Schema for the protocol between CDK framework and CDK CLI" +optional = false +python-versions = "~=3.8" +groups = ["main"] +files = [ + {file = "aws_cdk.cloud_assembly_schema-41.2.0-py3-none-any.whl", hash = "sha256:779ca7e3edb02695e0a94a1f38e322b04fbe192cd7944553f80b681a21edd670"}, + {file = "aws_cdk_cloud_assembly_schema-41.2.0.tar.gz", hash = "sha256:7064ac13f6944fd53f8d8eace611d3c5d8db7014049d629f5c47ede8dc5f2e3b"}, +] + +[package.dependencies] +jsii = ">=1.108.0,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<4.3.0" + +[[package]] +name = "aws-cdk-lib" +version = "2.188.0" +description = "Version 2 of the AWS Cloud Development Kit library" +optional = false +python-versions = "~=3.9" +groups = ["main"] +files = [ + {file = "aws_cdk_lib-2.188.0-py3-none-any.whl", hash = "sha256:0f7c26444eb56dcdcf9b9357a06863859fee51965999717753eaa2af64c9b99f"}, + {file = "aws_cdk_lib-2.188.0.tar.gz", hash = "sha256:373f40217dbb9f924f530cd51cb56b3d952bfe571bfa8702f764f1643cef7e88"}, +] + +[package.dependencies] +"aws-cdk.asset-awscli-v1" = ">=2.2.229,<3.0.0" +"aws-cdk.asset-node-proxy-agent-v6" = ">=2.1.0,<3.0.0" +"aws-cdk.cloud-assembly-schema" = ">=41.0.0,<42.0.0" +constructs = ">=10.0.0,<11.0.0" +jsii = ">=1.110.0,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<4.3.0" + +[[package]] +name = "babel" +version = "2.17.0" +description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[package.extras] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] + +[[package]] +name = "bandit" +version = "1.8.3" +description = "Security oriented static analyser for python code." +optional = false +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"}, - {file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"}, + {file = "bandit-1.8.3-py3-none-any.whl", hash = "sha256:28f04dc0d258e1dd0f99dee8eefa13d1cb5e3fde1a5ab0c523971f97b289bcd8"}, + {file = "bandit-1.8.3.tar.gz", hash = "sha256:f5847beb654d309422985c36644649924e0ea4425c76dec2e89110b87506193a"}, ] [package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.18.4" -typing-extensions = ">=4.6.1" +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +PyYAML = ">=5.3.1" +rich = "*" +stevedore = ">=1.20.0" [package.extras] -email = ["email-validator (>=2.0.0)"] +baseline = ["GitPython (>=3.1.30)"] +sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] +toml = ["tomli (>=1.1.0) ; python_version < \"3.11\""] +yaml = ["PyYAML"] [[package]] -name = "pydantic-core" -version = "2.18.4" -description = "Core functionality for Pydantic validation and serialization" +name = "bc-detect-secrets" +version = "1.5.38" +description = "Tool for detecting secrets in the codebase" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, - {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, - {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, - {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, - {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, - {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, - {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, - {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, - {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, - {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, - {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, - {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, - {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, - {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, - {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, - {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, - {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, - {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, - {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, - {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, - {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, - {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, - {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, - {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, - {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, - {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, - {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, - {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, - {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, - {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, - {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, - {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, - {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, - {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, - {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, - {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, - {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, - {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, - {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, - {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, + {file = "bc_detect_secrets-1.5.38-py3-none-any.whl", hash = "sha256:5d6412fcf6ca41e1cb353dddc31b1547465b3286ec29f5843c416b7d18f7afc7"}, + {file = "bc_detect_secrets-1.5.38.tar.gz", hash = "sha256:26d8a34c09085958b5c27620a55145ae603254e309213d7a49b9c1fd683d2707"}, ] [package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +pyyaml = "*" +requests = "*" +unidiff = "*" + +[package.extras] +gibberish = ["gibberish-detector"] +word-list = ["pyahocorasick"] [[package]] -name = "regex" -version = "2024.5.15" -description = "Alternative regular expression module, to replace re." +name = "bc-jsonpath-ng" +version = "1.6.1" +description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, - {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, - {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, - {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, - {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, - {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, - {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, - {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, - {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, - {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, - {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, - {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, - {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, - {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, - {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, - {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, - {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, - {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, - {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, - {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, - {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, - {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, - {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, - {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, - {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, - {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, - {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, - {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, - {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, - {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, - {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, - {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, - {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, - {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, - {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, - {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, + {file = "bc-jsonpath-ng-1.6.1.tar.gz", hash = "sha256:6ea4e379c4400a511d07605b8d981950292dd098a5619d143328af4e841a2320"}, + {file = "bc_jsonpath_ng-1.6.1-py3-none-any.whl", hash = "sha256:2c85bb1d194376808fe1fc49558dd484e39024b15c719995e22de811e6ba4dc8"}, ] +[package.dependencies] +decorator = "*" +ply = "*" + [[package]] -name = "tomli" +name = "bc-python-hcl2" +version = "0.4.2" +description = "A parser for HCL2" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "bc-python-hcl2-0.4.2.tar.gz", hash = "sha256:ac8ff59fb9bd437ea29b89a7d7c507fd0a1e957845bae9aeac69f2892b8d681e"}, + {file = "bc_python_hcl2-0.4.2-py3-none-any.whl", hash = "sha256:90d2afbaa2c7e77b7b30bf58180084e11d95287f7c3e19c5bfbdb54ab2fd80e9"}, +] + +[package.dependencies] +lark = ">=1.0.0" + +[[package]] +name = "beartype" +version = "0.20.2" +description = "Unbearably fast near-real-time hybrid runtime-static type-checking in pure Python." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "beartype-0.20.2-py3-none-any.whl", hash = "sha256:5171a91ecf01438a59884f0cde37d2d5da2c992198b53d6ba31db3940f47ff04"}, + {file = "beartype-0.20.2.tar.gz", hash = "sha256:38c60c065ad99364a8c767e8a0e71ba8263d467b91414ed5dcffb7758a2e8079"}, +] + +[package.extras] +dev = ["autoapi (>=0.9.0)", "click", "coverage (>=5.5)", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pydata-sphinx-theme (<=0.7.2)", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)", "xarray"] +doc-rtd = ["autoapi (>=0.9.0)", "pydata-sphinx-theme (<=0.7.2)", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)"] +test = ["click", "coverage (>=5.5)", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)", "xarray"] +test-tox = ["click", "equinox ; sys_platform == \"linux\"", "jax[cpu] ; sys_platform == \"linux\"", "jaxtyping ; sys_platform == \"linux\"", "langchain", "mypy (>=0.800) ; platform_python_implementation != \"PyPy\"", "nuitka (>=1.2.6) ; sys_platform == \"linux\"", "numba ; python_version < \"3.13.0\"", "numpy ; sys_platform != \"darwin\" and platform_python_implementation != \"PyPy\"", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "rich-click", "sphinx", "typing-extensions (>=3.10.0.0)", "xarray"] +test-tox-coverage = ["coverage (>=5.5)"] + +[[package]] +name = "beautifulsoup4" +version = "4.13.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.7.0" +groups = ["main"] +files = [ + {file = "beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16"}, + {file = "beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b"}, +] + +[package.dependencies] +soupsieve = ">1.2" +typing-extensions = ">=4.0.0" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bleach" +version = "6.2.0" +description = "An easy safelist-based HTML-sanitizing tool." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, + {file = "bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f"}, +] + +[package.dependencies] +tinycss2 = {version = ">=1.1.0,<1.5", optional = true, markers = "extra == \"css\""} +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.5)"] + +[[package]] +name = "boltons" +version = "21.0.0" +description = "When they're not builtins, they're boltons." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "boltons-21.0.0-py2.py3-none-any.whl", hash = "sha256:b9bb7b58b2b420bbe11a6025fdef6d3e5edc9f76a42fb467afe7ca212ef9948b"}, + {file = "boltons-21.0.0.tar.gz", hash = "sha256:65e70a79a731a7fe6e98592ecfb5ccf2115873d01dbc576079874629e5c90f13"}, +] + +[[package]] +name = "boolean-py" +version = "4.0" +description = "Define boolean algebras, create and parse boolean expressions and create custom boolean DSL." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "boolean.py-4.0-py3-none-any.whl", hash = "sha256:2876f2051d7d6394a531d82dc6eb407faa0b01a0a0b3083817ccd7323b8d96bd"}, + {file = "boolean.py-4.0.tar.gz", hash = "sha256:17b9a181630e43dde1851d42bef546d616d5d9b4480357514597e78b203d06e4"}, +] + +[[package]] +name = "boto3" +version = "1.35.49" +description = "The AWS SDK for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "boto3-1.35.49-py3-none-any.whl", hash = "sha256:b660c649a27a6b47a34f6f858f5bd7c3b0a798a16dec8dda7cbebeee80fd1f60"}, + {file = "boto3-1.35.49.tar.gz", hash = "sha256:ddecb27f5699ca9f97711c52b6c0652c2e63bf6c2bfbc13b819b4f523b4d30ff"}, +] + +[package.dependencies] +botocore = ">=1.35.49,<1.36.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.10.0,<0.11.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.35.99" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "botocore-1.35.99-py3-none-any.whl", hash = "sha256:b22d27b6b617fc2d7342090d6129000af2efd20174215948c0d7ae2da0fab445"}, + {file = "botocore-1.35.99.tar.gz", hash = "sha256:1eab44e969c39c5f3d9a3104a0836c24715579a455f12b3979a31d7cde51b3c3"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} + +[package.extras] +crt = ["awscrt (==0.22.0)"] + +[[package]] +name = "bracex" +version = "2.5.post1" +description = "Bash style brace expander." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "bracex-2.5.post1-py3-none-any.whl", hash = "sha256:13e5732fec27828d6af308628285ad358047cec36801598368cb28bc631dbaf6"}, + {file = "bracex-2.5.post1.tar.gz", hash = "sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6"}, +] + +[[package]] +name = "cached-property" version = "2.0.1" -description = "A lil' TOML parser" +description = "A decorator for caching properties in classes." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cached_property-2.0.1-py3-none-any.whl", hash = "sha256:f617d70ab1100b7bcf6e42228f9ddcb78c676ffa167278d9f730d1c2fba69ccb"}, + {file = "cached_property-2.0.1.tar.gz", hash = "sha256:484d617105e3ee0e4f1f58725e72a8ef9e93deee462222dbd51cd91230897641"}, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, + {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, ] [[package]] -name = "typing-extensions" -version = "4.12.1" -description = "Backported and Experimental Type Hints for Python 3.8+" +name = "cattrs" +version = "24.1.3" +description = "Composable complex class support for attrs and dataclasses." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cattrs-24.1.3-py3-none-any.whl", hash = "sha256:adf957dddd26840f27ffbd060a6c4dd3b2192c5b7c2c0525ef1bd8131d8a83f5"}, + {file = "cattrs-24.1.3.tar.gz", hash = "sha256:981a6ef05875b5bb0c7fb68885546186d306f10f0f6718fe9b96c226e68821ff"}, +] + +[package.dependencies] +attrs = ">=23.1.0" +exceptiongroup = {version = ">=1.1.1", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.1.0,<4.6.3 || >4.6.3", markers = "python_version < \"3.11\""} + +[package.extras] +bson = ["pymongo (>=4.4.0)"] +cbor2 = ["cbor2 (>=5.4.6)"] +msgpack = ["msgpack (>=1.0.5)"] +msgspec = ["msgspec (>=0.18.5) ; implementation_name == \"cpython\""] +orjson = ["orjson (>=3.9.2) ; implementation_name == \"cpython\""] +pyyaml = ["pyyaml (>=6.0)"] +tomlkit = ["tomlkit (>=0.11.8)"] +ujson = ["ujson (>=5.7.0)"] + +[[package]] +name = "cdk-nag" +version = "2.35.64" +description = "Check CDK v2 applications for best practices using a combination on available rule packs." +optional = false +python-versions = "~=3.9" +groups = ["main"] +files = [ + {file = "cdk_nag-2.35.64-py3-none-any.whl", hash = "sha256:9272c25b1be7dba8d4bed0ae05a09417ba7de5488e68599d465f378a8eadfa2c"}, + {file = "cdk_nag-2.35.64.tar.gz", hash = "sha256:0cfac4ecfdee7df515ee0970e97d490a4135390f1b2b23ec6b0311f9788054ff"}, +] + +[package.dependencies] +aws-cdk-lib = ">=2.156.0,<3.0.0" +constructs = ">=10.0.5,<11.0.0" +jsii = ">=1.111.0,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<4.3.0" + +[[package]] +name = "certifi" +version = "2025.1.31" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfn-flip" +version = "1.3.0" +description = "Convert AWS CloudFormation templates between JSON and YAML formats" +optional = false +python-versions = "*" +groups = ["main"] files = [ - {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, - {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, + {file = "cfn_flip-1.3.0-py3-none-any.whl", hash = "sha256:faca8e77f0d32fb84cce1db1ef4c18b14a325d31125dae73c13bcc01947d2722"}, + {file = "cfn_flip-1.3.0.tar.gz", hash = "sha256:003e02a089c35e1230ffd0e1bcfbbc4b12cc7d2deb2fcc6c4228ac9819307362"}, ] +[package.dependencies] +Click = "*" +PyYAML = ">=4.1" +six = "*" + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, + {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, + {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, +] + +[[package]] +name = "checkov" +version = "3.2.395" +description = "Infrastructure as code static analysis" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "checkov-3.2.395-py3-none-any.whl", hash = "sha256:4bc041a062c9184e8d8e492efa1bfba78b27b0b629bd3b614b68077d1fa730ca"}, + {file = "checkov-3.2.395.tar.gz", hash = "sha256:e588d1edf2e6f5d60c806d295a15966cbe321c7c489411e4012f4de8db8a1750"}, +] + +[package.dependencies] +aiodns = ">=3.0.0,<4.0.0" +aiohttp = ">=3.8.0,<4.0.0" +aiomultiprocess = ">=0.9.0,<0.10.0" +argcomplete = ">=3.0.0,<4.0.0" +bc-detect-secrets = "1.5.38" +bc-jsonpath-ng = "1.6.1" +bc-python-hcl2 = "0.4.2" +boto3 = "1.35.49" +cachetools = ">=5.2.0,<6.0.0" +charset-normalizer = ">=3.1.0,<4.0.0" +click = ">=8.1.0,<9.0.0" +cloudsplaining = ">=0.7.0,<0.8.0" +colorama = ">=0.4.3,<0.5.0" +configargparse = ">=1.5.3,<2.0.0" +cyclonedx-python-lib = ">=6.0.0,<8.0.0" +docker = ">=6.0.1,<8.0.0" +dockerfile-parse = ">=2.0.0,<3.0.0" +dpath = "2.1.3" +gitpython = ">=3.1.30,<4.0.0" +importlib-metadata = ">=6.0.0,<8.0.0" +jmespath = ">=1.0.0,<2.0.0" +jsonschema = ">=4.17.0,<5.0.0" +junit-xml = ">=1.9,<2.0" +license-expression = ">=30.1.0,<31.0.0" +networkx = "<2.7" +openai = "<1.0.0" +packageurl-python = ">=0.11.1,<0.14.0" +packaging = ">=23.0,<24.0" +prettytable = ">=3.6.0,<4.0.0" +pycep-parser = "0.5.1" +pydantic = ">=2.0.0,<3.0.0" +pyston = {version = "2.3.5", markers = "python_version < \"3.11\" and (sys_platform == \"linux\" or sys_platform == \"darwin\") and platform_machine == \"x86_64\" and implementation_name == \"cpython\""} +pyston-autoload = {version = "2.3.5", markers = "python_version < \"3.11\" and (sys_platform == \"linux\" or sys_platform == \"darwin\") and platform_machine == \"x86_64\" and implementation_name == \"cpython\""} +pyyaml = ">=6.0.0,<7.0.0" +requests = ">=2.28.0,<3.0.0" +rustworkx = ">=0.13.0,<1.0.0" +schema = "<=0.7.5" +spdx-tools = ">=0.8.0,<0.9.0" +tabulate = ">=0.9.0,<0.10.0" +termcolor = ">=1.1.0,<2.4.0" +tqdm = ">=4.65.0,<5.0.0" +typing-extensions = ">=4.5.0,<5.0.0" +yarl = ">=1.9.1,<2.0.0" + +[package.extras] +dev = ["GitPython (==3.1.41)", "bandit", "coverage (==7.6.1)", "coverage-badge", "jsonschema", "pytest (<8.0.0)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "click-option-group" +version = "0.5.7" +description = "Option groups missing in Click" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "click_option_group-0.5.7-py3-none-any.whl", hash = "sha256:96b9f52f397ef4d916f81929bd6c1f85e89046c7a401a64e72a61ae74ad35c24"}, + {file = "click_option_group-0.5.7.tar.gz", hash = "sha256:8dc780be038712fc12c9fecb3db4fe49e0d0723f9c171d7cda85c20369be693c"}, +] + +[package.dependencies] +click = ">=7.0" + +[package.extras] +dev = ["pre-commit", "pytest"] +docs = ["m2r2", "pallets-sphinx-themes", "sphinx"] +test = ["pytest"] +test-cov = ["pytest", "pytest-cov"] + +[[package]] +name = "cloudsplaining" +version = "0.7.0" +description = "AWS IAM Security Assessment tool that identifies violations of least privilege and generates a risk-prioritized HTML report." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cloudsplaining-0.7.0-py3-none-any.whl", hash = "sha256:8e93c7b1671c8353f520627cdf7917ec543581c9b9936b3d344817bb4747174e"}, + {file = "cloudsplaining-0.7.0.tar.gz", hash = "sha256:2d8a1d1a3261368a39359bb23aa7d6ac9add274728ff24877b710cdfa96d96af"}, +] + +[package.dependencies] +boto3 = "*" +botocore = "*" +cached-property = "*" +click = "*" +click-option-group = "*" +jinja2 = "*" +markdown = "*" +policy-sentry = ">=0.13.0,<0.14" +pyyaml = "*" +schema = "*" + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +markers = {dev = "sys_platform == \"win32\""} + +[[package]] +name = "comm" +version = "0.2.2" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, + {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, +] + +[package.dependencies] +traitlets = ">=4" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "configargparse" +version = "1.7" +description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." +optional = false +python-versions = ">=3.5" +groups = ["main"] +files = [ + {file = "ConfigArgParse-1.7-py3-none-any.whl", hash = "sha256:d249da6591465c6c26df64a9f73d2536e743be2f244eb3ebe61114af2f94f86b"}, + {file = "ConfigArgParse-1.7.tar.gz", hash = "sha256:e7067471884de5478c58a511e529f0f9bd1c66bfef1dea90935438d6c23306d1"}, +] + +[package.extras] +test = ["PyYAML", "mock", "pytest"] +yaml = ["PyYAML"] + +[[package]] +name = "constructs" +version = "10.4.2" +description = "A programming model for software-defined state" +optional = false +python-versions = "~=3.8" +groups = ["main"] +files = [ + {file = "constructs-10.4.2-py3-none-any.whl", hash = "sha256:1f0f59b004edebfde0f826340698b8c34611f57848139b7954904c61645f13c1"}, + {file = "constructs-10.4.2.tar.gz", hash = "sha256:ce54724360fffe10bab27d8a081844eb81f5ace7d7c62c84b719c49f164d5307"}, +] + +[package.dependencies] +jsii = ">=1.102.0,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + +[[package]] +name = "contextlib2" +version = "21.6.0" +description = "Backports and enhancements for the contextlib module" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "contextlib2-21.6.0-py2.py3-none-any.whl", hash = "sha256:3fbdb64466afd23abaf6c977627b75b6139a5a3e8ce38405c5b413aed7a0471f"}, + {file = "contextlib2-21.6.0.tar.gz", hash = "sha256:ab1e2bfe1d01d968e1b7e8d9023bc51ef3509bba217bb730cee3827e1ee82869"}, +] + +[[package]] +name = "coverage" +version = "7.8.0" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, + {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"}, + {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"}, + {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"}, + {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"}, + {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"}, + {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"}, + {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"}, + {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"}, + {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"}, + {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"}, + {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"}, + {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"}, + {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"}, + {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"}, + {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"}, + {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"}, + {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"}, + {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"}, + {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"}, + {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"}, + {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"}, + {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"}, + {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"}, + {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"}, + {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"}, + {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"}, + {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"}, + {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"}, + {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"}, + {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"}, + {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"}, + {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"}, + {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"}, + {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"}, + {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"}, + {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"}, + {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"}, + {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "cyclonedx-python-lib" +version = "7.6.2" +description = "Python library for CycloneDX" +optional = false +python-versions = "<4.0,>=3.8" +groups = ["main"] +files = [ + {file = "cyclonedx_python_lib-7.6.2-py3-none-any.whl", hash = "sha256:c42fab352cc0f7418d1b30def6751d9067ebcf0e8e4be210fc14d6e742a9edcc"}, + {file = "cyclonedx_python_lib-7.6.2.tar.gz", hash = "sha256:31186c5725ac0cfcca433759a407b1424686cdc867b47cc86e6cf83691310903"}, +] + +[package.dependencies] +license-expression = ">=30,<31" +packageurl-python = ">=0.11,<2" +py-serializable = ">=1.1.0,<2.0.0" +sortedcontainers = ">=2.4.0,<3.0.0" + +[package.extras] +json-validation = ["jsonschema[format] (>=4.18,<5.0)"] +validation = ["jsonschema[format] (>=4.18,<5.0)", "lxml (>=4,<6)"] +xml-validation = ["lxml (>=4,<6)"] + +[[package]] +name = "debugpy" +version = "1.8.13" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "debugpy-1.8.13-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:06859f68e817966723ffe046b896b1bd75c665996a77313370336ee9e1de3e90"}, + {file = "debugpy-1.8.13-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c2db69fb8df3168bc857d7b7d2494fed295dfdbde9a45f27b4b152f37520"}, + {file = "debugpy-1.8.13-cp310-cp310-win32.whl", hash = "sha256:46abe0b821cad751fc1fb9f860fb2e68d75e2c5d360986d0136cd1db8cad4428"}, + {file = "debugpy-1.8.13-cp310-cp310-win_amd64.whl", hash = "sha256:dc7b77f5d32674686a5f06955e4b18c0e41fb5a605f5b33cf225790f114cfeec"}, + {file = "debugpy-1.8.13-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:eee02b2ed52a563126c97bf04194af48f2fe1f68bb522a312b05935798e922ff"}, + {file = "debugpy-1.8.13-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4caca674206e97c85c034c1efab4483f33971d4e02e73081265ecb612af65377"}, + {file = "debugpy-1.8.13-cp311-cp311-win32.whl", hash = "sha256:7d9a05efc6973b5aaf076d779cf3a6bbb1199e059a17738a2aa9d27a53bcc888"}, + {file = "debugpy-1.8.13-cp311-cp311-win_amd64.whl", hash = "sha256:62f9b4a861c256f37e163ada8cf5a81f4c8d5148fc17ee31fb46813bd658cdcc"}, + {file = "debugpy-1.8.13-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:2b8de94c5c78aa0d0ed79023eb27c7c56a64c68217d881bee2ffbcb13951d0c1"}, + {file = "debugpy-1.8.13-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887d54276cefbe7290a754424b077e41efa405a3e07122d8897de54709dbe522"}, + {file = "debugpy-1.8.13-cp312-cp312-win32.whl", hash = "sha256:3872ce5453b17837ef47fb9f3edc25085ff998ce63543f45ba7af41e7f7d370f"}, + {file = "debugpy-1.8.13-cp312-cp312-win_amd64.whl", hash = "sha256:63ca7670563c320503fea26ac688988d9d6b9c6a12abc8a8cf2e7dd8e5f6b6ea"}, + {file = "debugpy-1.8.13-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:31abc9618be4edad0b3e3a85277bc9ab51a2d9f708ead0d99ffb5bb750e18503"}, + {file = "debugpy-1.8.13-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0bd87557f97bced5513a74088af0b84982b6ccb2e254b9312e29e8a5c4270eb"}, + {file = "debugpy-1.8.13-cp313-cp313-win32.whl", hash = "sha256:5268ae7fdca75f526d04465931cb0bd24577477ff50e8bb03dab90983f4ebd02"}, + {file = "debugpy-1.8.13-cp313-cp313-win_amd64.whl", hash = "sha256:79ce4ed40966c4c1631d0131606b055a5a2f8e430e3f7bf8fd3744b09943e8e8"}, + {file = "debugpy-1.8.13-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:acf39a6e98630959763f9669feddee540745dfc45ad28dbc9bd1f9cd60639391"}, + {file = "debugpy-1.8.13-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:924464d87e7d905eb0d79fb70846558910e906d9ee309b60c4fe597a2e802590"}, + {file = "debugpy-1.8.13-cp38-cp38-win32.whl", hash = "sha256:3dae443739c6b604802da9f3e09b0f45ddf1cf23c99161f3a1a8039f61a8bb89"}, + {file = "debugpy-1.8.13-cp38-cp38-win_amd64.whl", hash = "sha256:ed93c3155fc1f888ab2b43626182174e457fc31b7781cd1845629303790b8ad1"}, + {file = "debugpy-1.8.13-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:6fab771639332bd8ceb769aacf454a30d14d7a964f2012bf9c4e04c60f16e85b"}, + {file = "debugpy-1.8.13-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32b6857f8263a969ce2ca098f228e5cc0604d277447ec05911a8c46cf3e7e307"}, + {file = "debugpy-1.8.13-cp39-cp39-win32.whl", hash = "sha256:f14d2c4efa1809da125ca62df41050d9c7cd9cb9e380a2685d1e453c4d450ccb"}, + {file = "debugpy-1.8.13-cp39-cp39-win_amd64.whl", hash = "sha256:ea869fe405880327497e6945c09365922c79d2a1eed4c3ae04d77ac7ae34b2b5"}, + {file = "debugpy-1.8.13-py2.py3-none-any.whl", hash = "sha256:d4ba115cdd0e3a70942bd562adba9ec8c651fe69ddde2298a1be296fc331906f"}, + {file = "debugpy-1.8.13.tar.gz", hash = "sha256:837e7bef95bdefba426ae38b9a94821ebdc5bea55627879cd48165c90b9e50ce"}, +] + +[[package]] +name = "decorator" +version = "5.2.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, + {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + +[[package]] +name = "deprecated" +version = "1.2.18" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["main"] +files = [ + {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, + {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] + +[[package]] +name = "docker" +version = "7.1.0" +description = "A Python library for the Docker Engine API." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, + {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, +] + +[package.dependencies] +pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} +requests = ">=2.26.0" +urllib3 = ">=1.26.0" + +[package.extras] +dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"] +docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"] +ssh = ["paramiko (>=2.4.3)"] +websockets = ["websocket-client (>=1.3.0)"] + +[[package]] +name = "dockerfile-parse" +version = "2.0.1" +description = "Python library for Dockerfile manipulation" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "dockerfile-parse-2.0.1.tar.gz", hash = "sha256:3184ccdc513221983e503ac00e1aa504a2aa8f84e5de673c46b0b6eee99ec7bc"}, + {file = "dockerfile_parse-2.0.1-py2.py3-none-any.whl", hash = "sha256:bdffd126d2eb26acf1066acb54cb2e336682e1d72b974a40894fac76a4df17f6"}, +] + +[[package]] +name = "dpath" +version = "2.1.3" +description = "Filesystem-like pathing and searching for dictionaries" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "dpath-2.1.3-py3-none-any.whl", hash = "sha256:d9560e03ccd83b3c6f29988b0162ce9b34fd28b9d8dbda46663b20c68d9cdae3"}, + {file = "dpath-2.1.3.tar.gz", hash = "sha256:d1a7a0e6427d0a4156c792c82caf1f0109603f68ace792e36ca4596fd2cb8d9d"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] +markers = {dev = "python_version == \"3.10\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "executing" +version = "2.2.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, + {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] + +[[package]] +name = "face" +version = "24.0.0" +description = "A command-line application framework (and CLI parser). Friendly for users, full-featured for developers." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "face-24.0.0-py3-none-any.whl", hash = "sha256:0e2c17b426fa4639a4e77d1de9580f74a98f4869ba4c7c8c175b810611622cd3"}, + {file = "face-24.0.0.tar.gz", hash = "sha256:611e29a01ac5970f0077f9c577e746d48c082588b411b33a0dd55c4d872949f6"}, +] + +[package.dependencies] +boltons = ">=20.0.0" + +[[package]] +name = "fastjsonschema" +version = "2.21.1" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"}, + {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "fqdn" +version = "1.5.1" +description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" +optional = false +python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" +groups = ["main"] +files = [ + {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, + {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, +] + +[[package]] +name = "frozenlist" +version = "1.5.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, + {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, + {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, + {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, + {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, + {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, + {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, + {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, + {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, + {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, + {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, + {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, + {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, + {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, + {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +description = "Git Object Database" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, + {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, +] + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.44" +description = "GitPython is a Python library used to interact with Git repositories" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, + {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, +] + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[package.extras] +doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] + +[[package]] +name = "glom" +version = "22.1.0" +description = "A declarative object transformer and formatter, for conglomerating nested data." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "glom-22.1.0-py2.py3-none-any.whl", hash = "sha256:5339da206bf3532e01a83a35aca202960ea885156986d190574b779598e9e772"}, + {file = "glom-22.1.0.tar.gz", hash = "sha256:1510c6587a8f9c64a246641b70033cbc5ebde99f02ad245693678038e821aeb5"}, +] + +[package.dependencies] +attrs = "*" +boltons = ">=19.3.0" +face = ">=20.1.0" + +[package.extras] +yaml = ["PyYAML"] + +[[package]] +name = "googleapis-common-protos" +version = "1.69.2" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "googleapis_common_protos-1.69.2-py3-none-any.whl", hash = "sha256:0b30452ff9c7a27d80bfc5718954063e8ab53dd3697093d3bc99581f5fd24212"}, + {file = "googleapis_common_protos-1.69.2.tar.gz", hash = "sha256:3e1b904a27a33c821b4b749fd31d334c0c9c30e6113023d495e48979a3dc9c5f"}, +] + +[package.dependencies] +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0)"] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.7" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, + {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] + +[[package]] +name = "httpx" +version = "0.28.1" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" + +[package.extras] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "importlib-metadata" +version = "7.1.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, + {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] + +[[package]] +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + +[[package]] +name = "ipykernel" +version = "6.29.5" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, + {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=24" +tornado = ">=6.1" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.34.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "ipython-8.34.0-py3-none-any.whl", hash = "sha256:0419883fa46e0baa182c5d50ebb8d6b49df1889fdb70750ad6d8cfe678eda6e3"}, + {file = "ipython-8.34.0.tar.gz", hash = "sha256:c31d658e754673ecc6514583e7dda8069e47136eb62458816b7d1e6625948b5a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt_toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack_data = "*" +traitlets = ">=5.13.0" +typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} + +[package.extras] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli ; python_version < \"3.11\"", "typing_extensions"] +kernel = ["ipykernel"] +matplotlib = ["matplotlib"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] + +[[package]] +name = "isodate" +version = "0.7.2" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"}, + {file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"}, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +description = "Operations with ISO 8601 durations" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, + {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, +] + +[package.dependencies] +arrow = ">=0.15.0" + +[[package]] +name = "jedi" +version = "0.19.2" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, +] + +[package.dependencies] +parso = ">=0.8.4,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "jsii" +version = "1.111.0" +description = "Python client for jsii runtime" +optional = false +python-versions = "~=3.9" +groups = ["main"] +files = [ + {file = "jsii-1.111.0-py3-none-any.whl", hash = "sha256:3084e31173e73d2eefee678c8ee31aa49428830509043057a421a4c0dde94434"}, + {file = "jsii-1.111.0.tar.gz", hash = "sha256:db523ab9b6575c84d6ed8779cdbdc739abd48a7cb0723b66beb84c1a9dc31c7c"}, +] + +[package.dependencies] +attrs = ">=21.2,<26.0" +cattrs = ">=1.8,<24.2" +importlib-resources = ">=5.2.0" +publication = ">=0.0.3" +python-dateutil = "*" +typeguard = ">=2.13.3,<4.5.0" +typing-extensions = ">=3.8,<5.0" + +[[package]] +name = "json5" +version = "0.10.0" +description = "A Python implementation of the JSON5 data format." +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +files = [ + {file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"}, + {file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"}, +] + +[package.extras] +dev = ["build (==1.2.2.post1)", "coverage (==7.5.3)", "mypy (==1.13.0)", "pip (==24.3.1)", "pylint (==3.2.3)", "ruff (==0.7.3)", "twine (==5.1.1)", "uv (==0.5.1)"] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +fqdn = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +idna = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +isoduration = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +jsonpointer = {version = ">1.13", optional = true, markers = "extra == \"format-nongpl\""} +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rfc3339-validator = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +rfc3986-validator = {version = ">0.1.0", optional = true, markers = "extra == \"format-nongpl\""} +rpds-py = ">=0.7.1" +uri-template = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +webcolors = {version = ">=24.6.0", optional = true, markers = "extra == \"format-nongpl\""} + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, + {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "junit-xml" +version = "1.9" +description = "Creates JUnit XML test result documents that can be read by tools such as Jenkins" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "junit-xml-1.9.tar.gz", hash = "sha256:de16a051990d4e25a3982b2dd9e89d671067548718866416faec14d9de56db9f"}, + {file = "junit_xml-1.9-py2.py3-none-any.whl", hash = "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "jupyter-client" +version = "8.6.3" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, + {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, +] + +[package.dependencies] +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko ; sys_platform == \"win32\"", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, + {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyter-events" +version = "0.12.0" +description = "Jupyter Event System library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb"}, + {file = "jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b"}, +] + +[package.dependencies] +jsonschema = {version = ">=4.18.0", extras = ["format-nongpl"]} +packaging = "*" +python-json-logger = ">=2.0.4" +pyyaml = ">=5.3" +referencing = "*" +rfc3339-validator = "*" +rfc3986-validator = ">=0.1.1" +traitlets = ">=5.3" + +[package.extras] +cli = ["click", "rich"] +docs = ["jupyterlite-sphinx", "myst-parser", "pydata-sphinx-theme (>=0.16)", "sphinx (>=8)", "sphinxcontrib-spelling"] +test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "pytest-console-scripts", "rich"] + +[[package]] +name = "jupyter-lsp" +version = "2.2.5" +description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001"}, + {file = "jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da"}, +] + +[package.dependencies] +jupyter-server = ">=1.1.2" + +[[package]] +name = "jupyter-server" +version = "2.15.0" +description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jupyter_server-2.15.0-py3-none-any.whl", hash = "sha256:872d989becf83517012ee669f09604aa4a28097c0bd90b2f424310156c2cdae3"}, + {file = "jupyter_server-2.15.0.tar.gz", hash = "sha256:9d446b8697b4f7337a1b7cdcac40778babdd93ba614b6d68ab1c0c918f1c4084"}, +] + +[package.dependencies] +anyio = ">=3.1.0" +argon2-cffi = ">=21.1" +jinja2 = ">=3.0.3" +jupyter-client = ">=7.4.4" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-events = ">=0.11.0" +jupyter-server-terminals = ">=0.4.4" +nbconvert = ">=6.4.4" +nbformat = ">=5.3.0" +overrides = ">=5.0" +packaging = ">=22.0" +prometheus-client = ">=0.9" +pywinpty = {version = ">=2.0.1", markers = "os_name == \"nt\""} +pyzmq = ">=24" +send2trash = ">=1.8.2" +terminado = ">=0.8.3" +tornado = ">=6.2.0" +traitlets = ">=5.6.0" +websocket-client = ">=1.7" + +[package.extras] +docs = ["ipykernel", "jinja2", "jupyter-client", "myst-parser", "nbformat", "prometheus-client", "pydata-sphinx-theme", "send2trash", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-openapi (>=0.8.0)", "sphinxcontrib-spelling", "sphinxemoji", "tornado", "typing-extensions"] +test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0,<9)", "pytest-console-scripts", "pytest-jupyter[server] (>=0.7)", "pytest-timeout", "requests"] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +description = "A Jupyter Server Extension Providing Terminals." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa"}, + {file = "jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269"}, +] + +[package.dependencies] +pywinpty = {version = ">=2.0.3", markers = "os_name == \"nt\""} +terminado = ">=0.8.3" + +[package.extras] +docs = ["jinja2", "jupyter-server", "mistune (<4.0)", "myst-parser", "nbformat", "packaging", "pydata-sphinx-theme", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado"] +test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (>=0.5.3)", "pytest-timeout"] + +[[package]] +name = "jupyterlab" +version = "4.3.6" +description = "JupyterLab computational environment" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyterlab-4.3.6-py3-none-any.whl", hash = "sha256:fc9eb0455562a56a9bd6d2977cf090842f321fa1a298fcee9bf8c19de353d5fd"}, + {file = "jupyterlab-4.3.6.tar.gz", hash = "sha256:2900ffdbfca9ed37c4ad7fdda3eb76582fd945d46962af3ac64741ae2d6b2ff4"}, +] + +[package.dependencies] +async-lru = ">=1.0.0" +httpx = ">=0.25.0" +ipykernel = ">=6.5.0" +jinja2 = ">=3.0.3" +jupyter-core = "*" +jupyter-lsp = ">=2.0.0" +jupyter-server = ">=2.4.0,<3" +jupyterlab-server = ">=2.27.1,<3" +notebook-shim = ">=0.2" +packaging = "*" +setuptools = ">=40.8.0" +tomli = {version = ">=1.2.2", markers = "python_version < \"3.11\""} +tornado = ">=6.2.0" +traitlets = "*" + +[package.extras] +dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.6.9)"] +docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<8.1.0)", "sphinx-copybutton"] +docs-screenshots = ["altair (==5.4.1)", "ipython (==8.16.1)", "ipywidgets (==8.1.5)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.2.post3)", "matplotlib (==3.9.2)", "nbconvert (>=7.0.0)", "pandas (==2.2.3)", "scipy (==1.14.1)", "vega-datasets (==0.9.0)"] +test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] +upgrade-extension = ["copier (>=9,<10)", "jinja2-time (<0.3)", "pydantic (<3.0)", "pyyaml-include (<3.0)", "tomli-w (<2.0)"] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +description = "Pygments theme using JupyterLab CSS variables" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, + {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, +] + +[[package]] +name = "jupyterlab-server" +version = "2.27.3" +description = "A set of server components for JupyterLab and JupyterLab like applications." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4"}, + {file = "jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4"}, +] + +[package.dependencies] +babel = ">=2.10" +jinja2 = ">=3.0.3" +json5 = ">=0.9.0" +jsonschema = ">=4.18.0" +jupyter-server = ">=1.21,<3" +packaging = ">=21.3" +requests = ">=2.31" + +[package.extras] +docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"] +openapi = ["openapi-core (>=0.18.0,<0.19.0)", "ruamel-yaml"] +test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.8.0)", "pytest (>=7.0,<8)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] + +[[package]] +name = "lark" +version = "1.2.2" +description = "a modern parsing library" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "lark-1.2.2-py3-none-any.whl", hash = "sha256:c2276486b02f0f1b90be155f2c8ba4a8e194d42775786db622faccd652d8e80c"}, + {file = "lark-1.2.2.tar.gz", hash = "sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80"}, +] + +[package.extras] +atomic-cache = ["atomicwrites"] +interegular = ["interegular (>=0.3.1,<0.4.0)"] +nearley = ["js2py"] +regex = ["regex"] + +[[package]] +name = "license-expression" +version = "30.4.1" +description = "license-expression is a comprehensive utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "license_expression-30.4.1-py3-none-any.whl", hash = "sha256:679646bc3261a17690494a3e1cada446e5ee342dbd87dcfa4a0c24cc5dce13ee"}, + {file = "license_expression-30.4.1.tar.gz", hash = "sha256:9f02105f9e0fcecba6a85dfbbed7d94ea1c3a70cf23ddbfb5adf3438a6f6fce0"}, +] + +[package.dependencies] +"boolean.py" = ">=4.0" + +[package.extras] +docs = ["Sphinx (>=5.0.2)", "doc8 (>=0.11.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-reredirects (>=0.1.2)", "sphinx-rtd-dark-mode (>=1.3.0)", "sphinx-rtd-theme (>=1.0.0)", "sphinxcontrib-apidoc (>=0.4.0)"] +testing = ["black", "isort", "pytest (>=6,!=7.0.0)", "pytest-xdist (>=2)", "twine"] + +[[package]] +name = "markdown" +version = "3.7" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, + {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, +] + +[package.extras] +docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mistune" +version = "3.1.3" +description = "A sane and fast Markdown parser with useful plugins and renderers" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9"}, + {file = "mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0"}, +] + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.11\""} + +[[package]] +name = "multidict" +version = "6.3.0" +description = "multidict implementation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "multidict-6.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3bcb8cdfeb08cef0138d696e52ec08fffaf009ef4b1c7c5a40340af672bd9b60"}, + {file = "multidict-6.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67caf9435b1f0115042cbc37e9d60475891b2d9b2a711ade0876580da2a5e0df"}, + {file = "multidict-6.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:77e0406dc247851a1e250fdcfd46d13cdecc2ac5b8d1d038dea216da2e7fa9b7"}, + {file = "multidict-6.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6193564d51d02a4aa6fc60bb40f04b65840e70e124282a7337cd94a9ed1b0b81"}, + {file = "multidict-6.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72a1159144e4ad7d152b2c3f75bed8c9b60ad06e9da322e1965981f8371c0dee"}, + {file = "multidict-6.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb3269a3641b868ab7856c3cd939782ee1c802fa120b470b597286d2f9e93aee"}, + {file = "multidict-6.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79658460926a1c48b2ac10196a673cc7d81857dc268ca593a66fd661ed1b77ea"}, + {file = "multidict-6.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd289ef26241bc2c0b7b047417a33786790bf2a01f6c92d9688eb2693c9116df"}, + {file = "multidict-6.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a48fd39ba69f399f39b911885469bdfedd1de4a095146211e39e8b5db0e3a937"}, + {file = "multidict-6.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:478d29a270785fc58632c9d1e0f59f595d3fb06c5e63ac117136dba2d0e5f4f9"}, + {file = "multidict-6.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:31c68e103b253643cf9b4704f5d655997c116626b20ef3bb953992b7f447485d"}, + {file = "multidict-6.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:69983ee3e649e41dc41ddea68f6c2ab64d7f19fcab4e21a72a1accf6482c5605"}, + {file = "multidict-6.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5bd70d75b4bbfc0a26fdbfc72e260b70090637020b0d7d27eb26c7ae5edf16da"}, + {file = "multidict-6.3.0-cp310-cp310-win32.whl", hash = "sha256:82bd76a12588bd2bb27965aaf9fcff2258ffb03c9a290368288ed4571ed539ab"}, + {file = "multidict-6.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:430cb9ee0cf2d5a2fa91e78f306544352009dbda9ebb4bd04c5785e67fc04953"}, + {file = "multidict-6.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:904895323a59dd50387cddd1337c3eac48ec0fe6cb4d2e4106dc1a3b80091727"}, + {file = "multidict-6.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1aa6e1a605c602c119a22368f7d3b9dfb102a7cd69edae5a89e55da34fd64c2b"}, + {file = "multidict-6.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72574693fad34d075a54fa771984a1a8809a9eff880b731152078e81c7d8c80f"}, + {file = "multidict-6.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72d15a61b76fa4b814390c21defd772893d1152157e3297e6afb328ef32d1711"}, + {file = "multidict-6.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bf0d43f2c56b600c6895c25a420e4b46c6a5acf91d6c9a6300713496f70e63b"}, + {file = "multidict-6.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6be1182387fb9a95fd8a8cbfab2f2b61d61ea0ab4699780d4300ea207c8f8d71"}, + {file = "multidict-6.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ba7d12ff77cadb59519aba674cac3a0b16d0fc5abaa3ef79b5ff3806e52e991"}, + {file = "multidict-6.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eeca775446b00826ec8ca34eed8c902c6a1ae7dc42d78094b1f73ecd38d1dcf8"}, + {file = "multidict-6.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a1a5f50ccc57a317db68582411ce6aca076ee3bc386290b416a5783e807afa1"}, + {file = "multidict-6.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6989ea9326f9a9171edc920a459003b1deffc6cb48605b8f2610846c7fbd201a"}, + {file = "multidict-6.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9efe23b3d0671eeb8b1e70791612538dcdc1b39275563ca78f338aa0ce9619d2"}, + {file = "multidict-6.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:560af476d03256a65bd4ff05c83e7869ec3c2b40579d4ed1ac74075ef19e02bf"}, + {file = "multidict-6.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:091f3dfe63e9f48ae6c7da8d638e8d8e050ff11e348f69bafc20e65d11f7aa48"}, + {file = "multidict-6.3.0-cp311-cp311-win32.whl", hash = "sha256:38e60e400e07ba678a755cc89b09955a49acb0188d47aee2829ed4efc1ccb053"}, + {file = "multidict-6.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:65e763769cec2fa9c79b70555e3f2a7903747d127ab14ec9eaa5f00763bb4152"}, + {file = "multidict-6.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2a83896925e3a2ddec736c48255572e6f15a95339024c02b800dab28e63461a3"}, + {file = "multidict-6.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74e45386528db2d9346618276e8746507ec6ccb91512c5f02d2049ccafb0576e"}, + {file = "multidict-6.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc15e81587b652bbeba9d4d7d3707bcaaa1d7b4aab2024c14be477662003a211"}, + {file = "multidict-6.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a716344c6e92551bdf842b999c7d7b34a13e32facf3e6c5942690c9883d45e3a"}, + {file = "multidict-6.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:299070f503a9a99e4af38d07da784ced28377cc62b678084b9e2e48fa51c57d3"}, + {file = "multidict-6.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e87a8635a7db577617b50bd2f2080744ed20e556750b97e4f9988e6d20d3941"}, + {file = "multidict-6.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab4ea5f49166b990411c522c1f5f901014932ead15a463616ec93e10fff2c05"}, + {file = "multidict-6.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2cef02d3878804909283549bc10d4789a14c610fcd286f17cd94a195b21fe469"}, + {file = "multidict-6.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a466c14574152b7caed5d76f1e46ae2963d33f5b0eb2dd915fa33870183b0f26"}, + {file = "multidict-6.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:883c07b404229e98d30b1ef64f07d4e3422d431ecd727c2ebba8549f70b2ba16"}, + {file = "multidict-6.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:767ed12b1662fac32d84eb43707e4f778b12554fc12ce3c8f7793a02111a9e32"}, + {file = "multidict-6.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ef116022119d3053ecce8ba62109c8b54a650a537b183e79de1db4078894a8"}, + {file = "multidict-6.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ad737617d38c55c0b72cd3ea01bf5cbab0d75123de5e52c39643ddc6465cb5f"}, + {file = "multidict-6.3.0-cp312-cp312-win32.whl", hash = "sha256:3d783be54d076843f58bf061fdaf1071789fb924fb35a0eb84dbc2c8b68499a2"}, + {file = "multidict-6.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:6fbe184451480c17f1f8dac160c9f3f6d243010fdb8416de4d3d7ee69ea65aa4"}, + {file = "multidict-6.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90c3c60985e7da13e44aeaaf2e1c10fe1b7825788a18c82b0f9eaeb6c4e9d9c6"}, + {file = "multidict-6.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:80935f26af0eec490c4e52381a28f39b08c2bc4ef4562448890027e4a4cfa3a4"}, + {file = "multidict-6.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e18db3b38ac30d8cc1ddbdfc7f6d3d814c1abb8936c57bd1c09c5da02873c8c4"}, + {file = "multidict-6.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8836280abc310ea6260dac93126645d21284885008c317c2ac4281a90f1398"}, + {file = "multidict-6.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5737f40ffb1926b8bf38d32fdac48a27c441b757f1bf44cbaa100dafef049a01"}, + {file = "multidict-6.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6df1b707adab4858dfaa74877f60f07d9d6be01a57015cf4e62aa8f00f3576b"}, + {file = "multidict-6.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:162af9c2e0743465a47e2b32d9b0a7c260b7843629b5e6f0a8a92819b5a40d27"}, + {file = "multidict-6.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc4e82c5db4e65703c842b0947699dd958a7262a8b854d1c19bbfe2d27be9333"}, + {file = "multidict-6.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5a922601cb94f427dd96eec3a7c776537ce7490f2beb69e88a81859e537343e4"}, + {file = "multidict-6.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3da30fb560b37cede31306b890c982213a23fa7335d368cdc16cf7034170860b"}, + {file = "multidict-6.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1996d963016e6486b6a672f64f868e6b4e7e9e2caf710994df11b04790903e"}, + {file = "multidict-6.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9584441b7a603a12aa382adf8c093ddc5a22328a108dabc6a4a112fa5b4facae"}, + {file = "multidict-6.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71a8ce430f6c341725aefc0031626c484e0858fd95d1bf3625e0d27919d8edca"}, + {file = "multidict-6.3.0-cp313-cp313-win32.whl", hash = "sha256:b7d3053742a9a559dda8598a52e0c1bcbd18258cc199cba52137ce8c8e92c537"}, + {file = "multidict-6.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:6b7e30e4246c2cd5e6c2c907486d235e4f3e8a39979744e9e0b8090629c62da4"}, + {file = "multidict-6.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be034aa2841c57ea43a55bc123d8f3f41cc2d3d102a22390c863791ba6bf23f1"}, + {file = "multidict-6.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:168a02178b7e980899f3475ff008436eaab035d50e39cb8f7de641bbe9bbc3a6"}, + {file = "multidict-6.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c4a6ec469c30f4162745d6485b778432c497032b8a2cb3a0332eea9d3b6aef6"}, + {file = "multidict-6.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b293804f4b53be6297bc2afdeaf15aff76c1b6be69f3a3da785143eebdfb656"}, + {file = "multidict-6.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5eb2a8415a891564054447a327a7ef7ec90e69e0e63d85d1ffb03f82e102c740"}, + {file = "multidict-6.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db61c6ae9ee7268dc69a078ea22deaaf861350ad2e4c194c70798b8ec9789131"}, + {file = "multidict-6.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88548dec2f6f69e547252fe3113bf1b3b08c0879f5b44633927be07ed18c5cc0"}, + {file = "multidict-6.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:864684b36e0488715aac531785abe7514e872bfe83ecc768828e9ddaadeed320"}, + {file = "multidict-6.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71d92e846541669ae1d11e00906c408c1dc9890196e13f11d4d62bd235ac9ddb"}, + {file = "multidict-6.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c5223ab45639ce93c7233d518f6c3199422b49dbd0ebfb1d7917b5da2636712e"}, + {file = "multidict-6.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:56225de73d69f5ee6b783648eb1936e1bbe874a529cb1e15d64038904c54efb2"}, + {file = "multidict-6.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:66c108d8e02da2febb0aa7d7002e14c4a0571460993c9edf8249393cdae7eeef"}, + {file = "multidict-6.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b95d96a0640decaa24cd9cf386fd4d8a96c973aafa42dd9c65609f9f0d66cc34"}, + {file = "multidict-6.3.0-cp313-cp313t-win32.whl", hash = "sha256:6b25953a1d6a97746becbd663b49e3b436a5001c995a62662d65835a2ba996a7"}, + {file = "multidict-6.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d9c2b1ca98e5454b78cd434f29fc33eb8f8a2f343efc5f975225d92070b9f7f6"}, + {file = "multidict-6.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7594756e9607e5ffbc16c7f3b1715c0f7224a82ac389f0087fa5faaa65fb96e2"}, + {file = "multidict-6.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ae6ea8e610143348199c1db9fea5392f6b11a0e37ac8e8e7cae9c408625c5d0"}, + {file = "multidict-6.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:257bd1540b7c32982b17e86f024985b9c591da8542a3fb350ed7dad42d725998"}, + {file = "multidict-6.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43ad68da1385738a1e96a188029f092e88381352e697a0687784191c1d45845b"}, + {file = "multidict-6.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e31697466812ff1cdabd7a9db7fb7f37d319e58e4c8a2ce1065012c1a5cfebde"}, + {file = "multidict-6.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e0db6ea39ac96ae2ccc582291633ee55ae93c9cc6dca1f7ab4dd9f0b7e08f8a"}, + {file = "multidict-6.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac6ea86ac161198653dba8f0af047d7b718318ba6d6bf4c719bc288ac21017b"}, + {file = "multidict-6.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:150cc33d5d972c1bd7b0e5c446cccfa4dc6b8e185ef54dab3327db101149f797"}, + {file = "multidict-6.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2264957a0e0fb92d8d538a27738178c48628c65ef0ec6b068d64d2e5d11cc00d"}, + {file = "multidict-6.3.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6befa3c621caf26f5affc6121bf863b8f049f862a7a10aabb0d19cce34c6e363"}, + {file = "multidict-6.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7074f10bf324e4d38208e1bcc2b6abd87901675b1a567507035ff1e7403378c0"}, + {file = "multidict-6.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fa476e5f763f59270bda050695e22078b2461dc14bc62a0d27756c5906bca86c"}, + {file = "multidict-6.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4c3da8aa4b28f83382f0dee288d030eb0fa212df9632dc2fe4e4fc2a8e2577a4"}, + {file = "multidict-6.3.0-cp39-cp39-win32.whl", hash = "sha256:02a239ad4e35d3c033e701bc8b7beb05013fc6617b13948497cf50e9e9c12f7f"}, + {file = "multidict-6.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:8895999e0de5ddddddd146bb1bfc0316af808580637fea158873b84785f7a9e9"}, + {file = "multidict-6.3.0-py3-none-any.whl", hash = "sha256:9ca652d9c6f68535537d75502b549ed0ca07fa6d3908f84f29f92148ec7310f2"}, + {file = "multidict-6.3.0.tar.gz", hash = "sha256:2cf3e0781febf9f093eff3eca2d6dd7954ef2969ff46f6cd95173a4db8397fd8"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "mypy" +version = "1.15.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, + {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, + {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}, + {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}, + {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}, + {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}, + {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}, + {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}, + {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}, + {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}, + {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}, + {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}, + {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}, + {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}, + {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}, + {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}, + {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}, + {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}, + {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}, + {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}, + {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}, + {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}, + {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}, + {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}, + {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"}, + {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"}, + {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"}, + {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"}, + {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"}, + {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"}, + {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}, + {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nbclient" +version = "0.10.2" +description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +optional = false +python-versions = ">=3.9.0" +groups = ["main"] +files = [ + {file = "nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d"}, + {file = "nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193"}, +] + +[package.dependencies] +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +nbformat = ">=5.1" +traitlets = ">=5.4" + +[package.extras] +dev = ["pre-commit"] +docs = ["autodoc-traits", "flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "mock", "moto", "myst-parser", "nbconvert (>=7.1.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling", "testpath", "xmltodict"] +test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.1.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] + +[[package]] +name = "nbconvert" +version = "7.16.6" +description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b"}, + {file = "nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +bleach = {version = "!=5.0.0", extras = ["css"]} +defusedxml = "*" +jinja2 = ">=3.0" +jupyter-core = ">=4.7" +jupyterlab-pygments = "*" +markupsafe = ">=2.0" +mistune = ">=2.0.3,<4" +nbclient = ">=0.5.0" +nbformat = ">=5.7" +packaging = "*" +pandocfilters = ">=1.4.1" +pygments = ">=2.4.1" +traitlets = ">=5.1" + +[package.extras] +all = ["flaky", "ipykernel", "ipython", "ipywidgets (>=7.5)", "myst-parser", "nbsphinx (>=0.2.12)", "playwright", "pydata-sphinx-theme", "pyqtwebengine (>=5.15)", "pytest (>=7)", "sphinx (==5.0.2)", "sphinxcontrib-spelling", "tornado (>=6.1)"] +docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] +qtpdf = ["pyqtwebengine (>=5.15)"] +qtpng = ["pyqtwebengine (>=5.15)"] +serve = ["tornado (>=6.1)"] +test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest (>=7)"] +webpdf = ["playwright"] + +[[package]] +name = "nbformat" +version = "5.10.4" +description = "The Jupyter Notebook format" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"}, + {file = "nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a"}, +] + +[package.dependencies] +fastjsonschema = ">=2.15" +jsonschema = ">=2.6" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +traitlets = ">=5.1" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["pep440", "pre-commit", "pytest", "testpath"] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +groups = ["main"] +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + +[[package]] +name = "networkx" +version = "2.6.3" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "networkx-2.6.3-py3-none-any.whl", hash = "sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef"}, + {file = "networkx-2.6.3.tar.gz", hash = "sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51"}, +] + +[package.extras] +default = ["matplotlib (>=3.3)", "numpy (>=1.19)", "pandas (>=1.1)", "scipy (>=1.5,!=1.6.1)"] +developer = ["black (==21.5b1)", "pre-commit (>=2.12)"] +doc = ["nb2plots (>=0.6)", "numpydoc (>=1.1)", "pillow (>=8.2)", "pydata-sphinx-theme (>=0.6,<1.0)", "sphinx (>=4.0,<5.0)", "sphinx-gallery (>=0.9,<1.0)", "texext (>=0.6.6)"] +extra = ["lxml (>=4.5)", "pydot (>=1.4.1)", "pygraphviz (>=1.7)"] +test = ["codecov (>=2.1)", "pytest (>=6.2)", "pytest-cov (>=2.12)"] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +description = "A shim layer for notebook traits and config" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef"}, + {file = "notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb"}, +] + +[package.dependencies] +jupyter-server = ">=1.8,<3" + +[package.extras] +test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"] + +[[package]] +name = "numpy" +version = "2.2.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9"}, + {file = "numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae"}, + {file = "numpy-2.2.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775"}, + {file = "numpy-2.2.4-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9"}, + {file = "numpy-2.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2"}, + {file = "numpy-2.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020"}, + {file = "numpy-2.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3"}, + {file = "numpy-2.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017"}, + {file = "numpy-2.2.4-cp310-cp310-win32.whl", hash = "sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a"}, + {file = "numpy-2.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542"}, + {file = "numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4"}, + {file = "numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4"}, + {file = "numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f"}, + {file = "numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880"}, + {file = "numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1"}, + {file = "numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5"}, + {file = "numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687"}, + {file = "numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6"}, + {file = "numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09"}, + {file = "numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91"}, + {file = "numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4"}, + {file = "numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854"}, + {file = "numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24"}, + {file = "numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee"}, + {file = "numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba"}, + {file = "numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592"}, + {file = "numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb"}, + {file = "numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f"}, + {file = "numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00"}, + {file = "numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146"}, + {file = "numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7"}, + {file = "numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0"}, + {file = "numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392"}, + {file = "numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc"}, + {file = "numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298"}, + {file = "numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7"}, + {file = "numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6"}, + {file = "numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd"}, + {file = "numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c"}, + {file = "numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3"}, + {file = "numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8"}, + {file = "numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39"}, + {file = "numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd"}, + {file = "numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0"}, + {file = "numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960"}, + {file = "numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8"}, + {file = "numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc"}, + {file = "numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff"}, + {file = "numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286"}, + {file = "numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d"}, + {file = "numpy-2.2.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8"}, + {file = "numpy-2.2.4-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c"}, + {file = "numpy-2.2.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d"}, + {file = "numpy-2.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d"}, + {file = "numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f"}, +] + +[[package]] +name = "openai" +version = "0.28.1" +description = "Python client library for the OpenAI API" +optional = false +python-versions = ">=3.7.1" +groups = ["main"] +files = [ + {file = "openai-0.28.1-py3-none-any.whl", hash = "sha256:d18690f9e3d31eedb66b57b88c2165d760b24ea0a01f150dd3f068155088ce68"}, + {file = "openai-0.28.1.tar.gz", hash = "sha256:4be1dad329a65b4ce1a660fe6d5431b438f429b5855c883435f0f7fcb6d2dcc8"}, +] + +[package.dependencies] +aiohttp = "*" +requests = ">=2.20" +tqdm = "*" + +[package.extras] +datalib = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] +dev = ["black (>=21.6b0,<22.0)", "pytest (==6.*)", "pytest-asyncio", "pytest-mock"] +embeddings = ["matplotlib", "numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "plotly", "scikit-learn (>=1.0.2)", "scipy", "tenacity (>=8.0.1)"] +wandb = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)", "wandb"] + +[[package]] +name = "opentelemetry-api" +version = "1.25.0" +description = "OpenTelemetry Python API" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "opentelemetry_api-1.25.0-py3-none-any.whl", hash = "sha256:757fa1aa020a0f8fa139f8959e53dec2051cc26b832e76fa839a6d76ecefd737"}, + {file = "opentelemetry_api-1.25.0.tar.gz", hash = "sha256:77c4985f62f2614e42ce77ee4c9da5fa5f0bc1e1821085e9a47533a9323ae869"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +importlib-metadata = ">=6.0,<=7.1" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.25.0" +description = "OpenTelemetry Protobuf encoding" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "opentelemetry_exporter_otlp_proto_common-1.25.0-py3-none-any.whl", hash = "sha256:15637b7d580c2675f70246563363775b4e6de947871e01d0f4e3881d1848d693"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.25.0.tar.gz", hash = "sha256:c93f4e30da4eee02bacd1e004eb82ce4da143a2f8e15b987a9f603e0a85407d3"}, +] + +[package.dependencies] +opentelemetry-proto = "1.25.0" + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.25.0" +description = "OpenTelemetry Collector Protobuf over HTTP Exporter" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "opentelemetry_exporter_otlp_proto_http-1.25.0-py3-none-any.whl", hash = "sha256:2eca686ee11b27acd28198b3ea5e5863a53d1266b91cda47c839d95d5e0541a6"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.25.0.tar.gz", hash = "sha256:9f8723859e37c75183ea7afa73a3542f01d0fd274a5b97487ea24cb683d7d684"}, +] + +[package.dependencies] +deprecated = ">=1.2.6" +googleapis-common-protos = ">=1.52,<2.0" +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-proto-common = "1.25.0" +opentelemetry-proto = "1.25.0" +opentelemetry-sdk = ">=1.25.0,<1.26.0" +requests = ">=2.7,<3.0" + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.46b0" +description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "opentelemetry_instrumentation-0.46b0-py3-none-any.whl", hash = "sha256:89cd721b9c18c014ca848ccd11181e6b3fd3f6c7669e35d59c48dc527408c18b"}, + {file = "opentelemetry_instrumentation-0.46b0.tar.gz", hash = "sha256:974e0888fb2a1e01c38fbacc9483d024bb1132aad92d6d24e2e5543887a7adda"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.4,<2.0" +setuptools = ">=16.0" +wrapt = ">=1.0.0,<2.0.0" + +[[package]] +name = "opentelemetry-instrumentation-requests" +version = "0.46b0" +description = "OpenTelemetry requests instrumentation" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "opentelemetry_instrumentation_requests-0.46b0-py3-none-any.whl", hash = "sha256:a8c2472800d8686f3f286cd524b8746b386154092e85a791ba14110d1acc9b81"}, + {file = "opentelemetry_instrumentation_requests-0.46b0.tar.gz", hash = "sha256:ef0ad63bfd0d52631daaf7d687e763dbd89b465f5cb052f12a4e67e5e3d181e4"}, +] + +[package.dependencies] +opentelemetry-api = ">=1.12,<2.0" +opentelemetry-instrumentation = "0.46b0" +opentelemetry-semantic-conventions = "0.46b0" +opentelemetry-util-http = "0.46b0" + +[package.extras] +instruments = ["requests (>=2.0,<3.0)"] + +[[package]] +name = "opentelemetry-proto" +version = "1.25.0" +description = "OpenTelemetry Python Proto" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "opentelemetry_proto-1.25.0-py3-none-any.whl", hash = "sha256:f07e3341c78d835d9b86665903b199893befa5e98866f63d22b00d0b7ca4972f"}, + {file = "opentelemetry_proto-1.25.0.tar.gz", hash = "sha256:35b6ef9dc4a9f7853ecc5006738ad40443701e52c26099e197895cbda8b815a3"}, +] + +[package.dependencies] +protobuf = ">=3.19,<5.0" + +[[package]] +name = "opentelemetry-sdk" +version = "1.25.0" +description = "OpenTelemetry Python SDK" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "opentelemetry_sdk-1.25.0-py3-none-any.whl", hash = "sha256:d97ff7ec4b351692e9d5a15af570c693b8715ad78b8aafbec5c7100fe966b4c9"}, + {file = "opentelemetry_sdk-1.25.0.tar.gz", hash = "sha256:ce7fc319c57707ef5bf8b74fb9f8ebdb8bfafbe11898410e0d2a761d08a98ec7"}, +] + +[package.dependencies] +opentelemetry-api = "1.25.0" +opentelemetry-semantic-conventions = "0.46b0" +typing-extensions = ">=3.7.4" + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.46b0" +description = "OpenTelemetry Semantic Conventions" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "opentelemetry_semantic_conventions-0.46b0-py3-none-any.whl", hash = "sha256:6daef4ef9fa51d51855d9f8e0ccd3a1bd59e0e545abe99ac6203804e36ab3e07"}, + {file = "opentelemetry_semantic_conventions-0.46b0.tar.gz", hash = "sha256:fbc982ecbb6a6e90869b15c1673be90bd18c8a56ff1cffc0864e38e2edffaefa"}, +] + +[package.dependencies] +opentelemetry-api = "1.25.0" + +[[package]] +name = "opentelemetry-util-http" +version = "0.46b0" +description = "Web util for OpenTelemetry" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "opentelemetry_util_http-0.46b0-py3-none-any.whl", hash = "sha256:8dc1949ce63caef08db84ae977fdc1848fe6dc38e6bbaad0ae3e6ecd0d451629"}, + {file = "opentelemetry_util_http-0.46b0.tar.gz", hash = "sha256:03b6e222642f9c7eae58d9132343e045b50aca9761fcb53709bd2b663571fdf6"}, +] + +[[package]] +name = "orjson" +version = "3.10.16" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "orjson-3.10.16-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4cb473b8e79154fa778fb56d2d73763d977be3dcc140587e07dbc545bbfc38f8"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:622a8e85eeec1948690409a19ca1c7d9fd8ff116f4861d261e6ae2094fe59a00"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c682d852d0ce77613993dc967e90e151899fe2d8e71c20e9be164080f468e370"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c520ae736acd2e32df193bcff73491e64c936f3e44a2916b548da048a48b46b"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:134f87c76bfae00f2094d85cfab261b289b76d78c6da8a7a3b3c09d362fd1e06"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b59afde79563e2cf37cfe62ee3b71c063fd5546c8e662d7fcfc2a3d5031a5c4c"}, + {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113602f8241daaff05d6fad25bd481d54c42d8d72ef4c831bb3ab682a54d9e15"}, + {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4fc0077d101f8fab4031e6554fc17b4c2ad8fdbc56ee64a727f3c95b379e31da"}, + {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9c6bf6ff180cd69e93f3f50380224218cfab79953a868ea3908430bcfaf9cb5e"}, + {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5673eadfa952f95a7cd76418ff189df11b0a9c34b1995dff43a6fdbce5d63bf4"}, + {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5fe638a423d852b0ae1e1a79895851696cb0d9fa0946fdbfd5da5072d9bb9551"}, + {file = "orjson-3.10.16-cp310-cp310-win32.whl", hash = "sha256:33af58f479b3c6435ab8f8b57999874b4b40c804c7a36b5cc6b54d8f28e1d3dd"}, + {file = "orjson-3.10.16-cp310-cp310-win_amd64.whl", hash = "sha256:0338356b3f56d71293c583350af26f053017071836b07e064e92819ecf1aa055"}, + {file = "orjson-3.10.16-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44fcbe1a1884f8bc9e2e863168b0f84230c3d634afe41c678637d2728ea8e739"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78177bf0a9d0192e0b34c3d78bcff7fe21d1b5d84aeb5ebdfe0dbe637b885225"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12824073a010a754bb27330cad21d6e9b98374f497f391b8707752b96f72e741"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddd41007e56284e9867864aa2f29f3136bb1dd19a49ca43c0b4eda22a579cf53"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0877c4d35de639645de83666458ca1f12560d9fa7aa9b25d8bb8f52f61627d14"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a09a539e9cc3beead3e7107093b4ac176d015bec64f811afb5965fce077a03c"}, + {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b98bc9b40610fec971d9a4d67bb2ed02eec0a8ae35f8ccd2086320c28526ca"}, + {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0ce243f5a8739f3a18830bc62dc2e05b69a7545bafd3e3249f86668b2bcd8e50"}, + {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:64792c0025bae049b3074c6abe0cf06f23c8e9f5a445f4bab31dc5ca23dbf9e1"}, + {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea53f7e68eec718b8e17e942f7ca56c6bd43562eb19db3f22d90d75e13f0431d"}, + {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a741ba1a9488c92227711bde8c8c2b63d7d3816883268c808fbeada00400c164"}, + {file = "orjson-3.10.16-cp311-cp311-win32.whl", hash = "sha256:c7ed2c61bb8226384c3fdf1fb01c51b47b03e3f4536c985078cccc2fd19f1619"}, + {file = "orjson-3.10.16-cp311-cp311-win_amd64.whl", hash = "sha256:cd67d8b3e0e56222a2e7b7f7da9031e30ecd1fe251c023340b9f12caca85ab60"}, + {file = "orjson-3.10.16-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6d3444abbfa71ba21bb042caa4b062535b122248259fdb9deea567969140abca"}, + {file = "orjson-3.10.16-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:30245c08d818fdcaa48b7d5b81499b8cae09acabb216fe61ca619876b128e184"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ba1d0baa71bf7579a4ccdcf503e6f3098ef9542106a0eca82395898c8a500a"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb0beefa5ef3af8845f3a69ff2a4aa62529b5acec1cfe5f8a6b4141033fd46ef"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6daa0e1c9bf2e030e93c98394de94506f2a4d12e1e9dadd7c53d5e44d0f9628e"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9da9019afb21e02410ef600e56666652b73eb3e4d213a0ec919ff391a7dd52aa"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:daeb3a1ee17b69981d3aae30c3b4e786b0f8c9e6c71f2b48f1aef934f63f38f4"}, + {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fed80eaf0e20a31942ae5d0728849862446512769692474be5e6b73123a23b"}, + {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73390ed838f03764540a7bdc4071fe0123914c2cc02fb6abf35182d5fd1b7a42"}, + {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a22bba012a0c94ec02a7768953020ab0d3e2b884760f859176343a36c01adf87"}, + {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5385bbfdbc90ff5b2635b7e6bebf259652db00a92b5e3c45b616df75b9058e88"}, + {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02c6279016346e774dd92625d46c6c40db687b8a0d685aadb91e26e46cc33e1e"}, + {file = "orjson-3.10.16-cp312-cp312-win32.whl", hash = "sha256:7ca55097a11426db80f79378e873a8c51f4dde9ffc22de44850f9696b7eb0e8c"}, + {file = "orjson-3.10.16-cp312-cp312-win_amd64.whl", hash = "sha256:86d127efdd3f9bf5f04809b70faca1e6836556ea3cc46e662b44dab3fe71f3d6"}, + {file = "orjson-3.10.16-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:148a97f7de811ba14bc6dbc4a433e0341ffd2cc285065199fb5f6a98013744bd"}, + {file = "orjson-3.10.16-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1d960c1bf0e734ea36d0adc880076de3846aaec45ffad29b78c7f1b7962516b8"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a318cd184d1269f68634464b12871386808dc8b7c27de8565234d25975a7a137"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df23f8df3ef9223d1d6748bea63fca55aae7da30a875700809c500a05975522b"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b94dda8dd6d1378f1037d7f3f6b21db769ef911c4567cbaa962bb6dc5021cf90"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f12970a26666a8775346003fd94347d03ccb98ab8aa063036818381acf5f523e"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15a1431a245d856bd56e4d29ea0023eb4d2c8f71efe914beb3dee8ab3f0cd7fb"}, + {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c83655cfc247f399a222567d146524674a7b217af7ef8289c0ff53cfe8db09f0"}, + {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fa59ae64cb6ddde8f09bdbf7baf933c4cd05734ad84dcf4e43b887eb24e37652"}, + {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ca5426e5aacc2e9507d341bc169d8af9c3cbe88f4cd4c1cf2f87e8564730eb56"}, + {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6fd5da4edf98a400946cd3a195680de56f1e7575109b9acb9493331047157430"}, + {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:980ecc7a53e567169282a5e0ff078393bac78320d44238da4e246d71a4e0e8f5"}, + {file = "orjson-3.10.16-cp313-cp313-win32.whl", hash = "sha256:28f79944dd006ac540a6465ebd5f8f45dfdf0948ff998eac7a908275b4c1add6"}, + {file = "orjson-3.10.16-cp313-cp313-win_amd64.whl", hash = "sha256:fe0a145e96d51971407cb8ba947e63ead2aa915db59d6631a355f5f2150b56b7"}, + {file = "orjson-3.10.16-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c35b5c1fb5a5d6d2fea825dec5d3d16bea3c06ac744708a8e1ff41d4ba10cdf1"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9aac7ecc86218b4b3048c768f227a9452287001d7548500150bb75ee21bf55d"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6e19f5102fff36f923b6dfdb3236ec710b649da975ed57c29833cb910c5a73ab"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17210490408eb62755a334a6f20ed17c39f27b4f45d89a38cd144cd458eba80b"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbbe04451db85916e52a9f720bd89bf41f803cf63b038595674691680cbebd1b"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a966eba501a3a1f309f5a6af32ed9eb8f316fa19d9947bac3e6350dc63a6f0a"}, + {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01e0d22f06c81e6c435723343e1eefc710e0510a35d897856766d475f2a15687"}, + {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7c1e602d028ee285dbd300fb9820b342b937df64d5a3336e1618b354e95a2569"}, + {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d230e5020666a6725629df81e210dc11c3eae7d52fe909a7157b3875238484f3"}, + {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0f8baac07d4555f57d44746a7d80fbe6b2c4fe2ed68136b4abb51cfec512a5e9"}, + {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:524e48420b90fc66953e91b660b3d05faaf921277d6707e328fde1c218b31250"}, + {file = "orjson-3.10.16-cp39-cp39-win32.whl", hash = "sha256:a9f614e31423d7292dbca966a53b2d775c64528c7d91424ab2747d8ab8ce5c72"}, + {file = "orjson-3.10.16-cp39-cp39-win_amd64.whl", hash = "sha256:c338dc2296d1ed0d5c5c27dfb22d00b330555cb706c2e0be1e1c3940a0895905"}, + {file = "orjson-3.10.16.tar.gz", hash = "sha256:d2aaa5c495e11d17b9b93205f5fa196737ee3202f000aaebf028dc9a73750f10"}, +] + +[[package]] +name = "overrides" +version = "7.7.0" +description = "A decorator to automatically detect mismatch when overriding a method." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, + {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, +] + +[[package]] +name = "packageurl-python" +version = "0.13.4" +description = "A purl aka. Package URL parser and builder" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "packageurl-python-0.13.4.tar.gz", hash = "sha256:6eb5e995009cc73387095e0b507ab65df51357d25ddc5fce3d3545ad6dcbbee8"}, + {file = "packageurl_python-0.13.4-py3-none-any.whl", hash = "sha256:62aa13d60a0082ff115784fefdfe73a12f310e455365cca7c6d362161067f35f"}, +] + +[package.extras] +build = ["setuptools", "wheel"] +lint = ["black", "isort", "mypy"] +sqlalchemy = ["sqlalchemy (>=2.0.0)"] +test = ["pytest"] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +description = "Utilities for writing pandoc filters in python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +files = [ + {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, + {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, +] + +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pbr" +version = "6.1.1" +description = "Python Build Reasonableness" +optional = false +python-versions = ">=2.6" +groups = ["main"] +files = [ + {file = "pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76"}, + {file = "pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "peewee" +version = "3.17.9" +description = "a little orm" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "peewee-3.17.9.tar.gz", hash = "sha256:fe15cd001758e324c8e3ca8c8ed900e7397c2907291789e1efc383e66b9bc7a8"}, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "platformdirs" +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "ply" +version = "3.11" +description = "Python Lex & Yacc" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, + {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, +] + +[[package]] +name = "policy-sentry" +version = "0.13.2" +description = "Generate locked-down AWS IAM Policies" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "policy_sentry-0.13.2-py3-none-any.whl", hash = "sha256:e82c3bc1783606449399c4221f67d05f6b08d8a184ba2fee87d04541d7282b86"}, + {file = "policy_sentry-0.13.2.tar.gz", hash = "sha256:db2b39f92989077f83fc4dd1d064e3ff20b69cfed82168ebdc060e7dce292e77"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +click = "*" +orjson = "*" +PyYAML = "*" +requests = "*" +schema = "*" + +[[package]] +name = "prettytable" +version = "3.16.0" +description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "prettytable-3.16.0-py3-none-any.whl", hash = "sha256:b5eccfabb82222f5aa46b798ff02a8452cf530a352c31bddfa29be41242863aa"}, + {file = "prettytable-3.16.0.tar.gz", hash = "sha256:3c64b31719d961bf69c9a7e03d0c1e477320906a98da63952bc6698d6164ff57"}, +] + +[package.dependencies] +wcwidth = "*" + +[package.extras] +tests = ["pytest", "pytest-cov", "pytest-lazy-fixtures"] + +[[package]] +name = "prometheus-client" +version = "0.21.1" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301"}, + {file = "prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb"}, +] + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.50" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +files = [ + {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, + {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "propcache" +version = "0.3.1" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f27785888d2fdd918bc36de8b8739f2d6c791399552333721b58193f68ea3e98"}, + {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4e89cde74154c7b5957f87a355bb9c8ec929c167b59c83d90654ea36aeb6180"}, + {file = "propcache-0.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:730178f476ef03d3d4d255f0c9fa186cb1d13fd33ffe89d39f2cda4da90ceb71"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967a8eec513dbe08330f10137eacb427b2ca52118769e82ebcfcab0fba92a649"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b9145c35cc87313b5fd480144f8078716007656093d23059e8993d3a8fa730f"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e64e948ab41411958670f1093c0a57acfdc3bee5cf5b935671bbd5313bcf229"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:319fa8765bfd6a265e5fa661547556da381e53274bc05094fc9ea50da51bfd46"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66d8ccbc902ad548312b96ed8d5d266d0d2c6d006fd0f66323e9d8f2dd49be7"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2d219b0dbabe75e15e581fc1ae796109b07c8ba7d25b9ae8d650da582bed01b0"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd6a55f65241c551eb53f8cf4d2f4af33512c39da5d9777694e9d9c60872f519"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9979643ffc69b799d50d3a7b72b5164a2e97e117009d7af6dfdd2ab906cb72cd"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4cf9e93a81979f1424f1a3d155213dc928f1069d697e4353edb8a5eba67c6259"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2fce1df66915909ff6c824bbb5eb403d2d15f98f1518e583074671a30fe0c21e"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4d0dfdd9a2ebc77b869a0b04423591ea8823f791293b527dc1bb896c1d6f1136"}, + {file = "propcache-0.3.1-cp310-cp310-win32.whl", hash = "sha256:1f6cc0ad7b4560e5637eb2c994e97b4fa41ba8226069c9277eb5ea7101845b42"}, + {file = "propcache-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:47ef24aa6511e388e9894ec16f0fbf3313a53ee68402bc428744a367ec55b833"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9"}, + {file = "propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005"}, + {file = "propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7"}, + {file = "propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b"}, + {file = "propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef"}, + {file = "propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24"}, + {file = "propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a"}, + {file = "propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d"}, + {file = "propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ed5f6d2edbf349bd8d630e81f474d33d6ae5d07760c44d33cd808e2f5c8f4ae6"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:668ddddc9f3075af019f784456267eb504cb77c2c4bd46cc8402d723b4d200bf"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c86e7ceea56376216eba345aa1fc6a8a6b27ac236181f840d1d7e6a1ea9ba5c"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83be47aa4e35b87c106fc0c84c0fc069d3f9b9b06d3c494cd404ec6747544894"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27c6ac6aa9fc7bc662f594ef380707494cb42c22786a558d95fcdedb9aa5d035"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a956dff37080b352c1c40b2966b09defb014347043e740d420ca1eb7c9b908"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82de5da8c8893056603ac2d6a89eb8b4df49abf1a7c19d536984c8dd63f481d5"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3c3a203c375b08fd06a20da3cf7aac293b834b6f4f4db71190e8422750cca5"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b303b194c2e6f171cfddf8b8ba30baefccf03d36a4d9cab7fd0bb68ba476a3d7"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:916cd229b0150129d645ec51614d38129ee74c03293a9f3f17537be0029a9641"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a461959ead5b38e2581998700b26346b78cd98540b5524796c175722f18b0294"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:069e7212890b0bcf9b2be0a03afb0c2d5161d91e1bf51569a64f629acc7defbf"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef2e4e91fb3945769e14ce82ed53007195e616a63aa43b40fb7ebaaf907c8d4c"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8638f99dca15b9dff328fb6273e09f03d1c50d9b6512f3b65a4154588a7595fe"}, + {file = "propcache-0.3.1-cp39-cp39-win32.whl", hash = "sha256:6f173bbfe976105aaa890b712d1759de339d8a7cef2fc0a1714cc1a1e1c47f64"}, + {file = "propcache-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:603f1fe4144420374f1a69b907494c3acbc867a581c2d49d4175b0de7cc64566"}, + {file = "propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40"}, + {file = "propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf"}, +] + +[[package]] +name = "protobuf" +version = "4.25.6" +description = "" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "protobuf-4.25.6-cp310-abi3-win32.whl", hash = "sha256:61df6b5786e2b49fc0055f636c1e8f0aff263808bb724b95b164685ac1bcc13a"}, + {file = "protobuf-4.25.6-cp310-abi3-win_amd64.whl", hash = "sha256:b8f837bfb77513fe0e2f263250f423217a173b6d85135be4d81e96a4653bcd3c"}, + {file = "protobuf-4.25.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:6d4381f2417606d7e01750e2729fe6fbcda3f9883aa0c32b51d23012bded6c91"}, + {file = "protobuf-4.25.6-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:5dd800da412ba7f6f26d2c08868a5023ce624e1fdb28bccca2dc957191e81fb5"}, + {file = "protobuf-4.25.6-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:4434ff8bb5576f9e0c78f47c41cdf3a152c0b44de475784cd3fd170aef16205a"}, + {file = "protobuf-4.25.6-cp38-cp38-win32.whl", hash = "sha256:8bad0f9e8f83c1fbfcc34e573352b17dfce7d0519512df8519994168dc015d7d"}, + {file = "protobuf-4.25.6-cp38-cp38-win_amd64.whl", hash = "sha256:b6905b68cde3b8243a198268bb46fbec42b3455c88b6b02fb2529d2c306d18fc"}, + {file = "protobuf-4.25.6-cp39-cp39-win32.whl", hash = "sha256:3f3b0b39db04b509859361ac9bca65a265fe9342e6b9406eda58029f5b1d10b2"}, + {file = "protobuf-4.25.6-cp39-cp39-win_amd64.whl", hash = "sha256:6ef2045f89d4ad8d95fd43cd84621487832a61d15b49500e4c1350e8a0ef96be"}, + {file = "protobuf-4.25.6-py3-none-any.whl", hash = "sha256:07972021c8e30b870cfc0863409d033af940213e0e7f64e27fe017b929d2c9f7"}, + {file = "protobuf-4.25.6.tar.gz", hash = "sha256:f8cfbae7c5afd0d0eaccbe73267339bff605a2315860bb1ba08eb66670a9a91f"}, +] + +[[package]] +name = "psutil" +version = "7.0.0" +description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, + {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, + {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, + {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, + {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, + {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, +] + +[package.extras] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\" or os_name != \"nt\"" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "publication" +version = "0.0.3" +description = "Publication helps you maintain public-api-friendly modules by preventing unintentional access to private implementation details via introspection." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "publication-0.0.3-py2.py3-none-any.whl", hash = "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6"}, + {file = "publication-0.0.3.tar.gz", hash = "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "py-serializable" +version = "1.1.2" +description = "Library for serializing and deserializing Python Objects to and from JSON and XML." +optional = false +python-versions = "<4.0,>=3.8" +groups = ["main"] +files = [ + {file = "py_serializable-1.1.2-py3-none-any.whl", hash = "sha256:801be61b0a1ba64c3861f7c624f1de5cfbbabf8b458acc9cdda91e8f7e5effa1"}, + {file = "py_serializable-1.1.2.tar.gz", hash = "sha256:89af30bc319047d4aa0d8708af412f6ce73835e18bacf1a080028bb9e2f42bdb"}, +] + +[package.dependencies] +defusedxml = ">=0.7.1,<0.8.0" + +[[package]] +name = "pycares" +version = "4.5.0" +description = "Python interface for c-ares" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pycares-4.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13a82fad8239d6fbcf916099bee17d8b5666d0ddb77dace431e0f7961c9427ab"}, + {file = "pycares-4.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fefc7bebbe39b2e3b4b9615471233a8f7356b96129a7db9030313a3ae4ecc42d"}, + {file = "pycares-4.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e322e8ce810026f6e0c7c2a254b9ed02191ab8d42fa2ce6808ede1bdccab8e65"}, + {file = "pycares-4.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:723ba0803b016294430e40e544503fed9164949b694342c2552ab189e2b688ef"}, + {file = "pycares-4.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e48b20b59cdc929cc712a8b22e89c273256e482b49bb8999af98d2c6fc4563c2"}, + {file = "pycares-4.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de6e55bd9af595b112ac6080ac0a0d52b5853d0d8e6d01ac65ff09e51e62490a"}, + {file = "pycares-4.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6f4b9063e3dd70460400367917698f209c10aabb68bf70b09e364895444487d"}, + {file = "pycares-4.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:95522d4840d702fd766439a7c7cd747935aa54cf0b8675e9fadd8414dd9dd0df"}, + {file = "pycares-4.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4709ce4fd9dbee24b1397f71a2adb3267323bb5ad5e7fde3f87873d172dd156"}, + {file = "pycares-4.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8addbf3408af1010f50fd67ef634a6cb239ccb9c534c32a40713f3b8d306a98e"}, + {file = "pycares-4.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d0428ef42fcf575e197047e6a47892404faa34231902a453b3dfed66af4178b3"}, + {file = "pycares-4.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:aed5c2732f3a6bdbbfab202267d37044ca1162f690b9d34b7ece97ba43f27453"}, + {file = "pycares-4.5.0-cp310-cp310-win32.whl", hash = "sha256:b1859ea770a7abec40a6d02b5ab03c2396c4900c01f4e50ddb6c0dca4c2a6a7c"}, + {file = "pycares-4.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f87d8da20a3a80ab05fe80c14a62bf078bd726ca6af609edbeb376fb97d50ab"}, + {file = "pycares-4.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ca7a1dba7b88290710db45012e0903c21c839fa0a2b9ddc100bba8e66bfb251"}, + {file = "pycares-4.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:160e92588cdf1a0fa3a7015f47990b508d50efd9109ea4d719dee31c058f0648"}, + {file = "pycares-4.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f38e45d23660ed1dafdb956fd263ae4735530ef1578aa2bf2caabb94cee4523"}, + {file = "pycares-4.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f742acc6d29a99ffc14e3f154b3848ea05c5533b71065e0f0a0fd99c527491b2"}, + {file = "pycares-4.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceaf71bcd7b6447705e689b8fee8836c20c6148511a90122981f524a84bfcca9"}, + {file = "pycares-4.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdc3c0be7b5b83e78e28818fecd0405bd401110dd6e2e66f7f10713c1188362c"}, + {file = "pycares-4.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd458ee69800195247aa19b5675c5914cbc091c5a220e4f0e96777a31bb555c1"}, + {file = "pycares-4.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6649d713df73266708642fc3d04f110c0a66bee510fbce4cc5fed79df42083"}, + {file = "pycares-4.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ac57d7bda925c10b997434e7ce30a2c3689c2e96bab9fd0a1165d5577378eecd"}, + {file = "pycares-4.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba17d8e5eeec4b2e0eb1a6a840bae9e62cd1c1c9cbc8dc9db9d1b9fdf33d0b54"}, + {file = "pycares-4.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9e9b7d1a8de703283e4735c0e532ba4bc600e88de872dcd1a9a4950cf74d9f4f"}, + {file = "pycares-4.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c6922ecbe458c13a4a2c1177bbce38abc44b5f086bc82115a92eab34418915f"}, + {file = "pycares-4.5.0-cp311-cp311-win32.whl", hash = "sha256:1004b8a17614e33410b4b1bb68360977667f1cc9ab2dbcfb27240d6703e4cb6a"}, + {file = "pycares-4.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:2c9c1055c622258a0f315560b2880a372363484b87cbef48af092624804caa72"}, + {file = "pycares-4.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:506efbe5017807747ccd1bdcb3c2f6e64635bc01fee01a50c0b97d649018c162"}, + {file = "pycares-4.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c469ec9fbe0526f45a98f67c1ea55be03abf30809c4f9c9be4bc93fb6806304d"}, + {file = "pycares-4.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597c0950ede240c3a779f023fcf2442207fc11e570d3ca4ccdbb0db5bbaf2588"}, + {file = "pycares-4.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9aa0da03c4df6ed0f87dd52a293bd0508734515041cc5be0f85d9edc1814914f"}, + {file = "pycares-4.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1ebf52767c777d10a1b3d03844b9b05cc892714b3ee177d5d9fbff74fb9fa"}, + {file = "pycares-4.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb20d84269ddffb177b6048e3bc03d0b9ffe17592093d900d5544805958d86b3"}, + {file = "pycares-4.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3125df81b657971ee5c0333f8f560ba0151db1eb7cf04aea7d783bb433b306c1"}, + {file = "pycares-4.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:525c77ea44546c12f379641aee163585d403cf50e29b04a06059d6aac894e956"}, + {file = "pycares-4.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1fd87cb26b317a9988abfcfa4e4dbc55d5f20177e5979ad4d854468a9246c187"}, + {file = "pycares-4.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a90aecd41188884e57ae32507a2c6b010c60b791a253083761bbb37a488ecaed"}, + {file = "pycares-4.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0d3de65cab653979dcc491e03f596566c9d40346c9deb088e0f9fe70600d8737"}, + {file = "pycares-4.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27a77b43604b3ba24e4fc49fd3ea59f50f7d89c7255f1f1ea46928b26cccacfa"}, + {file = "pycares-4.5.0-cp312-cp312-win32.whl", hash = "sha256:6028cb8766f0fea1d2caa69fac23621fbe2cff9ce6968374e165737258703a33"}, + {file = "pycares-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:2ce10672c4cfd1c5fb6718e8b25f0336ca11c89aab88aa6df53dafc4e41df740"}, + {file = "pycares-4.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:011cd670da7caf55664c944abb71ec39af82b837f8d48da7cf0eec80f5682c4c"}, + {file = "pycares-4.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b5c67930497fb2b1dbcaa85f8c4188fc2cb62e41d787deeed2d33cfe9dd6bf52"}, + {file = "pycares-4.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d435a3b8468c656a7e7180dd7c4794510f6c612c33ad61a0fff6e440621f8b5"}, + {file = "pycares-4.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8371f5ee1efb33d6276e275d152c9c5605e5f2e58a9e168519ec1f9e13dd95ae"}, + {file = "pycares-4.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76a9096fd5dc49c61c5235ea7032e8b43f4382800d64ca1e0e0cda700c082aa"}, + {file = "pycares-4.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b604af76b57469ff68b44e9e4c857eaee43bc5035f4f183f07f4f7149191fe1b"}, + {file = "pycares-4.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c589bd4f9160bfdb2f8080cf564bb120a4312cf091db07fe417f8e58a896a63c"}, + {file = "pycares-4.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:361262805bb09742c364ec0117842043c950339e38561009bcabbb6ac89458ef"}, + {file = "pycares-4.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d2afb3c0776467055bf33db843ef483d25639be0f32e3a13ef5d4dc64098bf5"}, + {file = "pycares-4.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bc7a1d8ed7c7a4de17706a3c89b305b02eb64c778897e6727c043e5b9dd0d853"}, + {file = "pycares-4.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5703ec878b5c1efacdbf24ceaedfa606112fc67af5564f4db99c2c210f3ffadc"}, + {file = "pycares-4.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d87758e09dbf52c27ed7cf7bc7eaf8b3226217d10c52b03d61a14d59f40fcae1"}, + {file = "pycares-4.5.0-cp313-cp313-win32.whl", hash = "sha256:3316d490b4ce1a69f034881ac1ea7608f5f24ea5293db24ab574ac70b7d7e407"}, + {file = "pycares-4.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:018e700fb0d1a2db5ec96e404ffa85ed97cc96e96d6af0bb9548111e37cf36a3"}, + {file = "pycares-4.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:78c9890d93108c70708babee8a783e6021233f1f0a763d3634add6fd429aae58"}, + {file = "pycares-4.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba69f8123995aa3df99f6ebc726fc6a4b08e467a957b215c0a82749b901d5eed"}, + {file = "pycares-4.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d33c4ffae31d1b544adebe0b9aee2be1fb18aedd3f4f91e41c495ccbafd6d8"}, + {file = "pycares-4.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17a060cfc469828abf7f5945964d505bd8c0a756942fee159538f7885169752e"}, + {file = "pycares-4.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1d0d5e69fa29e41b590a9dd5842454e8f34e2b928c92540aaf87e0161de8120"}, + {file = "pycares-4.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f096699c46f5dde2c7a8d91501a36d2d58500f4d63682e2ec14a0fed7cca6402"}, + {file = "pycares-4.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:429fe2065581a64a5f024f507b5f679bf37ea0ed39c3ba6289dba907e1c8a8f4"}, + {file = "pycares-4.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9ea2f6d48e64b413b97b41b47392087b452af9bf9f9d4d6d05305a159f45909f"}, + {file = "pycares-4.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:96d3aecd747a3fcd1e12c1ea1481b0813b4e0e80d40f314db7a86dda5bb1bd94"}, + {file = "pycares-4.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:32919f6eda7f5ea4df3e64149fc5792b0d455277d23d6d0fc365142062f35d80"}, + {file = "pycares-4.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:37add862461f9a3fc7ee4dd8b68465812b39456e21cebd5a33c414131ac05060"}, + {file = "pycares-4.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ed1d050d2c6d74a77c1b6c51fd99426cc000b4202a50d28d6ca75f7433099a6b"}, + {file = "pycares-4.5.0-cp39-cp39-win32.whl", hash = "sha256:887ac451ffe6e39ee46d3d0989c7bb829933d77e1dad5776511d825fc7e6a25b"}, + {file = "pycares-4.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c8b87c05740595bc8051dc98e51f022f003750e7da90f62f7a9fd50e330b196"}, + {file = "pycares-4.5.0.tar.gz", hash = "sha256:025b6c2ffea4e9fb8f9a097381c2fecb24aff23fbd6906e70da22ec9ba60e19d"}, +] + +[package.dependencies] +cffi = ">=1.5.0" + +[package.extras] +idna = ["idna (>=2.1)"] + +[[package]] +name = "pycep-parser" +version = "0.5.1" +description = "A Python based Bicep parser" +optional = false +python-versions = "<4.0,>=3.8" +groups = ["main"] +files = [ + {file = "pycep_parser-0.5.1-py3-none-any.whl", hash = "sha256:8c3f99c0dc1301193b1bcbe0a44c6b2763f6d2daf24964ca48dcdfbb73087fa0"}, + {file = "pycep_parser-0.5.1.tar.gz", hash = "sha256:683bb001077c09f98408285b1b6ba10cfb3941610966c45d0638a0e1a5e1d2a4"}, +] + +[package.dependencies] +lark = ">=1.1.2" +regex = ">=2022.1.18" +typing-extensions = ">=3.10.0" + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pydantic" +version = "2.11.1" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8"}, + {file = "pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.33.0" +typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] + +[[package]] +name = "pydantic-core" +version = "2.33.0" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_core-2.33.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71dffba8fe9ddff628c68f3abd845e91b028361d43c5f8e7b3f8b91d7d85413e"}, + {file = "pydantic_core-2.33.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:abaeec1be6ed535a5d7ffc2e6c390083c425832b20efd621562fbb5bff6dc518"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759871f00e26ad3709efc773ac37b4d571de065f9dfb1778012908bcc36b3a73"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dcfebee69cd5e1c0b76a17e17e347c84b00acebb8dd8edb22d4a03e88e82a207"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b1262b912435a501fa04cd213720609e2cefa723a07c92017d18693e69bf00b"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4726f1f3f42d6a25678c67da3f0b10f148f5655813c5aca54b0d1742ba821b8f"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e790954b5093dff1e3a9a2523fddc4e79722d6f07993b4cd5547825c3cbf97b5"}, + {file = "pydantic_core-2.33.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34e7fb3abe375b5c4e64fab75733d605dda0f59827752debc99c17cb2d5f3276"}, + {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ecb158fb9b9091b515213bed3061eb7deb1d3b4e02327c27a0ea714ff46b0760"}, + {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:4d9149e7528af8bbd76cc055967e6e04617dcb2a2afdaa3dea899406c5521faa"}, + {file = "pydantic_core-2.33.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e81a295adccf73477220e15ff79235ca9dcbcee4be459eb9d4ce9a2763b8386c"}, + {file = "pydantic_core-2.33.0-cp310-cp310-win32.whl", hash = "sha256:f22dab23cdbce2005f26a8f0c71698457861f97fc6318c75814a50c75e87d025"}, + {file = "pydantic_core-2.33.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cb2390355ba084c1ad49485d18449b4242da344dea3e0fe10babd1f0db7dcfc"}, + {file = "pydantic_core-2.33.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a608a75846804271cf9c83e40bbb4dab2ac614d33c6fd5b0c6187f53f5c593ef"}, + {file = "pydantic_core-2.33.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e1c69aa459f5609dec2fa0652d495353accf3eda5bdb18782bc5a2ae45c9273a"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9ec80eb5a5f45a2211793f1c4aeddff0c3761d1c70d684965c1807e923a588b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e925819a98318d17251776bd3d6aa9f3ff77b965762155bdad15d1a9265c4cfd"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bf68bb859799e9cec3d9dd8323c40c00a254aabb56fe08f907e437005932f2b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b2ea72dea0825949a045fa4071f6d5b3d7620d2a208335207793cf29c5a182d"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1583539533160186ac546b49f5cde9ffc928062c96920f58bd95de32ffd7bffd"}, + {file = "pydantic_core-2.33.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23c3e77bf8a7317612e5c26a3b084c7edeb9552d645742a54a5867635b4f2453"}, + {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7a7f2a3f628d2f7ef11cb6188bcf0b9e1558151d511b974dfea10a49afe192b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:f1fb026c575e16f673c61c7b86144517705865173f3d0907040ac30c4f9f5915"}, + {file = "pydantic_core-2.33.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:635702b2fed997e0ac256b2cfbdb4dd0bf7c56b5d8fba8ef03489c03b3eb40e2"}, + {file = "pydantic_core-2.33.0-cp311-cp311-win32.whl", hash = "sha256:07b4ced28fccae3f00626eaa0c4001aa9ec140a29501770a88dbbb0966019a86"}, + {file = "pydantic_core-2.33.0-cp311-cp311-win_amd64.whl", hash = "sha256:4927564be53239a87770a5f86bdc272b8d1fbb87ab7783ad70255b4ab01aa25b"}, + {file = "pydantic_core-2.33.0-cp311-cp311-win_arm64.whl", hash = "sha256:69297418ad644d521ea3e1aa2e14a2a422726167e9ad22b89e8f1130d68e1e9a"}, + {file = "pydantic_core-2.33.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6c32a40712e3662bebe524abe8abb757f2fa2000028d64cc5a1006016c06af43"}, + {file = "pydantic_core-2.33.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ec86b5baa36f0a0bfb37db86c7d52652f8e8aa076ab745ef7725784183c3fdd"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4deac83a8cc1d09e40683be0bc6d1fa4cde8df0a9bf0cda5693f9b0569ac01b6"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:175ab598fb457a9aee63206a1993874badf3ed9a456e0654273e56f00747bbd6"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f36afd0d56a6c42cf4e8465b6441cf546ed69d3a4ec92724cc9c8c61bd6ecf4"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a98257451164666afafc7cbf5fb00d613e33f7e7ebb322fbcd99345695a9a61"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecc6d02d69b54a2eb83ebcc6f29df04957f734bcf309d346b4f83354d8376862"}, + {file = "pydantic_core-2.33.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a69b7596c6603afd049ce7f3835bcf57dd3892fc7279f0ddf987bebed8caa5a"}, + {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea30239c148b6ef41364c6f51d103c2988965b643d62e10b233b5efdca8c0099"}, + {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:abfa44cf2f7f7d7a199be6c6ec141c9024063205545aa09304349781b9a125e6"}, + {file = "pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20d4275f3c4659d92048c70797e5fdc396c6e4446caf517ba5cad2db60cd39d3"}, + {file = "pydantic_core-2.33.0-cp312-cp312-win32.whl", hash = "sha256:918f2013d7eadea1d88d1a35fd4a1e16aaf90343eb446f91cb091ce7f9b431a2"}, + {file = "pydantic_core-2.33.0-cp312-cp312-win_amd64.whl", hash = "sha256:aec79acc183865bad120b0190afac467c20b15289050648b876b07777e67ea48"}, + {file = "pydantic_core-2.33.0-cp312-cp312-win_arm64.whl", hash = "sha256:5461934e895968655225dfa8b3be79e7e927e95d4bd6c2d40edd2fa7052e71b6"}, + {file = "pydantic_core-2.33.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f00e8b59e1fc8f09d05594aa7d2b726f1b277ca6155fc84c0396db1b373c4555"}, + {file = "pydantic_core-2.33.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a73be93ecef45786d7d95b0c5e9b294faf35629d03d5b145b09b81258c7cd6d"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff48a55be9da6930254565ff5238d71d5e9cd8c5487a191cb85df3bdb8c77365"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4ea04195638dcd8c53dadb545d70badba51735b1594810e9768c2c0b4a5da"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41d698dcbe12b60661f0632b543dbb119e6ba088103b364ff65e951610cb7ce0"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae62032ef513fe6281ef0009e30838a01057b832dc265da32c10469622613885"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f225f3a3995dbbc26affc191d0443c6c4aa71b83358fd4c2b7d63e2f6f0336f9"}, + {file = "pydantic_core-2.33.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bdd36b362f419c78d09630cbaebc64913f66f62bda6d42d5fbb08da8cc4f181"}, + {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a0147c0bef783fd9abc9f016d66edb6cac466dc54a17ec5f5ada08ff65caf5d"}, + {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c860773a0f205926172c6644c394e02c25421dc9a456deff16f64c0e299487d3"}, + {file = "pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:138d31e3f90087f42aa6286fb640f3c7a8eb7bdae829418265e7e7474bd2574b"}, + {file = "pydantic_core-2.33.0-cp313-cp313-win32.whl", hash = "sha256:d20cbb9d3e95114325780f3cfe990f3ecae24de7a2d75f978783878cce2ad585"}, + {file = "pydantic_core-2.33.0-cp313-cp313-win_amd64.whl", hash = "sha256:ca1103d70306489e3d006b0f79db8ca5dd3c977f6f13b2c59ff745249431a606"}, + {file = "pydantic_core-2.33.0-cp313-cp313-win_arm64.whl", hash = "sha256:6291797cad239285275558e0a27872da735b05c75d5237bbade8736f80e4c225"}, + {file = "pydantic_core-2.33.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b79af799630af263eca9ec87db519426d8c9b3be35016eddad1832bac812d87"}, + {file = "pydantic_core-2.33.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabf946a4739b5237f4f56d77fa6668263bc466d06a8036c055587c130a46f7b"}, + {file = "pydantic_core-2.33.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8a1d581e8cdbb857b0e0e81df98603376c1a5c34dc5e54039dcc00f043df81e7"}, + {file = "pydantic_core-2.33.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:7c9c84749f5787781c1c45bb99f433402e484e515b40675a5d121ea14711cf61"}, + {file = "pydantic_core-2.33.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:64672fa888595a959cfeff957a654e947e65bbe1d7d82f550417cbd6898a1d6b"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bc7367c0961dec292244ef2549afa396e72e28cc24706210bd44d947582c59"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ce72d46eb201ca43994303025bd54d8a35a3fc2a3495fac653d6eb7205ce04f4"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14229c1504287533dbf6b1fc56f752ce2b4e9694022ae7509631ce346158de11"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:085d8985b1c1e48ef271e98a658f562f29d89bda98bf120502283efbc87313eb"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31860fbda80d8f6828e84b4a4d129fd9c4535996b8249cfb8c720dc2a1a00bb8"}, + {file = "pydantic_core-2.33.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f200b2f20856b5a6c3a35f0d4e344019f805e363416e609e9b47c552d35fd5ea"}, + {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f72914cfd1d0176e58ddc05c7a47674ef4222c8253bf70322923e73e14a4ac3"}, + {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:91301a0980a1d4530d4ba7e6a739ca1a6b31341252cb709948e0aca0860ce0ae"}, + {file = "pydantic_core-2.33.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7419241e17c7fbe5074ba79143d5523270e04f86f1b3a0dff8df490f84c8273a"}, + {file = "pydantic_core-2.33.0-cp39-cp39-win32.whl", hash = "sha256:7a25493320203005d2a4dac76d1b7d953cb49bce6d459d9ae38e30dd9f29bc9c"}, + {file = "pydantic_core-2.33.0-cp39-cp39-win_amd64.whl", hash = "sha256:82a4eba92b7ca8af1b7d5ef5f3d9647eee94d1f74d21ca7c21e3a2b92e008358"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e2762c568596332fdab56b07060c8ab8362c56cf2a339ee54e491cd503612c50"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bf637300ff35d4f59c006fff201c510b2b5e745b07125458a5389af3c0dff8c"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c151ce3d59ed56ebd7ce9ce5986a409a85db697d25fc232f8e81f195aa39a1"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee65f0cc652261744fd07f2c6e6901c914aa6c5ff4dcfaf1136bc394d0dd26b"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:024d136ae44d233e6322027bbf356712b3940bee816e6c948ce4b90f18471b3d"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e37f10f6d4bc67c58fbd727108ae1d8b92b397355e68519f1e4a7babb1473442"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:502ed542e0d958bd12e7c3e9a015bce57deaf50eaa8c2e1c439b512cb9db1e3a"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:715c62af74c236bf386825c0fdfa08d092ab0f191eb5b4580d11c3189af9d330"}, + {file = "pydantic_core-2.33.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bccc06fa0372151f37f6b69834181aa9eb57cf8665ed36405fb45fbf6cac3bae"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d8dc9f63a26f7259b57f46a7aab5af86b2ad6fbe48487500bb1f4b27e051e4c"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30369e54d6d0113d2aa5aee7a90d17f225c13d87902ace8fcd7bbf99b19124db"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eb479354c62067afa62f53bb387827bee2f75c9c79ef25eef6ab84d4b1ae3b"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0310524c833d91403c960b8a3cf9f46c282eadd6afd276c8c5edc617bd705dc9"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eddb18a00bbb855325db27b4c2a89a4ba491cd6a0bd6d852b225172a1f54b36c"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:ade5dbcf8d9ef8f4b28e682d0b29f3008df9842bb5ac48ac2c17bc55771cc976"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2c0afd34f928383e3fd25740f2050dbac9d077e7ba5adbaa2227f4d4f3c8da5c"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7da333f21cd9df51d5731513a6d39319892947604924ddf2e24a4612975fb936"}, + {file = "pydantic_core-2.33.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b6d77c75a57f041c5ee915ff0b0bb58eabb78728b69ed967bc5b780e8f701b8"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba95691cf25f63df53c1d342413b41bd7762d9acb425df8858d7efa616c0870e"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f1ab031feb8676f6bd7c85abec86e2935850bf19b84432c64e3e239bffeb1ec"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58c1151827eef98b83d49b6ca6065575876a02d2211f259fb1a6b7757bd24dd8"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a66d931ea2c1464b738ace44b7334ab32a2fd50be023d863935eb00f42be1778"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0bcf0bab28995d483f6c8d7db25e0d05c3efa5cebfd7f56474359e7137f39856"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:89670d7a0045acb52be0566df5bc8b114ac967c662c06cf5e0c606e4aadc964b"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:b716294e721d8060908dbebe32639b01bfe61b15f9f57bcc18ca9a0e00d9520b"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fc53e05c16697ff0c1c7c2b98e45e131d4bfb78068fffff92a82d169cbb4c7b7"}, + {file = "pydantic_core-2.33.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:68504959253303d3ae9406b634997a2123a0b0c1da86459abbd0ffc921695eac"}, + {file = "pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pygments" +version = "2.19.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyparsing" +version = "3.2.3" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, + {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyston" +version = "2.3.5" +description = "A JIT for Python" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.10\" and (sys_platform == \"linux\" or sys_platform == \"darwin\") and platform_machine == \"x86_64\" and implementation_name == \"cpython\"" +files = [ + {file = "pyston-2.3.5-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:deb9dac7f8f67d1b2dc709300e1d8fda51bbc1375957509f58e1dc4459324ea7"}, + {file = "pyston-2.3.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d33602480ff742a45e21413377123bcead27c5ea11b06efaf0260ccb60633da3"}, + {file = "pyston-2.3.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b93ddaed1e62b8bd261e531f3356590014822f7451619000c6b9efe699dd148f"}, + {file = "pyston-2.3.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30caaee3b58d92817efa2cd4f32c24289dd5f4ddf9b5b4ec5b62ed564230ca8a"}, + {file = "pyston-2.3.5-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:5ea981d286de250467e48fd8d80d461acd1e4f27cd10775478206b273045c58d"}, + {file = "pyston-2.3.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44211f95ba99f4d6bd5fc5e27aba834644ba0277554fc52f9a98672720c3ff17"}, + {file = "pyston-2.3.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3805cac00fde2791408d09a988b32911009dcd86a8215a17d9a85dd83fe1c662"}, + {file = "pyston-2.3.5-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cab90f5bf4646c6de85c25763762dcfa94d209de955173b3f83e3c108d39028f"}, + {file = "pyston-2.3.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c2bddc7ea4755476ec9b75af94a63346ff6d2f34225dff73bb48c8a9f38795da"}, + {file = "pyston-2.3.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf36e6bdb84417b3291052e9156e8dfb03c3ec4879973bf7a47253fef24506d7"}, + {file = "pyston-2.3.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:539ad38ecd392cf60586122db2af45c22fc01fd83dc466ef05e3de7cfb79adb2"}, + {file = "pyston-2.3.5-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:5872e66a4583d56d9555caf6fa1959a33437593a75bf22c982e535d9e742dd38"}, + {file = "pyston-2.3.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a64edbcbf9494ee0e0c230544929d274a2705798abf0e298d82317dbfa5449e9"}, + {file = "pyston-2.3.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1f88d0594edc40b7d7fc4880e0aef33a69c97b4af0b14c91e8b5f4847ac618"}, + {file = "pyston-2.3.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f983b89f0f79ee527f2cadf167bc8c72b3e1c40574f71f12717a4cb13c75ed47"}, +] + +[[package]] +name = "pyston-autoload" +version = "2.3.5" +description = "Automatically loads and enables pyston" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.10\" and (sys_platform == \"linux\" or sys_platform == \"darwin\") and platform_machine == \"x86_64\" and implementation_name == \"cpython\"" +files = [ + {file = "pyston_autoload-2.3.5-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:50c5d2e2855a542f9e427601ed1cc94aa1ff82937c001e5765f4db67af8a309a"}, + {file = "pyston_autoload-2.3.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2efd392dffb78f782ccb40775086f7c53c7ad85fb469f6dddfc29141da37df26"}, + {file = "pyston_autoload-2.3.5-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d4c9f5624087b66713e5e674e055a77492ac1c19c40dfc306053c5eb7d970a98"}, + {file = "pyston_autoload-2.3.5-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:14d09effb82c436b2b090cf3293e8279e14af065ed5383ab06f72d5481f89cfd"}, + {file = "pyston_autoload-2.3.5-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:fbedb3013c93d52959c543bc9493572401d8f8e928bc265af6fdf6a2fb0258e8"}, + {file = "pyston_autoload-2.3.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4321d6d12ac04613d8f3cac4ff2c07d01a2c6736dc0ecf587808c75f755173df"}, + {file = "pyston_autoload-2.3.5-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:adf3fa86cfaf9968df7c22260bf63f27139a268dfa5fa01d23a0ae3e5ede92db"}, + {file = "pyston_autoload-2.3.5-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:940734109bd38beca8b0211c540492b2888bada4e332415906ba768002cb45b1"}, + {file = "pyston_autoload-2.3.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:26e7769005b4b33b48e51d822a3e3f81e5c2ce530634c0e614e7a939016a2171"}, + {file = "pyston_autoload-2.3.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d9eed4629ae2c798dff581b30a5044e369d2a0d7a8d19754dc95f48cd532fb97"}, + {file = "pyston_autoload-2.3.5-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:9ce3e3bdfbbb7b5900600cc6d1dcd68dfca145a61d10250263e1a4537ab4ff58"}, + {file = "pyston_autoload-2.3.5-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:4fd03b7cd2b439edda14ce7dd6e97158d0f75ff21f270a0c3792cd8341dcf0fa"}, + {file = "pyston_autoload-2.3.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cd6b85b50d3a86caec0db5382550abefe94fd1341dea8014cc7ba6d69daf6c86"}, + {file = "pyston_autoload-2.3.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f2c191a1cbcee2ed70d65510dd540edb5d5d2b3288b74be89be401456ae747d1"}, + {file = "pyston_autoload-2.3.5-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:7533445844ec987c2c10983485a0758dbbbfe5bdc98264a6ff4a70cdf6d1df74"}, +] + +[package.dependencies] +pyston = "2.3.5" + +[[package]] +name = "pytest" +version = "8.3.5" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, + {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "6.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_cov-6.1.0-py3-none-any.whl", hash = "sha256:cd7e1d54981d5185ef2b8d64b50172ce97e6f357e6df5cb103e828c7f993e201"}, + {file = "pytest_cov-6.1.0.tar.gz", hash = "sha256:ec55e828c66755e5b74a21bd7cc03c303a9f928389c0563e50ba454a6dbe71db"}, +] + +[package.dependencies] +coverage = {version = ">=7.5", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-json-logger" +version = "3.3.0" +description = "JSON Log Formatter for the Python Logging Package" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7"}, + {file = "python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84"}, +] + +[package.extras] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "black", "build", "freezegun", "mdx_truly_sane_lists", "mike", "mkdocs", "mkdocs-awesome-pages-plugin", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-material (>=8.5)", "mkdocstrings[python]", "msgspec ; implementation_name != \"pypy\"", "mypy", "orjson ; implementation_name != \"pypy\"", "pylint", "pytest", "tzdata", "validate-pyproject[all]"] + +[[package]] +name = "pywin32" +version = "310" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, + {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, + {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"}, + {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"}, + {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"}, + {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"}, + {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"}, + {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"}, + {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"}, + {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"}, + {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"}, + {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"}, + {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"}, + {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"}, + {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"}, + {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, +] + +[[package]] +name = "pywinpty" +version = "2.0.15" +description = "Pseudo terminal support for Windows from Python." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "os_name == \"nt\"" +files = [ + {file = "pywinpty-2.0.15-cp310-cp310-win_amd64.whl", hash = "sha256:8e7f5de756a615a38b96cd86fa3cd65f901ce54ce147a3179c45907fa11b4c4e"}, + {file = "pywinpty-2.0.15-cp311-cp311-win_amd64.whl", hash = "sha256:9a6bcec2df2707aaa9d08b86071970ee32c5026e10bcc3cc5f6f391d85baf7ca"}, + {file = "pywinpty-2.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:83a8f20b430bbc5d8957249f875341a60219a4e971580f2ba694fbfb54a45ebc"}, + {file = "pywinpty-2.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:ab5920877dd632c124b4ed17bc6dd6ef3b9f86cd492b963ffdb1a67b85b0f408"}, + {file = "pywinpty-2.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:a4560ad8c01e537708d2790dbe7da7d986791de805d89dd0d3697ca59e9e4901"}, + {file = "pywinpty-2.0.15-cp39-cp39-win_amd64.whl", hash = "sha256:d261cd88fcd358cfb48a7ca0700db3e1c088c9c10403c9ebc0d8a8b57aa6a117"}, + {file = "pywinpty-2.0.15.tar.gz", hash = "sha256:312cf39153a8736c617d45ce8b6ad6cd2107de121df91c455b10ce6bba7a39b2"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "pyzmq" +version = "26.3.0" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pyzmq-26.3.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:1586944f4736515af5c6d3a5b150c7e8ca2a2d6e46b23057320584d6f2438f4a"}, + {file = "pyzmq-26.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa7efc695d1fc9f72d91bf9b6c6fe2d7e1b4193836ec530a98faf7d7a7577a58"}, + {file = "pyzmq-26.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd84441e4021cec6e4dd040550386cd9c9ea1d9418ea1a8002dbb7b576026b2b"}, + {file = "pyzmq-26.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9176856f36c34a8aa5c0b35ddf52a5d5cd8abeece57c2cd904cfddae3fd9acd3"}, + {file = "pyzmq-26.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:49334faa749d55b77f084389a80654bf2e68ab5191c0235066f0140c1b670d64"}, + {file = "pyzmq-26.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fd30fc80fe96efb06bea21667c5793bbd65c0dc793187feb39b8f96990680b00"}, + {file = "pyzmq-26.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b2eddfbbfb473a62c3a251bb737a6d58d91907f6e1d95791431ebe556f47d916"}, + {file = "pyzmq-26.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:70b3acb9ad729a53d4e751dace35404a024f188aad406013454216aba5485b4e"}, + {file = "pyzmq-26.3.0-cp310-cp310-win32.whl", hash = "sha256:c1bd75d692cd7c6d862a98013bfdf06702783b75cffbf5dae06d718fecefe8f2"}, + {file = "pyzmq-26.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:d7165bcda0dbf203e5ad04d79955d223d84b2263df4db92f525ba370b03a12ab"}, + {file = "pyzmq-26.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:e34a63f71d2ecffb3c643909ad2d488251afeb5ef3635602b3448e609611a7ed"}, + {file = "pyzmq-26.3.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:2833602d9d42c94b9d0d2a44d2b382d3d3a4485be018ba19dddc401a464c617a"}, + {file = "pyzmq-26.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8270d104ec7caa0bdac246d31d48d94472033ceab5ba142881704350b28159c"}, + {file = "pyzmq-26.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c208a977843d18d3bd185f323e4eaa912eb4869cb230947dc6edd8a27a4e558a"}, + {file = "pyzmq-26.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eddc2be28a379c218e0d92e4a432805dcb0ca5870156a90b54c03cd9799f9f8a"}, + {file = "pyzmq-26.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c0b519fa2159c42272f8a244354a0e110d65175647e5185b04008ec00df9f079"}, + {file = "pyzmq-26.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1595533de3a80bf8363372c20bafa963ec4bf9f2b8f539b1d9a5017f430b84c9"}, + {file = "pyzmq-26.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bbef99eb8d18ba9a40f00e8836b8040cdcf0f2fa649684cf7a66339599919d21"}, + {file = "pyzmq-26.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:979486d444ca3c469cd1c7f6a619ce48ff08b3b595d451937db543754bfacb65"}, + {file = "pyzmq-26.3.0-cp311-cp311-win32.whl", hash = "sha256:4b127cfe10b4c56e4285b69fd4b38ea1d368099ea4273d8fb349163fce3cd598"}, + {file = "pyzmq-26.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cf736cc1298ef15280d9fcf7a25c09b05af016656856dc6fe5626fd8912658dd"}, + {file = "pyzmq-26.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:2dc46ec09f5d36f606ac8393303149e69d17121beee13c8dac25e2a2078e31c4"}, + {file = "pyzmq-26.3.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:c80653332c6136da7f4d4e143975e74ac0fa14f851f716d90583bc19e8945cea"}, + {file = "pyzmq-26.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e317ee1d4528a03506cb1c282cd9db73660a35b3564096de37de7350e7d87a7"}, + {file = "pyzmq-26.3.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:943a22ebb3daacb45f76a9bcca9a7b74e7d94608c0c0505da30af900b998ca8d"}, + {file = "pyzmq-26.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fc9e71490d989144981ea21ef4fdfaa7b6aa84aff9632d91c736441ce2f6b00"}, + {file = "pyzmq-26.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e281a8071a06888575a4eb523c4deeefdcd2f5fe4a2d47e02ac8bf3a5b49f695"}, + {file = "pyzmq-26.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:be77efd735bb1064605be8dec6e721141c1421ef0b115ef54e493a64e50e9a52"}, + {file = "pyzmq-26.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a4ac2ffa34f1212dd586af90f4ba894e424f0cabb3a49cdcff944925640f6ac"}, + {file = "pyzmq-26.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ba698c7c252af83b6bba9775035263f0df5f807f0404019916d4b71af8161f66"}, + {file = "pyzmq-26.3.0-cp312-cp312-win32.whl", hash = "sha256:214038aaa88e801e54c2ef0cfdb2e6df27eb05f67b477380a452b595c5ecfa37"}, + {file = "pyzmq-26.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:bad7fe0372e505442482ca3ccbc0d6f38dae81b1650f57a0aa6bbee18e7df495"}, + {file = "pyzmq-26.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:b7b578d604e79e99aa39495becea013fd043fa9f36e4b490efa951f3d847a24d"}, + {file = "pyzmq-26.3.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:fa85953df84beb7b8b73cb3ec3f5d92b62687a09a8e71525c6734e020edf56fd"}, + {file = "pyzmq-26.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:209d09f0ab6ddbcebe64630d1e6ca940687e736f443c265ae15bc4bfad833597"}, + {file = "pyzmq-26.3.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d35cc1086f1d4f907df85c6cceb2245cb39a04f69c3f375993363216134d76d4"}, + {file = "pyzmq-26.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b380e9087078ba91e45fb18cdd0c25275ffaa045cf63c947be0ddae6186bc9d9"}, + {file = "pyzmq-26.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6d64e74143587efe7c9522bb74d1448128fdf9897cc9b6d8b9927490922fd558"}, + {file = "pyzmq-26.3.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:efba4f53ac7752eea6d8ca38a4ddac579e6e742fba78d1e99c12c95cd2acfc64"}, + {file = "pyzmq-26.3.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:9b0137a1c40da3b7989839f9b78a44de642cdd1ce20dcef341de174c8d04aa53"}, + {file = "pyzmq-26.3.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a995404bd3982c089e57b428c74edd5bfc3b0616b3dbcd6a8e270f1ee2110f36"}, + {file = "pyzmq-26.3.0-cp313-cp313-win32.whl", hash = "sha256:240b1634b9e530ef6a277d95cbca1a6922f44dfddc5f0a3cd6c722a8de867f14"}, + {file = "pyzmq-26.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:fe67291775ea4c2883764ba467eb389c29c308c56b86c1e19e49c9e1ed0cbeca"}, + {file = "pyzmq-26.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:73ca9ae9a9011b714cf7650450cd9c8b61a135180b708904f1f0a05004543dce"}, + {file = "pyzmq-26.3.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:fea7efbd7e49af9d7e5ed6c506dfc7de3d1a628790bd3a35fd0e3c904dc7d464"}, + {file = "pyzmq-26.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4430c7cba23bb0e2ee203eee7851c1654167d956fc6d4b3a87909ccaf3c5825"}, + {file = "pyzmq-26.3.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:016d89bee8c7d566fad75516b4e53ec7c81018c062d4c51cd061badf9539be52"}, + {file = "pyzmq-26.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04bfe59852d76d56736bfd10ac1d49d421ab8ed11030b4a0332900691507f557"}, + {file = "pyzmq-26.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:1fe05bd0d633a0f672bb28cb8b4743358d196792e1caf04973b7898a0d70b046"}, + {file = "pyzmq-26.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:2aa1a9f236d5b835fb8642f27de95f9edcfd276c4bc1b6ffc84f27c6fb2e2981"}, + {file = "pyzmq-26.3.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:21399b31753bf321043ea60c360ed5052cc7be20739785b1dff1820f819e35b3"}, + {file = "pyzmq-26.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d015efcd96aca8882057e7e6f06224f79eecd22cad193d3e6a0a91ec67590d1f"}, + {file = "pyzmq-26.3.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:18183cc3851b995fdc7e5f03d03b8a4e1b12b0f79dff1ec1da75069af6357a05"}, + {file = "pyzmq-26.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:da87e977f92d930a3683e10ba2b38bcc59adfc25896827e0b9d78b208b7757a6"}, + {file = "pyzmq-26.3.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf6db401f4957afbf372a4730c6d5b2a234393af723983cbf4bcd13d54c71e1a"}, + {file = "pyzmq-26.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03caa2ffd64252122139d50ec92987f89616b9b92c9ba72920b40e92709d5e26"}, + {file = "pyzmq-26.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fbf206e5329e20937fa19bd41cf3af06d5967f8f7e86b59d783b26b40ced755c"}, + {file = "pyzmq-26.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6fb539a6382a048308b409d8c66d79bf636eda1b24f70c78f2a1fd16e92b037b"}, + {file = "pyzmq-26.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7897b8c8bbbb2bd8cad887bffcb07aede71ef1e45383bd4d6ac049bf0af312a4"}, + {file = "pyzmq-26.3.0-cp38-cp38-win32.whl", hash = "sha256:91dead2daca698ae52ce70ee2adbb94ddd9b5f96877565fd40aa4efd18ecc6a3"}, + {file = "pyzmq-26.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:8c088e009a6d6b9f563336adb906e3a8d3fd64db129acc8d8fd0e9fe22b2dac8"}, + {file = "pyzmq-26.3.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2eaed0d911fb3280981d5495978152fab6afd9fe217fd16f411523665089cef1"}, + {file = "pyzmq-26.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7998b60ef1c105846fb3bfca494769fde3bba6160902e7cd27a8df8257890ee9"}, + {file = "pyzmq-26.3.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:96c0006a8d1d00e46cb44c8e8d7316d4a232f3d8f2ed43179d4578dbcb0829b6"}, + {file = "pyzmq-26.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e17cc198dc50a25a0f245e6b1e56f692df2acec3ccae82d1f60c34bfb72bbec"}, + {file = "pyzmq-26.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:92a30840f4f2a31f7049d0a7de5fc69dd03b19bd5d8e7fed8d0bde49ce49b589"}, + {file = "pyzmq-26.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f52eba83272a26b444f4b8fc79f2e2c83f91d706d693836c9f7ccb16e6713c31"}, + {file = "pyzmq-26.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:952085a09ff32115794629ba47f8940896d7842afdef1283332109d38222479d"}, + {file = "pyzmq-26.3.0-cp39-cp39-win32.whl", hash = "sha256:0240289e33e3fbae44a5db73e54e955399179332a6b1d47c764a4983ec1524c3"}, + {file = "pyzmq-26.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b2db7c82f08b8ce44c0b9d1153ce63907491972a7581e8b6adea71817f119df8"}, + {file = "pyzmq-26.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:2d3459b6311463c96abcb97808ee0a1abb0d932833edb6aa81c30d622fd4a12d"}, + {file = "pyzmq-26.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad03f4252d9041b0635c37528dfa3f44b39f46024ae28c8567f7423676ee409b"}, + {file = "pyzmq-26.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f3dfb68cf7bf4cfdf34283a75848e077c5defa4907506327282afe92780084d"}, + {file = "pyzmq-26.3.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:356ec0e39c5a9cda872b65aca1fd8a5d296ffdadf8e2442b70ff32e73ef597b1"}, + {file = "pyzmq-26.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:749d671b0eec8e738bbf0b361168369d8c682b94fcd458c20741dc4d69ef5278"}, + {file = "pyzmq-26.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f950f17ae608e0786298340163cac25a4c5543ef25362dd5ddb6dcb10b547be9"}, + {file = "pyzmq-26.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b4fc9903a73c25be9d5fe45c87faababcf3879445efa16140146b08fccfac017"}, + {file = "pyzmq-26.3.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c15b69af22030960ac63567e98ad8221cddf5d720d9cf03d85021dfd452324ef"}, + {file = "pyzmq-26.3.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2cf9ab0dff4dbaa2e893eb608373c97eb908e53b7d9793ad00ccbd082c0ee12f"}, + {file = "pyzmq-26.3.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ec332675f6a138db57aad93ae6387953763f85419bdbd18e914cb279ee1c451"}, + {file = "pyzmq-26.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:eb96568a22fe070590942cd4780950e2172e00fb033a8b76e47692583b1bd97c"}, + {file = "pyzmq-26.3.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:009a38241c76184cb004c869e82a99f0aee32eda412c1eb44df5820324a01d25"}, + {file = "pyzmq-26.3.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4c22a12713707467abedc6d75529dd365180c4c2a1511268972c6e1d472bd63e"}, + {file = "pyzmq-26.3.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1614fcd116275d24f2346ffca4047a741c546ad9d561cbf7813f11226ca4ed2c"}, + {file = "pyzmq-26.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e2cafe7e9c7fed690e8ecf65af119f9c482923b5075a78f6f7629c63e1b4b1d"}, + {file = "pyzmq-26.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:14e0b81753424bd374075df6cc30b87f2c99e5f022501d97eff66544ca578941"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:21c6ddb98557a77cfe3366af0c5600fb222a1b2de5f90d9cd052b324e0c295e8"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fc81d5d60c9d40e692de14b8d884d43cf67562402b931681f0ccb3ce6b19875"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52b064fafef772d0f5dbf52d4c39f092be7bc62d9a602fe6e82082e001326de3"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b72206eb041f780451c61e1e89dbc3705f3d66aaaa14ee320d4f55864b13358a"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ab78dc21c7b1e13053086bcf0b4246440b43b5409904b73bfd1156654ece8a1"}, + {file = "pyzmq-26.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0b42403ad7d1194dca9574cd3c56691c345f4601fa2d0a33434f35142baec7ac"}, + {file = "pyzmq-26.3.0.tar.gz", hash = "sha256:f1cd68b8236faab78138a8fc703f7ca0ad431b17a3fcac696358600d4e6243b3"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "rdflib" +version = "7.1.4" +description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information." +optional = false +python-versions = "<4.0.0,>=3.8.1" +groups = ["main"] +files = [ + {file = "rdflib-7.1.4-py3-none-any.whl", hash = "sha256:72f4adb1990fa5241abd22ddaf36d7cafa5d91d9ff2ba13f3086d339b213d997"}, + {file = "rdflib-7.1.4.tar.gz", hash = "sha256:fed46e24f26a788e2ab8e445f7077f00edcf95abb73bcef4b86cefa8b62dd174"}, +] + +[package.dependencies] +isodate = {version = ">=0.7.2,<1.0.0", markers = "python_version < \"3.11\""} +pyparsing = ">=2.1.0,<4" + +[package.extras] +berkeleydb = ["berkeleydb (>=18.1.0,<19.0.0)"] +html = ["html5rdf (>=1.2,<2)"] +lxml = ["lxml (>=4.3,<6.0)"] +networkx = ["networkx (>=2,<4)"] +orjson = ["orjson (>=3.9.14,<4)"] + +[[package]] +name = "referencing" +version = "0.36.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, + {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} + +[[package]] +name = "regex" +version = "2024.11.6" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +description = "A pure python RFC3339 validator" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +files = [ + {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, + {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +description = "Pure python rfc3986 validator" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +files = [ + {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, + {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, +] + +[[package]] +name = "rich" +version = "13.5.3" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +groups = ["main"] +files = [ + {file = "rich-13.5.3-py3-none-any.whl", hash = "sha256:9257b468badc3d347e146a4faa268ff229039d4c2d176ab0cffb4c4fbc73d5d9"}, + {file = "rich-13.5.3.tar.gz", hash = "sha256:87b43e0543149efa1253f485cd845bb7ee54df16c9617b8a893650ab84b4acb6"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rpds-py" +version = "0.24.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724"}, + {file = "rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8acd55bd5b071156bae57b555f5d33697998752673b9de554dd82f5b5352727"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e80d375134ddb04231a53800503752093dbb65dad8dabacce2c84cccc78e964"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60748789e028d2a46fc1c70750454f83c6bdd0d05db50f5ae83e2db500b34da5"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e1daf5bf6c2be39654beae83ee6b9a12347cb5aced9a29eecf12a2d25fff664"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b221c2457d92a1fb3c97bee9095c874144d196f47c038462ae6e4a14436f7bc"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66420986c9afff67ef0c5d1e4cdc2d0e5262f53ad11e4f90e5e22448df485bf0"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:43dba99f00f1d37b2a0265a259592d05fcc8e7c19d140fe51c6e6f16faabeb1f"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a88c0d17d039333a41d9bf4616bd062f0bd7aa0edeb6cafe00a2fc2a804e944f"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc31e13ce212e14a539d430428cd365e74f8b2d534f8bc22dd4c9c55b277b875"}, + {file = "rpds_py-0.24.0-cp310-cp310-win32.whl", hash = "sha256:fc2c1e1b00f88317d9de6b2c2b39b012ebbfe35fe5e7bef980fd2a91f6100a07"}, + {file = "rpds_py-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0145295ca415668420ad142ee42189f78d27af806fcf1f32a18e51d47dd2052"}, + {file = "rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef"}, + {file = "rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718"}, + {file = "rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a"}, + {file = "rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6"}, + {file = "rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205"}, + {file = "rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56"}, + {file = "rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30"}, + {file = "rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034"}, + {file = "rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c"}, + {file = "rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9"}, + {file = "rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143"}, + {file = "rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a"}, + {file = "rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114"}, + {file = "rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c"}, + {file = "rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba"}, + {file = "rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350"}, + {file = "rpds_py-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a36b452abbf29f68527cf52e181fced56685731c86b52e852053e38d8b60bc8d"}, + {file = "rpds_py-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b3b397eefecec8e8e39fa65c630ef70a24b09141a6f9fc17b3c3a50bed6b50e"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdabcd3beb2a6dca7027007473d8ef1c3b053347c76f685f5f060a00327b8b65"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5db385bacd0c43f24be92b60c857cf760b7f10d8234f4bd4be67b5b20a7c0b6b"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8097b3422d020ff1c44effc40ae58e67d93e60d540a65649d2cdaf9466030791"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493fe54318bed7d124ce272fc36adbf59d46729659b2c792e87c3b95649cdee9"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8aa362811ccdc1f8dadcc916c6d47e554169ab79559319ae9fae7d7752d0d60c"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8f9a6e7fd5434817526815f09ea27f2746c4a51ee11bb3439065f5fc754db58"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8205ee14463248d3349131bb8099efe15cd3ce83b8ef3ace63c7e976998e7124"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:921ae54f9ecba3b6325df425cf72c074cd469dea843fb5743a26ca7fb2ccb149"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32bab0a56eac685828e00cc2f5d1200c548f8bc11f2e44abf311d6b548ce2e45"}, + {file = "rpds_py-0.24.0-cp39-cp39-win32.whl", hash = "sha256:f5c0ed12926dec1dfe7d645333ea59cf93f4d07750986a586f511c0bc61fe103"}, + {file = "rpds_py-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:afc6e35f344490faa8276b5f2f7cbf71f88bc2cda4328e00553bd451728c571f"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:619ca56a5468f933d940e1bf431c6f4e13bef8e688698b067ae68eb4f9b30e3a"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b28e5122829181de1898c2c97f81c0b3246d49f585f22743a1246420bb8d399"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e5ab32cf9eb3647450bc74eb201b27c185d3857276162c101c0f8c6374e098"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:208b3a70a98cf3710e97cabdc308a51cd4f28aa6e7bb11de3d56cd8b74bab98d"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbc4362e06f950c62cad3d4abf1191021b2ffaf0b31ac230fbf0526453eee75e"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebea2821cdb5f9fef44933617be76185b80150632736f3d76e54829ab4a3b4d1"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4df06c35465ef4d81799999bba810c68d29972bf1c31db61bfdb81dd9d5bb"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3aa13bdf38630da298f2e0d77aca967b200b8cc1473ea05248f6c5e9c9bdb44"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:041f00419e1da7a03c46042453598479f45be3d787eb837af382bfc169c0db33"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8754d872a5dfc3c5bf9c0e059e8107451364a30d9fd50f1f1a85c4fb9481164"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:896c41007931217a343eff197c34513c154267636c8056fb409eafd494c3dcdc"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92558d37d872e808944c3c96d0423b8604879a3d1c86fdad508d7ed91ea547d5"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e0f3ef95795efcd3b2ec3fe0a5bcfb5dadf5e3996ea2117427e524d4fbf309c6"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2c13777ecdbbba2077670285dd1fe50828c8742f6a4119dbef6f83ea13ad10fb"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e8d804c2ccd618417e96720ad5cd076a86fa3f8cb310ea386a3e6229bae7d1"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd822f019ccccd75c832deb7aa040bb02d70a92eb15a2f16c7987b7ad4ee8d83"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0047638c3aa0dbcd0ab99ed1e549bbf0e142c9ecc173b6492868432d8989a046"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5b66d1b201cc71bc3081bc2f1fc36b0c1f268b773e03bbc39066651b9e18391"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbcbb6db5582ea33ce46a5d20a5793134b5365110d84df4e30b9d37c6fd40ad3"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63981feca3f110ed132fd217bf7768ee8ed738a55549883628ee3da75bb9cb78"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3a55fc10fdcbf1a4bd3c018eea422c52cf08700cf99c28b5cb10fe97ab77a0d3"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:c30ff468163a48535ee7e9bf21bd14c7a81147c0e58a36c1078289a8ca7af0bd"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:369d9c6d4c714e36d4a03957b4783217a3ccd1e222cdd67d464a3a479fc17796"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:24795c099453e3721fda5d8ddd45f5dfcc8e5a547ce7b8e9da06fecc3832e26f"}, + {file = "rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e"}, +] + +[[package]] +name = "ruamel-yaml" +version = "0.18.10" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1"}, + {file = "ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58"}, +] + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} + +[package.extras] +docs = ["mercurial (>5.7)", "ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.12" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\"" +files = [ + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, + {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, +] + +[[package]] +name = "ruff" +version = "0.11.2" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.11.2-py3-none-linux_armv6l.whl", hash = "sha256:c69e20ea49e973f3afec2c06376eb56045709f0212615c1adb0eda35e8a4e477"}, + {file = "ruff-0.11.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2c5424cc1c4eb1d8ecabe6d4f1b70470b4f24a0c0171356290b1953ad8f0e272"}, + {file = "ruff-0.11.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf20854cc73f42171eedb66f006a43d0a21bfb98a2523a809931cda569552d9"}, + {file = "ruff-0.11.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c543bf65d5d27240321604cee0633a70c6c25c9a2f2492efa9f6d4b8e4199bb"}, + {file = "ruff-0.11.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20967168cc21195db5830b9224be0e964cc9c8ecf3b5a9e3ce19876e8d3a96e3"}, + {file = "ruff-0.11.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:955a9ce63483999d9f0b8f0b4a3ad669e53484232853054cc8b9d51ab4c5de74"}, + {file = "ruff-0.11.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:86b3a27c38b8fce73bcd262b0de32e9a6801b76d52cdb3ae4c914515f0cef608"}, + {file = "ruff-0.11.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3b66a03b248c9fcd9d64d445bafdf1589326bee6fc5c8e92d7562e58883e30f"}, + {file = "ruff-0.11.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0397c2672db015be5aa3d4dac54c69aa012429097ff219392c018e21f5085147"}, + {file = "ruff-0.11.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869bcf3f9abf6457fbe39b5a37333aa4eecc52a3b99c98827ccc371a8e5b6f1b"}, + {file = "ruff-0.11.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2a2b50ca35457ba785cd8c93ebbe529467594087b527a08d487cf0ee7b3087e9"}, + {file = "ruff-0.11.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7c69c74bf53ddcfbc22e6eb2f31211df7f65054bfc1f72288fc71e5f82db3eab"}, + {file = "ruff-0.11.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6e8fb75e14560f7cf53b15bbc55baf5ecbe373dd5f3aab96ff7aa7777edd7630"}, + {file = "ruff-0.11.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:842a472d7b4d6f5924e9297aa38149e5dcb1e628773b70e6387ae2c97a63c58f"}, + {file = "ruff-0.11.2-py3-none-win32.whl", hash = "sha256:aca01ccd0eb5eb7156b324cfaa088586f06a86d9e5314b0eb330cb48415097cc"}, + {file = "ruff-0.11.2-py3-none-win_amd64.whl", hash = "sha256:3170150172a8f994136c0c66f494edf199a0bbea7a409f649e4bc8f4d7084080"}, + {file = "ruff-0.11.2-py3-none-win_arm64.whl", hash = "sha256:52933095158ff328f4c77af3d74f0379e34fd52f175144cefc1b192e7ccd32b4"}, + {file = "ruff-0.11.2.tar.gz", hash = "sha256:ec47591497d5a1050175bdf4e1a4e6272cddff7da88a2ad595e1e326041d8d94"}, +] + +[[package]] +name = "rustworkx" +version = "0.16.0" +description = "A python graph library implemented in Rust" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "rustworkx-0.16.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:476a6c67b0142acd941691943750cc6737a48372304489969c2b62d30aaf4c27"}, + {file = "rustworkx-0.16.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bef2ef42870f806af93979b457e240f6dfa4f867ca33965c620f3a804409ed3a"}, + {file = "rustworkx-0.16.0-cp39-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0db3a73bf68b3e66c08322a2fc95d3aa663d037d9b4e49c3509da4898d3529cc"}, + {file = "rustworkx-0.16.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f12a13d7486234fa2a84746d5e41f436bf9df43548043e7a232f48804ff8c61"}, + {file = "rustworkx-0.16.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89efd5c3a4653ddacc55ca39f28b261d43deec7d678f8f8fc6b76b5087f1dfea"}, + {file = "rustworkx-0.16.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec0c12aac8c54910ace20ac6ada4b890cd39f95f69100514715f8ad7af9041e4"}, + {file = "rustworkx-0.16.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d650e39fc1a1534335f7517358ebfc3478bb235428463cfcd7c5750d50377b33"}, + {file = "rustworkx-0.16.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:293180b83509ee9bff4c3af7ccc1024f6528d61b65d0cb7320bd31924f10cb71"}, + {file = "rustworkx-0.16.0-cp39-abi3-win32.whl", hash = "sha256:040c4368729cf502f756a3b0ff5f1c6915fc389f74dcc6afc6c3833688c97c01"}, + {file = "rustworkx-0.16.0-cp39-abi3-win_amd64.whl", hash = "sha256:905df608843c32fa45ac023687769fe13056edf7584474c801d5c50705d76e9b"}, + {file = "rustworkx-0.16.0.tar.gz", hash = "sha256:9f0dcb83f38d5ca2c3a683eb9b6951c8aec3262fbfe5141946a7ee5ba37e0bb6"}, +] + +[package.dependencies] +numpy = ">=1.16.0,<3" + +[package.extras] +all = ["matplotlib (>=3.0)", "pillow (>=5.4)"] +graphviz = ["pillow (>=5.4)"] +mpl = ["matplotlib (>=3.0)"] + +[[package]] +name = "s3transfer" +version = "0.10.4" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:244a76a24355363a68164241438de1b72f8781664920260c48465896b712a41e"}, + {file = "s3transfer-0.10.4.tar.gz", hash = "sha256:29edc09801743c21eb5ecbc617a152df41d3c287f67b615f73e5f750583666a7"}, +] + +[package.dependencies] +botocore = ">=1.33.2,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] + +[[package]] +name = "schema" +version = "0.7.5" +description = "Simple data validation library" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "schema-0.7.5-py2.py3-none-any.whl", hash = "sha256:f3ffdeeada09ec34bf40d7d79996d9f7175db93b7a5065de0faa7f41083c1e6c"}, + {file = "schema-0.7.5.tar.gz", hash = "sha256:f06717112c61895cabc4707752b88716e8420a8819d71404501e114f91043197"}, +] + +[package.dependencies] +contextlib2 = ">=0.5.5" + +[[package]] +name = "semantic-version" +version = "2.10.0" +description = "A library implementing the 'SemVer' scheme." +optional = false +python-versions = ">=2.7" +groups = ["main"] +files = [ + {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, + {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, +] + +[package.extras] +dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1) ; python_version == \"3.4\"", "coverage", "flake8", "nose2", "readme-renderer (<25.0) ; python_version == \"3.4\"", "tox", "wheel", "zest.releaser[recommended]"] +doc = ["Sphinx", "sphinx-rtd-theme"] + +[[package]] +name = "semgrep" +version = "1.116.0" +description = "Lightweight static analysis for many languages. Find bug variants with patterns that look like source code." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "semgrep-1.116.0-cp39.cp310.cp311.py39.py310.py311-none-macosx_10_14_x86_64.whl", hash = "sha256:e6c6df05b39fb18de731a6450a9abd55f9186a0eddb63f0bee3a8e7905ce9652"}, + {file = "semgrep-1.116.0-cp39.cp310.cp311.py39.py310.py311-none-macosx_11_0_arm64.whl", hash = "sha256:8cd6af5d79f31266a21786ca07db28a172532cf9035c5a49b067021d8e1e37df"}, + {file = "semgrep-1.116.0-cp39.cp310.cp311.py39.py310.py311-none-musllinux_1_0_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23297f669f2acf4ffd06dd202450d04a800c412660375c8b6671c1a796f847eb"}, + {file = "semgrep-1.116.0-cp39.cp310.cp311.py39.py310.py311-none-musllinux_1_0_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f290917529410bc524c47949e03f27f9e649608783429d76c08369b42e8c273"}, + {file = "semgrep-1.116.0-cp39.cp310.cp311.py39.py310.py311-none-win_amd64.whl", hash = "sha256:2c2d695ae1501573985e2fe9559733085793540db3918d2e811bb0b1872ccb0d"}, + {file = "semgrep-1.116.0.tar.gz", hash = "sha256:c74af400998d7ce2e83631a7741b4a2e82317cccf699d7eecec788d0bc11ec4b"}, +] + +[package.dependencies] +attrs = ">=21.3" +boltons = ">=21.0,<22.0" +click = ">=8.1,<9.0" +click-option-group = ">=0.5,<1.0" +colorama = ">=0.4.0,<0.5.0" +defusedxml = ">=0.7.1,<0.8.0" +exceptiongroup = ">=1.2.0,<1.3.0" +glom = ">=22.1,<23.0" +jsonschema = ">=4.6,<5.0" +opentelemetry-api = ">=1.25.0,<1.26.0" +opentelemetry-exporter-otlp-proto-http = ">=1.25.0,<1.26.0" +opentelemetry-instrumentation-requests = ">=0.46b0,<1.0" +opentelemetry-sdk = ">=1.25.0,<1.26.0" +packaging = ">=21.0" +peewee = ">=3.14,<4.0" +requests = ">=2.22,<3.0" +rich = ">=13.5.2,<13.6.0" +"ruamel.yaml" = ">=0.18.5" +tomli = ">=2.0.1,<2.1.0" +typing-extensions = ">=4.2,<5.0" +urllib3 = ">=2.0,<3.0" +wcmatch = ">=8.3,<9.0" + +[[package]] +name = "send2trash" +version = "1.8.3" +description = "Send file to trash natively under Mac OS X, Windows and Linux" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] +files = [ + {file = "Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9"}, + {file = "Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf"}, +] + +[package.extras] +nativelib = ["pyobjc-framework-Cocoa ; sys_platform == \"darwin\"", "pywin32 ; sys_platform == \"win32\""] +objc = ["pyobjc-framework-Cocoa ; sys_platform == \"darwin\""] +win32 = ["pywin32 ; sys_platform == \"win32\""] + +[[package]] +name = "setuptools" +version = "78.1.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8"}, + {file = "setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "smmap" +version = "5.0.2" +description = "A pure Python implementation of a sliding window memory map manager" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, + {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + +[[package]] +name = "soupsieve" +version = "2.6" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + +[[package]] +name = "spdx-tools" +version = "0.8.3" +description = "SPDX parser and tools." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "spdx-tools-0.8.3.tar.gz", hash = "sha256:68b8f9ce2893b5216bd90b2e63f1c821c2884e4ebc4fd295ebbf1fa8b8a94b93"}, + {file = "spdx_tools-0.8.3-py3-none-any.whl", hash = "sha256:638fd9bd8be61901316eb6d063574e16d5403a1870073ec4d9241426a997501a"}, +] + +[package.dependencies] +beartype = "*" +click = "*" +license-expression = "*" +ply = "*" +pyyaml = "*" +rdflib = "*" +semantic-version = "*" +uritools = "*" +xmltodict = "*" + +[package.extras] +code-style = ["black", "flake8", "isort"] +development = ["black", "flake8", "isort", "networkx", "pytest"] +graph-generation = ["networkx", "pygraphviz"] +test = ["pyshacl", "pytest", "tzdata"] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "stevedore" +version = "5.4.1" +description = "Manage dynamic plugins for Python applications" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe"}, + {file = "stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b"}, +] + +[package.dependencies] +pbr = ">=2.0.0" + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "termcolor" +version = "2.3.0" +description = "ANSI color formatting for output in terminal" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, + {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + +[[package]] +name = "terminado" +version = "0.18.1" +description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0"}, + {file = "terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"}, +] + +[package.dependencies] +ptyprocess = {version = "*", markers = "os_name != \"nt\""} +pywinpty = {version = ">=1.1.0", markers = "os_name == \"nt\""} +tornado = ">=6.1.0" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] +typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] + +[[package]] +name = "tinycss2" +version = "1.4.0" +description = "A tiny CSS parser" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"}, + {file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["pytest", "ruff"] + +[[package]] +name = "tomli" +version = "2.0.2" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] +markers = {dev = "python_full_version <= \"3.11.0a6\""} + +[[package]] +name = "tornado" +version = "6.4.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"}, + {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"}, + {file = "tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec"}, + {file = "tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946"}, + {file = "tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf"}, + {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634"}, + {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73"}, + {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c"}, + {file = "tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482"}, + {file = "tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38"}, + {file = "tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b"}, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typeguard" +version = "2.13.3" +description = "Run-time type checker for Python" +optional = false +python-versions = ">=3.5.3" +groups = ["main"] +files = [ + {file = "typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1"}, + {file = "typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4"}, +] + +[package.extras] +doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["mypy ; platform_python_implementation != \"PyPy\"", "pytest", "typing-extensions"] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20241206" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"}, + {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"}, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20250402" +description = "Typing stubs for PyYAML" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_pyyaml-6.0.12.20250402-py3-none-any.whl", hash = "sha256:652348fa9e7a203d4b0d21066dfb00760d3cbd5a15ebb7cf8d33c88a49546681"}, + {file = "types_pyyaml-6.0.12.20250402.tar.gz", hash = "sha256:d7c13c3e6d335b6af4b0122a01ff1d270aba84ab96d1a1a1063ecba3e13ec075"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, + {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + +[[package]] +name = "unidiff" +version = "0.7.5" +description = "Unified diff parsing/metadata extraction library." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "unidiff-0.7.5-py2.py3-none-any.whl", hash = "sha256:c93bf2265cc1ba2a520e415ab05da587370bc2a3ae9e0414329f54f0c2fc09e8"}, + {file = "unidiff-0.7.5.tar.gz", hash = "sha256:2e5f0162052248946b9f0970a40e9e124236bf86c82b70821143a6fc1dea2574"}, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +description = "RFC 6570 URI Template Processor" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, + {file = "uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"}, +] + +[package.extras] +dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake8-commas", "flake8-comprehensions", "flake8-continuation", "flake8-datetimez", "flake8-docstrings", "flake8-import-order", "flake8-literal", "flake8-modern-annotations", "flake8-noqa", "flake8-pyproject", "flake8-requirements", "flake8-typechecking-import", "flake8-use-fstring", "mypy", "pep8-naming", "types-PyYAML"] + +[[package]] +name = "uritools" +version = "4.0.3" +description = "URI parsing, classification and composition" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "uritools-4.0.3-py3-none-any.whl", hash = "sha256:bae297d090e69a0451130ffba6f2f1c9477244aa0a5543d66aed2d9f77d0dd9c"}, + {file = "uritools-4.0.3.tar.gz", hash = "sha256:ee06a182a9c849464ce9d5fa917539aacc8edd2a4924d1b7aabeeecabcae3bc2"}, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, + {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wcmatch" +version = "8.5.2" +description = "Wildcard/glob file name matcher." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "wcmatch-8.5.2-py3-none-any.whl", hash = "sha256:17d3ad3758f9d0b5b4dedc770b65420d4dac62e680229c287bf24c9db856a478"}, + {file = "wcmatch-8.5.2.tar.gz", hash = "sha256:a70222b86dea82fb382dd87b73278c10756c138bd6f8f714e2183128887b9eb2"}, +] + +[package.dependencies] +bracex = ">=2.1.1" + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "webcolors" +version = "24.11.1" +description = "A library for working with the color formats defined by HTML and CSS." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9"}, + {file = "webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, + {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, +] + +[package.extras] +docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "wrapt" +version = "1.17.2" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, + {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, + {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, + {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, + {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, + {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, + {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, + {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, + {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, + {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, + {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, + {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, + {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, + {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, + {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, + {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, + {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, +] + +[[package]] +name = "xmltodict" +version = "0.14.2" +description = "Makes working with XML feel like you are working with JSON" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "xmltodict-0.14.2-py2.py3-none-any.whl", hash = "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac"}, + {file = "xmltodict-0.14.2.tar.gz", hash = "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553"}, +] + +[[package]] +name = "yarl" +version = "1.18.3" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, + {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, + {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, + {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, + {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, + {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, + {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, + {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, + {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, + {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, + {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, + {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, + {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.0" + +[[package]] +name = "zipp" +version = "3.21.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.10" -content-hash = "cbead15f4f079224bce03479539e97db9bd2499aca67cb9bc49b941a5543575c" +content-hash = "d2f72ebd2761f2c85b9f9d6df25df027ff1cc8b8c0c80ad59db92c736b5a4f33" diff --git a/pyproject.toml b/pyproject.toml index f75d62d..96df912 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,24 +1,81 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 [tool.poetry] name = "automated-security-helper" -version = "2.0.1" -description = "" -authors = ["Nate Ferrell ", "Nathan Bates "] -license = "Apache-2.0" +version = "3.0.0" +description = "Automated Security Helper for GitHub Actions" readme = "README.md" +license = "Apache-2.0" repository = "https://github.com/awslabs/automated-security-helper" +authors = [ + "Daniel Begimher ", + "Madison Steiner ", + "Nate Ferrell ", + "Nathan Bates ", + "Rafael Pereyra ", + "Tim Hahn ", +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +packages = [ + {include = "automated_security_helper", from = "src"}, + {include = "asharp", from = "src"}, +] [tool.poetry.scripts] -asharp = 'automated_security_helper.asharp:main' +asharp = 'asharp.asharp:main' +ashv3 = "automated_security_helper.orchestrator:main" +schemagen = 'automated_security_helper.schemas.generate_schemas:main' +get-scan-set = 'automated_security_helper.utils.get_scan_set:main' [tool.poetry.dependencies] python = "^3.10" regex = "^2024.5.15" -pydantic = "^2.7.3" +pydantic = "^2.11.1" +pytest = "^8.3.5" +bandit = "^1.8.3" +nbconvert = "^7.16.6" +jupyterlab = "^4.3.6" +checkov = "^3.2.395" +pathspec = "^0.12.1" +semgrep = "^1.116.0" +junit-xml = "^1.9" +aws-cdk-lib = "^2.188.0" +constructs = "^10.4.2" +cdk-nag = "^2.35.64" +cfn-flip = "^1.3.0" [tool.poetry.group.dev.dependencies] -black = "^24.4.2" +ruff = "^0.11.2" +pytest-cov = "^6.1.0" +pytest-mock = "^3.14.0" +mypy = "^1.15.0" +types-pyyaml = "^6.0.12.20250402" + +[tool.mypy] +plugins = ["pydantic.mypy"] + +follow_imports = "silent" +warn_redundant_casts = true +warn_unused_ignores = true +disallow_any_generics = true +no_implicit_reexport = true +disallow_untyped_defs = true + +[tool.pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "-rA -l --cov-report term-missing --junit-xml=test-results/pytest.junit.xml --cov=automated_security_helper" [build-system] requires = ["poetry-core"] diff --git a/quickstart/README.md b/quickstart/README.md index dc39cca..5e4298e 100644 --- a/quickstart/README.md +++ b/quickstart/README.md @@ -41,4 +41,4 @@ In this case, another environment with the same name already exists. You will ne - [AWS Cloud9 User Guide](https://docs.aws.amazon.com/cloud9/latest/user-guide/welcome.html) - [AWS CloudFormation User Guide](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html) -- [AWS CloudFormation Troubleshooting](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/troubleshooting.html) \ No newline at end of file +- [AWS CloudFormation Troubleshooting](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/troubleshooting.html) diff --git a/src/asharp/__init__.py b/src/asharp/__init__.py new file mode 100644 index 0000000..6e41b73 --- /dev/null +++ b/src/asharp/__init__.py @@ -0,0 +1,6 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import importlib.metadata + +__version__ = importlib.metadata.version("automated_security_helper") diff --git a/src/automated_security_helper/asharp.py b/src/asharp/asharp.py similarity index 52% rename from src/automated_security_helper/asharp.py rename to src/asharp/asharp.py index 9d7914b..ea4b266 100644 --- a/src/automated_security_helper/asharp.py +++ b/src/asharp/asharp.py @@ -5,7 +5,10 @@ # asharp.py / Automated Security Helper - Aggregated Report Parser # A tool to parse, ingest, and output ASH aggregated reports. +import csv import datetime +from glob import glob +from pathlib import Path import regex as re import argparse import json @@ -13,18 +16,31 @@ from automated_security_helper import __version__ # default filenames for input and output -DEF_INFILE='aggregated_results.txt' -DEF_OUTFILE='asharp_results.json' +DEF_INFILE = "aggregated_results.txt" +DEF_OUTFILE = "asharp_results.json" # handle command line -cliparser = argparse.ArgumentParser( description="Automated Security Helper Aggregated Report Parser") -cliparser.add_argument('-i', '--input', help=f"File contained ASH aggregated results ({DEF_INFILE})") -cliparser.add_argument('-o', '--output', help="File to write ARP resulting model") -#cliparser.add_argument('-j', '--jq', help="Parse raw file and filter with jq") -cliparser.add_argument('-R', '--retain', action='store_true', help="TEMPORARY - Do not modify raw data output") -cliparser.add_argument('-s', '--stdout', action='store_true', help="Output ARP resulting model to console") -cliparser.add_argument('-v', '--verbose', action='store_true', help="Output instrumentation log") -cliparser.add_argument('--version', action='store_true', help="Output ASHARP version") +cliparser = argparse.ArgumentParser( + description="Automated Security Helper Aggregated Report Parser" +) +cliparser.add_argument( + "-i", "--input", help=f"File contained ASH aggregated results ({DEF_INFILE})" +) +cliparser.add_argument("-o", "--output", help="File to write ARP resulting model") +# cliparser.add_argument('-j', '--jq', help="Parse raw file and filter with jq") +cliparser.add_argument( + "-R", + "--retain", + action="store_true", + help="TEMPORARY - Do not modify raw data output", +) +cliparser.add_argument( + "-s", "--stdout", action="store_true", help="Output ARP resulting model to console" +) +cliparser.add_argument( + "-v", "--verbose", action="store_true", help="Output instrumentation log" +) +cliparser.add_argument("--version", action="store_true", help="Output ASHARP version") args = cliparser.parse_args() # simply output version and exit @@ -45,33 +61,39 @@ # basic debug log (this could be added to the model as asharp metadata) debug_log = [] + # basic instrumentation -def debug(x, y = 0): +def debug(x, y=0): def debugout(s): print(s) debug_log.append(s) + if args.verbose or y == 255: debugout(x) - if y == 0: return + if y == 0: + return debugout("!! access the help menu with -h or --help") exit(255) + # json encoder for datetime object class DateTimeEncoder(JSONEncoder): def default(self, obj): if isinstance(obj, (datetime.date, datetime.datetime)): - return obj.isoformat() + return obj.isoformat(timespec="seconds") + ### ## Parsing and extraction functions # + # extract embedded JSON from output def ExtractJsonFromData(data): - # does minimal necessary to validate JSON # uses backref (should reduce regex greediness) - result = re.findall(r"""(?xmsi) + result = re.findall( + r"""(?xmsi) ^[\n\s\t]* (?P (?> @@ -80,17 +102,53 @@ def ExtractJsonFromData(data): \[(?:[^[\]]|"(?:\\.|[^"\\])*"|(?&json))*\] # arrays ) ) - """, data) + """, + data, + ) - #debug(json.dumps(result, cls=DateTimeEncoder, indent=4)) + # debug(json.dumps(result, cls=DateTimeEncoder, indent=4)) return result + +# normalize json based on each tool format +def NormalizeJson(tool, file, data): + # normalize npm audit + if tool == "npm": + # extract vulnerabilities + vulns_raw = data["vulnerabilities"] + # loop on each vulnerability, and add to an array + vulns = [] + for vuln in vulns_raw: + item = vulns_raw[vuln] + if type(item["fixAvailable"]) is not bool: + item["fixDetails"] = item["fixAvailable"] + item["fixAvailable"] = True + + vulns.append(item) + + data["vulnerabilities"] = vulns + + # normalize bandit results + elif tool == "bandit": + # extract metrics + metrics_raw = data["metrics"] + # loop on each metric, and add to an array + metrics = [] + for metric in metrics_raw: + metrics.append( + {"file": metric.replace(file, ""), "metrics": metrics_raw[metric]} + ) + data["metrics"] = metrics + + return data + + # extract ASH invoked provider sections def ExtractSectionsFromData(data): - # find all section boundaries and retrieve innards # uses a backref (should reduce regex greediness) - result = re.findall(r'''(?xmsi) + result = re.findall( + r"""(?xmsi) #############################################\n Start\s+of\s+([^\n]+)\n # start marker #############################################\n @@ -98,14 +156,16 @@ def ExtractSectionsFromData(data): #############################################\n End\s+of\s+(\1)\n # end marker ############################################# - ''', data) + """, + data, + ) - #debug(json.dumps(result, cls=DateTimeEncoder, indent=4)) + # debug(json.dumps(result, cls=DateTimeEncoder, indent=4)) return result + # separate Grype, Syft, and Semgrep findings def ExtractSecondarySections(data): - # find all section boundaries and retrieve innards # uses a backref (should reduce regex greediness) # @@ -113,100 +173,111 @@ def ExtractSecondarySections(data): # section and hence, miss reporting something. This means # that all section start/end markers are IDENTICAL - result = re.findall(r'''(?xmsi) + result = re.findall( + r"""(?xmsi) >>>>>>\s+Begin\s+(\S+)\s+.+?\s+for\s+(.+?)\s+>>>>>>\n # start marker (.*?) # provider output <<<<<<\s+End\s+(\1)\s+.+?\s+for\s+(\2)\s+<<<<<< # end marker - ''', data) + """, + data, + ) - #debug(json.dumps(result, cls=DateTimeEncoder, indent=4)) + # debug(json.dumps(result, cls=DateTimeEncoder, indent=4)) return result -# need to parse cdk provider here and addModel accordingly -def parseCdkProvider(data): - - # rx for Warnings and Errors - results = re.findall(r'''(?xmsi) - - # total pita - this will not extract the 2nd pattern, even if swapped - # works well enough for now. will revisit later. - - ^[\n]* # just in case.. - ( - \[(Warning|Error)\s+at\s+([^\]]+)\]\s+ - ( - ([^:]+):\s+.+? # error refernce id - | - [^:]+:\s+'(Aws.+?)'\s+.+? # warning reference id - ) - [\n]+ - ) - ''', data) - - cdks = [] - for result in results: - o = { - 'raw': result[0], - 'severity': result[1], - 'ref': result[2], - 'id': result[4], - 'result': result[3] - } - - #debug(json.dumps(o, cls=DateTimeEncoder, indent=4)) - cdks.append(json.dumps(o, cls=DateTimeEncoder, indent=0)) - if not len(cdks): +# need to parse cdk provider here and addModel accordingly +def parseCdkProvider(data, aggfile): + # # rx for Warnings and Errors + # results = re.findall(r'''(?xmsi) + + # # total pita - this will not extract the 2nd pattern, even if swapped + # # works well enough for now. will revisit later. + + # ^[\n]* # just in case.. + # ( + # \[(Warning|Error)\s+at\s+([^\]]+)\]\s+ + # ( + # ([^:]+):\s+.+? # error refernce id + # | + # [^:]+:\s+'(Aws.+?)'\s+.+? # warning reference id + # ) + # [\n]+ + # ) + # ''', data) + + cdk_nag_output_dir = Path(aggfile).parent.joinpath("scanners").joinpath("cdk") + results = [] + debug(f"cdk_nag_output_dir = {cdk_nag_output_dir}") + for cdk_csv in glob(f"{cdk_nag_output_dir.as_posix()}/**/*NagReport.csv"): + debug(f"cdk_csv = {cdk_csv}") + cdk_csv = Path(cdk_csv) + with open(cdk_csv, "r") as f: + findings = csv.DictReader(f, skipinitialspace=True) + for finding in findings: + fmtd = { + "raw": finding, + "file": cdk_csv.name, + "rule_id": finding["Rule ID"], + "resource_id": finding["Resource ID"], + "compliance": finding["Compliance"], + "exception_reason": finding["Exception Reason"], + "rule_level": finding["Rule Level"], + "rule_info": finding["Rule Info"], + } + results.append(json.dumps(fmtd, cls=DateTimeEncoder, indent=0)) + + if not len(results): return - return cdks + return results -# parse out ASH report sections -def ParseAshSections(aggfile): +# parse out ASH report sections +def ParseAshSections(aggfile, aggregate_file_path): # find, separate, and extract each providers output section sections = ExtractSectionsFromData(aggfile) # does data contain valid sections? if not sections: - debug('!! Unable to find any section identifiers.') - debug('!! Is this an ASH aggregate_results.txt file?', 255) + debug("!! Unable to find any section identifiers.") + debug("!! Is this an ASH aggregate_results.txt file?", 255) # iterate through each section for section in sections: - # sanity check - make sure the regex wasn't overly greedy if section[0] != section[2]: - debug('!! Start and end do not match!!', 255) + debug("!! Start and end do not match!!", 255) # identify the provider from the report filename - prx = re.search(r'(grype|yaml|py|cdk|git|js)_report', section[0]) + prx = re.search(r"(grype|yaml|py|cdk|git|js)_report", section[0]) if not prx: - debug(f'!! No provider identified for {section[0]}', 255) + debug(f"!! No provider identified for {section[0]}", 255) provider = prx.group(1) # remove ansi, unprintables, and unicode from provider outout (just data section) # might be better to escape these.. thoughts? - dsanitize = section[1].replace(r'\x1B(?:[@-Z\\]|\[[0-?]*[ -/]*[@-~])', '') - dsanitize = dsanitize.replace(r'[^\x00-\x7F]+', '') + dsanitize = section[1].replace(r"\x1B(?:[@-Z\\]|\[[0-?]*[ -/]*[@-~])", "") + dsanitize = dsanitize.replace(r"[^\x00-\x7F]+", "") ##dsanitize = dsanitize.replace(r'\u[a-fA-F0-9]{4}', '') ##dsanitize = dsanitize.replace(r'[\u0000-\uffff]', '') ##dsanitize = dsanitize.replace(r'\p{Mn}+', '') - dsanitize = dsanitize.encode('ascii', 'ignore').decode() + dsanitize = dsanitize.encode("ascii", "ignore").decode() # collect the parsed information aggregated[provider] = { - 'file': section[0], - 'provider': provider, + "file": section[0], + "provider": provider, ##'output': section[1], # unnecessary - removal occurs below - 'data': dsanitize if dsanitize else section[1] + "data": dsanitize if dsanitize else section[1], } # creates a model object representing the subsection output models = [] + def addModels(tool, file, arr): for a in arr: - models.append({ 'tool': tool, 'file': file, 'data': a }) + models.append({"tool": tool, "file": file, "data": a}) return # need to separate findings found in different subsections @@ -216,14 +287,16 @@ def addModels(tool, file, arr): gmodel = None # if this is cdk provider, then we need to manually extract & generate json - if provider in ['cdk']: - gmodel = parseCdkProvider(subsection[2]) + if provider in ["cdk"]: + gmodel = parseCdkProvider(subsection[2], aggregate_file_path) else: gmodel = ExtractJsonFromData(subsection[2]) # continue to next if no model data found if not gmodel: - debug(f'-- {subsection[0]} model is {len(subsection[2])} and did not detect json') + debug( + f"-- {subsection[0]} model is {len(subsection[2])} and did not detect json" + ) continue # add the extracted data to the models array for further processing @@ -232,14 +305,15 @@ def addModels(tool, file, arr): # process the extracted json object data (needs to be parsed/loaded) arr = [] for m in models: - data = m['data'] - tool = m['tool'] - file = m['file'] + data = m["data"] + tool = m["tool"] + file = m["file"] # attempt to validate the extraction try: o = json.loads(data) - arr.append({'tool': tool, 'file': file, 'data': o}) + o = NormalizeJson(tool, file, o) + arr.append({"tool": tool, "file": file, "data": o}) # COMMENT THE FOLLOWING OUT TO RETAIN UNALTERED RAW OUTPUT # @@ -251,43 +325,52 @@ def addModels(tool, file, arr): # - the "output" object (1 page up) was to capture raw output # ..but maybe we prefer one over the other # - aggregated[provider]['data'] = aggregated[provider]['data'].replace(data, '') + aggregated[provider]["data"] = aggregated[provider]["data"].replace( + data, "" + ) except Exception as e: - debug(f'!! error - {e} {m}') - pass # hmmm, continue? + debug(f"!! error - {e} {m}") + pass # hmmm, continue? - debug(f'-- {tool} model starts with "{data[0]}", contains {len(data)} bytes, and ends with "{data[:-5]}"') + debug( + f'-- {tool} model starts with "{data[0]}", contains {len(data)} bytes, and ends with "{data[:-5]}"' + ) # place the extacted json objects into the provider data model - if arr: aggregated[provider]['model'] = arr + if arr: + aggregated[provider]["model"] = arr - #debug(json.dumps(aggregated[provider]['model'], cls=DateTimeEncoder, indent=4)) + # debug(json.dumps(aggregated[provider]['model'], cls=DateTimeEncoder, indent=4)) return aggregated + ## Begin execution # + def main(): # read the ASH aggregated report as text blob if not args.input: debug("!! provide the path to the ASH aggregate report", 255) - with open(args.input, 'r') as file: + aggregate_file_path = args.input + with open(aggregate_file_path, "r") as file: aggfile = file.read() # parse ASH report sections - ParseAshSections(aggfile) + ParseAshSections(aggfile, aggregate_file_path) # if output file specified then write to file if args.output: - with open(args.output, 'w') as file: - file.write(json.dumps(aggregated, cls=DateTimeEncoder, indent=4)) + with open(args.output, "w") as file: + file.write(json.dumps(aggregated, cls=DateTimeEncoder)) # output it to screen if not args.output or args.stdout: print(json.dumps(aggregated, cls=DateTimeEncoder, indent=4)) + if __name__ == "__main__": main() diff --git a/src/automated_security_helper/__init__.py b/src/automated_security_helper/__init__.py index 7841263..6e41b73 100644 --- a/src/automated_security_helper/__init__.py +++ b/src/automated_security_helper/__init__.py @@ -3,4 +3,4 @@ import importlib.metadata -__version__ = importlib.metadata.version('automated_security_helper') +__version__ = importlib.metadata.version("automated_security_helper") diff --git a/src/automated_security_helper/adapters/__init__.py b/src/automated_security_helper/config/__init__.py similarity index 100% rename from src/automated_security_helper/adapters/__init__.py rename to src/automated_security_helper/config/__init__.py diff --git a/src/automated_security_helper/config/config.py b/src/automated_security_helper/config/config.py new file mode 100644 index 0000000..1186a6a --- /dev/null +++ b/src/automated_security_helper/config/config.py @@ -0,0 +1,406 @@ +from pydantic import BaseModel, ConfigDict, Field, field_validator +from typing import Annotated, Any, Callable, List, Dict, Literal +from automated_security_helper.models.data_interchange import ExportFormat +from automated_security_helper.config.scanner_types import ( + BanditScannerConfig, + ScannerBaseConfig, + CfnNagScannerConfig, + CheckovScannerConfig, + CustomScannerConfig, + GitSecretsScannerConfig, + JupyterNotebookScannerConfig, + NpmAuditScannerConfig, + SemgrepScannerConfig, + CdkNagScannerConfig, + GrypeScannerConfig, + SyftScannerConfig, +) + + +class FileInvocationConfig(BaseModel): + """Configuration for file scanning.""" + + model_config = ConfigDict(extra="forbid") + + include: Annotated[ + List[str], + Field( + description="List of file patterns to include. Defaults to an empty list, which includes all files.", + examples=[ + "**/*", + ], + ), + ] = [] + exclude: Annotated[ + List[str], + Field( + description="List of file patterns to exclude. Defaults to an empty list, which excludes no files.", + examples=[ + "tests/", + ], + ), + ] = [] + + +class ScannerPluginConfig(ScannerBaseConfig): + """Configuration model for scanner plugins.""" + + command: Annotated[ + str, + Field( + description="The command to invoke the scanner, typically the binary or path to a script" + ), + ] = None + args: Annotated[ + List[str], + Field( + description="List of arguments to pass to the scanner command. Defaults to an empty list." + ), + ] = [] + invocation_mode: Annotated[ + Literal["directory", "file"], + Field( + description="Whether to run the scanner on a directory or a file. Defaults to 'directory' to scan the entire directory. If set to 'file', uses the file_config values to identify the files to scan and scan each one individually." + ), + ] = "directory" + file_config: Annotated[ + FileInvocationConfig, + Field( + description="Configuration for file scanning. Required if invocation_mode is 'file'." + ), + ] = FileInvocationConfig() + output_format: Annotated[ + ExportFormat | None, + Field(description="Expected output format from the scanner itself."), + ] = None + scan_path_arg_position: Annotated[ + Literal["before_args", "after_args"], + Field( + description="Whether to place the scan path argument before or after the scanner command args. Defaults to 'after_args'." + ), + ] = "after_args" + scan_path_arg: Annotated[ + str | None, + Field( + description="Argument to pass the scan path to when invoking the scanner command. Defaults to not including an arg for the scan path value, which results in the path being passed to the scanner as a positional argument at the scan_path_arg_position specified. If the ", + examples=[ + "-f", + "--file", + "-p", + "--path", + ], + ), + ] = None + format_arg_position: Annotated[ + Literal["before_args", "after_args"], + Field( + description="Whether to place the format argument before or after the scanner command args. Defaults to 'before_args'." + ), + ] = "before_args" + format_arg: Annotated[ + str | None, + Field( + description="Argument to pass the format option to when invoking the scanner command. Defaults to not including an arg for the format value, which results in the format option being passed to the scanner as a positional argument at the format_arg_position specified. If a value is provided, the value will be passed into the runtime args prior to the format option.", + examples=[ + "--format", + "-f", + "--output-format", + ], + ), + ] = None + format_arg_value: Annotated[ + str | None, + Field( + description="Value to pass to the format argument when invoking the scanner command. Defaults to 'json', but typically is explicitly set in ScannerPlugin implementations as a frozen property.", + examples=[ + "json", + "sarif", + "cyclonedx", + ], + ), + ] = "json" + output_arg: Annotated[ + str | None, + Field( + description="Argument to pass the output option to when invoking the scanner command. Defaults to not including an arg for the output value, which results in the output option being passed to the scanner as a positional argument at the format_arg_position specified. If a value is provided, the value will be passed into the runtime args prior to the output option.", + examples=[ + "--output", + "-o", + "--outfile", + ], + ), + ] = None + output_arg_position: Annotated[ + Literal["before_args", "after_args"], + Field( + description="Whether to place the output argument before or after the scanner command args. Defaults to 'before_args'." + ), + ] = "before_args" + get_tool_version_command: Annotated[ + List[str] | Callable[[], str] | None, + Field(description="Command to run that should return the scanner version"), + ] = None + output_stream: Annotated[ + Literal["stdout", "stderr", "file"], + Field( + description="Where to read scanner output from. Can be 'stdout', 'stderr' or 'file'. Defaults to 'stdout' to capture the output of the scanner directly." + ), + ] = "stdout" + + @field_validator("get_tool_version_command", check_fields=False) + @classmethod + def resolve_tool_version( + cls, get_tool_version_command: List[str] | Callable[[], str] + ) -> List[str] | str: + if callable(get_tool_version_command): + return get_tool_version_command() + else: + return get_tool_version_command + + @field_validator("type", mode="before") + @classmethod + def normalize_scanner_type(cls, scanner_type: Any) -> str: + """Normalize scanner type value before validation.""" + # Type checking + if not isinstance(scanner_type, str): + raise ValueError(f"Scanner type must be string, got {type(scanner_type)}") + + # Convert/normalize value + scanner_type = str(scanner_type).strip().upper() + if scanner_type == "STATIC": + scanner_type = "SAST" + + return scanner_type + + def model_post_init(self, context): + super().model_post_init(context) + if not hasattr(self, "name"): + self.name = self.command + + +class BuildConfig(BaseModel): + """Configuration model for build-time settings.""" + + model_config = ConfigDict(extra="allow") + + mode: Annotated[ + Literal["ASH_MODE_ONLINE", "ASH_MODE_OFFLINE"], + Field(description="Build mode for the container"), + ] = "ASH_MODE_ONLINE" + tool_install_scripts: Annotated[ + Dict[str, List[str]], + Field(description="Map of tool names to their installation scripts"), + ] = {} + custom_scanners: Annotated[ + List[ScannerPluginConfig], + Field(description="Scanner configurations by type"), + ] = [] + + +class ScannerTypeConfig(BaseModel): + """Configuration model for scanner type specific settings.""" + + model_config = ConfigDict(extra="allow") + enabled: Annotated[ + bool, Field(description="Whether this scanner type is enabled") + ] = True + + +# class SecurityScanConfig(BaseModel): +# """Configuration model for security scanning settings.""" + +# model_config = ConfigDict(extra="allow") +# sast: Annotated[ +# List[ +# Union[ +# CustomScannerConfig, +# BanditScannerConfig, +# SemgrepScannerConfig, +# CdkNagScannerConfig, +# GrypeScannerConfig, +# ] +# ], +# Field(description="List of SAST scanners to enable"), +# ] = [ +# BanditScannerConfig(), +# CdkNagScannerConfig(), +# GrypeScannerConfig(), +# SemgrepScannerConfig(), +# ] +# sbom: Annotated[ +# List[ +# Union[ +# CustomScannerConfig, +# SyftScannerConfig, +# ] +# ], +# Field(description="List of SBOM scanners to enable"), +# ] = [ +# SyftScannerConfig(), +# ] + + +class ParserConfig(ScannerBaseConfig): + """Configuration model for scanner result parsers.""" + + output_format: Annotated[ + str, + Field(description="Expected output format from the scanner"), + ] + finding_key: Annotated[ + str, + Field(description="Key used to identify individual findings in the output"), + ] = "findings" + severity_mapping: Annotated[ + Dict[str, str], + Field( + description="Mapping of scanner-specific severity levels to standardized levels" + ), + ] = {} + location_mapping: Annotated[ + Dict[str, str], + Field( + description="Mapping of scanner-specific location fields to standardized fields" + ), + ] = {} + + +class ScannerClassConfig(BaseModel): + """Configuration model for scanner classes.""" + + model_config = ConfigDict(extra="allow") + + +class ScannerListConfig(ScannerClassConfig): + __pydantic_extra__: Dict[str, CustomScannerConfig | bool] + + +class SASTScannerListConfig(ScannerListConfig): + bandit: Annotated[BanditScannerConfig | bool, Field()] = BanditScannerConfig() + jupyter: Annotated[JupyterNotebookScannerConfig | bool, Field()] = ( + JupyterNotebookScannerConfig() + ) + cdknag: Annotated[CdkNagScannerConfig | bool, Field()] = CdkNagScannerConfig() + cfnnag: Annotated[CfnNagScannerConfig | bool, Field()] = CfnNagScannerConfig() + checkov: Annotated[CheckovScannerConfig | bool, Field()] = CheckovScannerConfig() + gitsecrets: Annotated[GitSecretsScannerConfig | bool, Field()] = ( + GitSecretsScannerConfig() + ) + grype: Annotated[GrypeScannerConfig | bool, Field()] = GrypeScannerConfig() + npmaudit: Annotated[NpmAuditScannerConfig | bool, Field()] = NpmAuditScannerConfig() + semgrep: Annotated[SemgrepScannerConfig | bool, Field()] = SemgrepScannerConfig() + + +class SBOMScannerListConfig(ScannerListConfig): + syft: Annotated[SyftScannerConfig | bool, Field()] = SyftScannerConfig() + + +class SASTScannerConfig(ScannerClassConfig): + """Configuration model for SAST scanners.""" + + # Additional optional settings + output_formats: Annotated[ + List[ExportFormat], + Field(description="Format for SAST scan results output"), + ] = [ + ExportFormat.TEXT, + ExportFormat.JSON, + ExportFormat.JUNITXML, + ExportFormat.HTML, + ] + + scanners: Annotated[ + SASTScannerListConfig, + Field( + description="SAST scanners to enable and their corresponding configurations." + ), + ] = SASTScannerListConfig() + + +class SBOMScannerConfig(ScannerClassConfig): + """Configuration model for SBOM scanners.""" + + # Additional optional settings + output_formats: Annotated[ + List[ + Literal[ + ExportFormat.TEXT, + ExportFormat.YAML, + ExportFormat.JSON, + ExportFormat.JUNITXML, + ExportFormat.HTML, + ExportFormat.CYCLONEDX, + ExportFormat.SPDX, + ] + ], + Field(description="Format for SBOM scan results output"), + ] = [ + ExportFormat.JSON, + ExportFormat.HTML, + ExportFormat.CYCLONEDX, + ] + + scanners: Annotated[ + SBOMScannerListConfig, + Field( + description="SBOM scanners to enable and their corresponding configurations." + ), + ] = SBOMScannerListConfig() + + +class ASHConfig(BaseModel): + """Main configuration model for Automated Security Helper.""" + + model_config = ConfigDict( + str_strip_whitespace=True, + arbitrary_types_allowed=True, + extra="allow", + ) + + # Project information + project_name: Annotated[str, Field(description="Name of the project being scanned")] + + # Build configuration + build: Annotated[ + BuildConfig, + Field(description="Build-time configuration settings"), + ] = BuildConfig() + + # Scanner type configurations + sast: Annotated[ + SASTScannerConfig, + Field(description="SAST scanner configuration"), + ] = SASTScannerConfig() + + sbom: Annotated[ + SBOMScannerConfig, + Field(description="SBOM scanner configuration"), + ] = SBOMScannerConfig() + + # General scan settings + fail_on_findings: Annotated[ + bool, + Field( + description="Whether to exit with non-zero code if findings are detected" + ), + ] = True + + ignore_paths: Annotated[ + List[str], + Field(description="List of paths to ignore during scanning"), + ] = [] + + output_dir: Annotated[ + str, + Field(description="Directory to store scan outputs"), + ] = "ash_output" + + scan_paths: Annotated[List[str], Field(description="List of paths to scan")] = ["."] + + severity_threshold: Annotated[ + str, Field(description="Minimum severity level to report") + ] = "LOW" + + max_concurrent_scanners: Annotated[ + int, Field(description="Maximum number of scanners to run concurrently", ge=1) + ] = 4 diff --git a/src/automated_security_helper/config/default_config.py b/src/automated_security_helper/config/default_config.py new file mode 100644 index 0000000..cb09977 --- /dev/null +++ b/src/automated_security_helper/config/default_config.py @@ -0,0 +1,13 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +from automated_security_helper.config.config import ( + ASHConfig, +) + + +DEFAULT_ASH_CONFIG = ASHConfig( + project_name="automated-security-helper", + output_dir="ash_output", + fail_on_findings=True, + ignore_paths=["tests/**"], +) diff --git a/src/automated_security_helper/config/scanner_types.py b/src/automated_security_helper/config/scanner_types.py new file mode 100644 index 0000000..5bd5312 --- /dev/null +++ b/src/automated_security_helper/config/scanner_types.py @@ -0,0 +1,210 @@ +from typing import ( + Annotated, + Literal, + TypeVar, +) +from pydantic import BaseModel, ConfigDict, Field + +from automated_security_helper.models.core import SCANNER_TYPES + +T = TypeVar("T", bound="ScannerBaseConfig") + + +class BaseScannerOptions(BaseModel): + """Base class for scanner options.""" + + model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) + + +class BasePluginOptions(BaseModel): + """Base class for plugin options.""" + + enabled: bool = True + + +class ScannerBaseConfig(BaseModel): + """Base configuration model with common settings.""" + + model_config = ConfigDict( + str_strip_whitespace=True, + arbitrary_types_allowed=True, + extra="allow", + ) + + name: Annotated[ + str, + Field( + min_length=1, + description="Name of the component using letters, numbers, underscores and hyphens. Must begin with a letter.", + pattern=r"^[a-zA-Z][\w-]+$", + ), + ] = None + enabled: Annotated[bool, Field(description="Whether the component is enabled")] = ( + True + ) + type: Annotated[ + SCANNER_TYPES, + Field(description=f"Type of scanner. Valid options include: {SCANNER_TYPES}"), + ] = "UNKNOWN" + options: Annotated[BaseScannerOptions, Field(description="Scanner options")] = ( + BaseScannerOptions() + ) + + +class CustomScannerConfig(ScannerBaseConfig): + model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) + + name: Annotated[ + str, + Field( + ..., + description="The name of the custom scanner.", + ), + ] + type: SCANNER_TYPES = "CUSTOM" + options: Annotated[ + BaseScannerOptions, Field(description="Configure custom scanner") + ] = BaseScannerOptions() + + +class BanditScannerConfig(ScannerBaseConfig): + """Bandit SAST scanner configuration.""" + + name: Literal["bandit"] = "bandit" + type: SCANNER_TYPES = "SAST" + options: Annotated[ + BaseScannerOptions, Field(description="Configure Bandit scanner") + ] = BaseScannerOptions() + + +class JupyterNotebookScannerConfig(ScannerBaseConfig): + """Jupyter Notebook (.ipynb) SAST scanner configuration.""" + + name: Literal["jupyter"] = "jupyter" + type: SCANNER_TYPES = "SAST" + options: Annotated[ + BaseScannerOptions, Field(description="Configure Jupyter Notebook scanner") + ] = BaseScannerOptions() + + +class CdkNagPacks(BaseModel): + model_config = ConfigDict(extra="allow") + + AwsSolutionsChecks: Annotated[ + bool, + Field(description="Runs the AwsSolutionsChecks NagPack included with CDK Nag."), + ] = True + HIPAASecurityChecks: Annotated[ + bool, + Field( + description="Runs the HIPAASecurityChecks NagPack included with CDK Nag." + ), + ] = False + NIST80053R4Checks: Annotated[ + bool, + Field(description="Runs the NIST80053R4Checks NagPack included with CDK Nag."), + ] = False + NIST80053R5Checks: Annotated[ + bool, + Field(description="Runs the NIST80053R5Checks NagPack included with CDK Nag."), + ] = False + PCIDSS321Checks: Annotated[ + bool, + Field(description="Runs the PCIDSS321Checks NagPack included with CDK Nag."), + ] = False + + +class CdkNagScannerConfigOptions(BaseScannerOptions): + """CDK Nag IAC SAST scanner options.""" + + nag_packs: Annotated[ + CdkNagPacks, + Field( + description="CDK Nag packs to enable", + ), + ] = CdkNagPacks() + + +class CdkNagScannerConfig(ScannerBaseConfig): + """CDK Nag IAC SAST scanner configuration.""" + + name: Literal["cdknag"] = "cdknag" + type: SCANNER_TYPES = "IAC" + options: Annotated[ + CdkNagScannerConfigOptions, Field(description="Configure CDK Nag scanner") + ] = CdkNagScannerConfigOptions() + + +class CfnNagScannerConfig(ScannerBaseConfig): + """CFN Nag IAC SAST scanner configuration.""" + + name: Literal["cfnnag"] = "cfnnag" + type: SCANNER_TYPES = "IAC" + options: Annotated[ + BaseScannerOptions, + Field(description="Enable CFN Nag IAC scanner"), + ] = BaseScannerOptions() + + +class NpmAuditScannerConfig(ScannerBaseConfig): + """JS/TS Dependency scanner configuration.""" + + name: Literal["npmaudit"] = "npmaudit" + type: SCANNER_TYPES = "DEPENDENCY" + options: Annotated[ + BaseScannerOptions, + Field( + description="Enable NPM/PNPM/Yarn Audit dependency scanner", + ), + ] = BaseScannerOptions() + + +class GitSecretsScannerConfig(ScannerBaseConfig): + """Git Secrets scanner configuration.""" + + name: Literal["gitsecrets"] = "gitsecrets" + type: SCANNER_TYPES = "SECRETS" + options: Annotated[ + BaseScannerOptions, + Field(description="Enable Git Secrets scanner"), + ] = BaseScannerOptions() + + +class SemgrepScannerConfig(ScannerBaseConfig): + """Semgrep SAST scanner configuration.""" + + name: Literal["semgrep"] = "semgrep" + type: SCANNER_TYPES = "SAST" + options: Annotated[ + BaseScannerOptions, Field(description="Configure Semgrep scanner") + ] = BaseScannerOptions() + + +class CheckovScannerConfig(ScannerBaseConfig): + """Checkov SAST/IaC scanner configuration.""" + + name: Literal["checkov"] = "checkov" + type: SCANNER_TYPES = "IAC" + options: Annotated[ + BaseScannerOptions, Field(description="Configure Checkov scanner") + ] = BaseScannerOptions() + + +class GrypeScannerConfig(ScannerBaseConfig): + """Grype SAST scanner configuration.""" + + name: Literal["grype"] = "grype" + type: SCANNER_TYPES = "SAST" + options: Annotated[ + BaseScannerOptions, Field(description="Configure Grype scanner") + ] = BaseScannerOptions() + + +class SyftScannerConfig(ScannerBaseConfig): + """Syft SBOM scanner configuration.""" + + name: Literal["syft"] = "syft" + type: SCANNER_TYPES = "SBOM" + options: Annotated[ + BaseScannerOptions, Field(description="Configure Syft scanner") + ] = BaseScannerOptions() diff --git a/src/automated_security_helper/exceptions.py b/src/automated_security_helper/exceptions.py new file mode 100644 index 0000000..caa81bc --- /dev/null +++ b/src/automated_security_helper/exceptions.py @@ -0,0 +1,4 @@ +class ScannerError(Exception): + """Exception raised when scanner execution fails.""" + + pass diff --git a/src/automated_security_helper/execution_engine.py b/src/automated_security_helper/execution_engine.py new file mode 100644 index 0000000..1472dd9 --- /dev/null +++ b/src/automated_security_helper/execution_engine.py @@ -0,0 +1,1112 @@ +"""Execution engine for security scanners.""" + +import json +import logging +import multiprocessing +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime +from enum import Enum +from pathlib import Path +from typing import Callable, Dict, List, Optional, Type, Any, Union + +from automated_security_helper.models.scan_results import ScanResultsContainer +from automated_security_helper.config.config import ( + ASHConfig, + SASTScannerConfig, + SASTScannerListConfig, + SBOMScannerConfig, + SBOMScannerListConfig, +) +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.json_serializer import ASHARPModelSerializer +from automated_security_helper.scanner_factory import ScannerFactory +from automated_security_helper.models.scanner_plugin import ScannerPlugin +from automated_security_helper.config.scanner_types import ( + ScannerBaseConfig, +) +from automated_security_helper.utils.log import ASH_LOGGER as logger + + +class ExecutionStrategy(Enum): + """Strategy for executing scanners.""" + + SEQUENTIAL = "sequential" + PARALLEL = "parallel" + + +class ScanProgress: + """Tracks progress of security scans.""" + + def __init__(self, total: int): + """Initialize scan progress tracker.""" + self.completed = 0 + self.total = total + + def increment(self): + """Increment the completed count.""" + self.completed += 1 + + +class ScanExecutionEngine: + """Manages the execution of security scanners.""" + + def __init__( + self, + source_dir: Path = None, + output_dir: Path = None, + # enabled_scanners is the list of scanner names that were explicitly passed + # in from the Orchestrator. This allows ASH users to specify the scanners via CLI + # at runtime for a more focused scan (e.g. during finding remediation where + # there is only a single scanner failing, isolating scans to just that scanner will allow quicker retesting until passing and a full scan can be ) + enabled_scanners: Optional[List[str]] = None, + strategy: ExecutionStrategy = ExecutionStrategy.PARALLEL, + config: ASHConfig = None, + logger: logging.Logger = logging.Logger(name=__name__), + ): + """Initialize the execution engine. + + Args: + source_dir: Source directory to scan + output_dir: Output directory for scan results + enabled_scanners: List of scanner names to enable. If None, all scanners are enabled. + If empty list, no scanners are enabled. + strategy: Execution strategy to use for scanner execution (default: PARALLEL) + logger: Logger instance to use for logging + """ + # Set up logging and initial state + self.logger = logger + self._scanners = {} + self._scan_results = {} + self._enabled_scanners = enabled_scanners # None means all enabled + self.logger.info("Initializing ScanExecutionEngine") + + # Initialize basic configuration + self._config = None + self._scan_results = {} + self._strategy = strategy + self._initialized = False # Track initialization state + + # Normalize and store enabled scanner names + self._enabled_scanners = { + s.lower().strip() for s in (enabled_scanners or set()) + } + + # Convert and set up paths + try: + # Convert and validate source directory + self.source_dir = Path(source_dir) if source_dir else None + + # Convert and validate output directory + if output_dir: + self.output_dir = Path(output_dir) + self.work_dir = self.output_dir / "work" + self.work_dir.mkdir(parents=True, exist_ok=True) + else: + self.output_dir = None + self.work_dir = None + + # Log directory setup + self.logger.debug(f"Source directory: {self.source_dir}") + self.logger.debug(f"Output directory: {self.output_dir}") + self.logger.debug(f"Work directory: {self.work_dir}") + + except Exception as e: + self.logger.error(f"Failed to setup directories: {e}") + raise + + # Initialize base components + self._completed_scanners = [] + self._results = {"scanners": {}} + self._progress = None + self._max_workers = min(4, multiprocessing.cpu_count()) + + # Initialize scanner components + self._scanner_factory = ScannerFactory() + self._scanners = {} # Maps scanner names to factory functions + self._initialized = False # Track initialization state + + # Initialize all scanners + self._register_default_scanners() + + # Enable all scanners by default if no specific ones requested + if not self._enabled_scanners: + self._enabled_scanners = [ + k.lower().strip() for k in self._scanner_factory._scanners.keys() + ] + + # Log registration status + # Log scanner registration status + enabled = ", ".join(sorted(set(self._enabled_scanners))) + registered = ", ".join(sorted(k for k in self._scanners.keys() if k.strip())) + self.logger.info(f"Enabled scanners: {enabled}") + self.logger.info(f"Registered scanners: {registered}") + + # Mark initialization as complete + self._initialized = True + + def _register_default_scanners(self) -> None: + """Register all default scanners from the factory and enable them. + + This method: + 1. Initializes enabled_scanners set if needed + 2. Gets all available scanners from factory + 3. Registers each scanner with force=True to enable it + 4. Logs registration status for debugging + + By default, all registered scanners are enabled unless explicitly disabled via config. + """ + self.logger.debug("Registering default scanners from factory") + self.logger.info("Registering default scanners") + + # Initialize enabled scanners set if needed + if self._enabled_scanners is None: + self._enabled_scanners = set() + self.logger.debug("Initialized enabled scanners set") + + # Initialize scanners dict if needed + if not self._scanners: + self._scanners = {} + + # First register all scanners in factory + self._scanner_factory._register_default_scanners() + + # Normalize enabled scanner names if provided + if self._enabled_scanners: + enabled = set() + for name in self._enabled_scanners: + normalized = name.lower().strip() + if "scanner" in normalized.lower(): + normalized = normalized.split("scanner")[0].strip() + enabled.add(normalized) + self._enabled_scanners = enabled + + # Get initial set of scanners from factory + registered = set() + all_scanners = {} + + # Ensure factory has latest scanners registered + self._scanner_factory._register_default_scanners() + + # Collect scanners from factory first + for name, scanner in self._scanner_factory._scanners.items(): + normalized = name.lower().strip() + all_scanners[normalized] = scanner + # Also add base name if it's a scanner suffix + if normalized.endswith("scanner"): + base = normalized[:-7].strip() + if base and base not in all_scanners: + all_scanners[base] = scanner + + # Add any missing default scanners + for scanner_class in self._scanner_factory.default_scanners: + if not hasattr(scanner_class, "_default_config"): + self.logger.warning( + f"Scanner {scanner_class.__name__} missing _default_config" + ) + continue + + config = scanner_class._default_config + if not hasattr(config, "name") or not config.name: + self.logger.warning( + f"Scanner {scanner_class.__name__} missing name in config" + ) + continue + + scanner_name = config.name.lower().strip() + if not scanner_name: + continue + + if scanner_name not in all_scanners: + all_scanners[scanner_name] = scanner_class + + # Also add base name variant + if scanner_name.endswith("scanner"): + base_name = scanner_name[:-7].strip() + if base_name and base_name not in all_scanners: + all_scanners[base_name] = scanner_class + + # Register all scanners - filtering happens at usage time + for name, scanner_class in all_scanners.items(): + try: + # Force registration since this is initial setup + self._scanners[name] = scanner_class + registered.add(name) + + # Register base name variant + if name.endswith("scanner"): + base = name[:-7].strip() + if base and base not in registered: + # Also force register base name variant + self._scanners[base] = scanner_class + registered.add(base) + except Exception as e: + self.logger.warning(f"Failed to register {name}: {str(e)}") + + # Log the registered scanners + registered_names = ", ".join(sorted(registered)) + self.logger.info(f"Registered scanners: {registered_names}") + + # Log placeholders separately + placeholders = [ + name + for name, factory in self._scanners.items() + if factory is None or not callable(factory) + ] + if placeholders: + placeholder_names = ", ".join(sorted(placeholders)) + self.logger.info( + f"Placeholder scanners (not implemented): {placeholder_names}" + ) + + def register_scanner( + self, + name: str, + scanner_factory: Optional[ + Union[Type[ScannerPlugin], Callable[..., ScannerPlugin]] + ], + force: bool = True, # Default to force=True to simplify registration + ) -> None: + """Register a scanner with the execution engine. + + Args: + name: Scanner name (will be normalized) + scanner_factory: Scanner class or factory function for creating scanner instances, or None for placeholder registration + force: If True, register scanner even if not in enabled list + """ + # Normalize scanner name for consistent lookup + scanner_name = name.lower().strip() + + self.logger.debug(f"Attempting to register scanner: {scanner_name}") + + # If already registered and force=True, just ensure it's enabled + if scanner_name in self._scanners: + self.logger.debug(f"Scanner {scanner_name} already registered") + if force: + self._enabled_scanners = self._enabled_scanners or set() + self._enabled_scanners.add(scanner_name) + if scanner_name.endswith("scanner"): + base_name = scanner_name[:-7].strip() + if base_name: + self._enabled_scanners.add(base_name) + self.logger.debug(f"Force enabled existing scanner: {scanner_name}") + return + + # Default behavior is to enable scanners: + # 1. On initial registration + # 2. When forced enabled + # 3. When no explicit enabled list exists + # This ensures default scanners and explicitly registered ones work + if self._enabled_scanners is None: + self._enabled_scanners = set() + + # Add to enabled list unless explicitly disabled via config + if force or not self._enabled_scanners: + self._enabled_scanners.add(scanner_name) + if scanner_name.endswith("scanner"): + base_name = scanner_name[:-7].strip() + if base_name: + self._enabled_scanners.add(base_name) + self.logger.debug(f"Enabled scanner: {scanner_name}") + + self.logger.debug(f"Registering scanner: {scanner_name}") + + # Handle scanner registration, enabling by default unless explicitly disabled + if scanner_factory is None: + # For placeholder registration + if scanner_name not in self._scanners: + # Register empty placeholder + self._scanners[scanner_name] = None + self.logger.debug(f"Registered placeholder scanner: {scanner_name}") + else: + # Register actual scanner implementation + if scanner_name not in self._scanners: + # Create factory function to handle scanner instance creation + self.logger.debug( + f"Creating factory function for scanner: {scanner_name}" + ) + # Always register actual implementations + self._scanners[name] = scanner_factory + + # Process class-based scanner factory + if isinstance(scanner_factory, type): + # Allow None factory for placeholder registration + if scanner_factory is None: + self._scanners[name] = None + return + + scanner_class = scanner_factory + + def factory_fn(source_dir=None, output_dir=None, logger=None): + if scanner_factory is None: + raise NotImplementedError( + f"Scanner {name} is registered but not implemented" + ) + if source_dir is not None and not isinstance(source_dir, Path): + source_dir = Path(source_dir) + if output_dir is not None and not isinstance(output_dir, Path): + output_dir = Path(output_dir) + # Ensure paths are Path objects + if isinstance(source_dir, str): + source_dir = Path(source_dir) + if isinstance(output_dir, str): + output_dir = Path(output_dir) + # Use engine paths if none provided + source_dir = source_dir if source_dir is not None else self.source_dir + output_dir = output_dir if output_dir is not None else self.output_dir + if source_dir is None or output_dir is None: + raise ValueError("source_dir and output_dir are required") + + # Always force paths to be Path objects + if isinstance(source_dir, str): + source_dir = Path(source_dir) + if isinstance(output_dir, str): + output_dir = Path(output_dir) + + # Ensure we always have minimum required paths + if not source_dir: + source_dir = self.source_dir or Path(".") + if not output_dir: + output_dir = self.output_dir or Path("output") + + # Always use Path objects + if isinstance(source_dir, str): + source_dir = Path(source_dir) + if isinstance(output_dir, str): + output_dir = Path(output_dir) + + try: + scanner = scanner_class( + source_dir=source_dir or Path("."), + output_dir=output_dir or Path("output"), + logger=logger or self.logger, + ) + return scanner + except Exception as e: + self.logger.error(f"Failed to create scanner: {str(e)}") + raise + + else: + factory_fn = scanner_factory + scanner_instance = factory_fn() + scanner_class = type(scanner_instance) + + # Initialize enabled scanners list if not already done + if not hasattr(self, "_enabled_scanners"): + self._enabled_scanners = [] + + # Enable scanner if forced or no enabled list exists + if force or not self._enabled_scanners: + if scanner_name not in self._enabled_scanners: + self._enabled_scanners.append(scanner_name) + + # Register scanner with both factory and store + try: + # Always update scanner registration to ensure latest version + self._scanners[scanner_name] = factory_fn + self._scanner_factory.register_scanner(scanner_name, scanner_class) + except Exception as e: + self.logger.warning(f"Failed to register scanner {scanner_name}: {str(e)}") + raise + + def get_scanner( + self, scanner_name: str, check_enabled: bool = True + ) -> ScannerPlugin: + """Get a scanner instance by name. + + Attempts to find and instantiate a scanner in the following order: + 1. First validates if scanner is enabled if check_enabled=True + 2. Looks up scanner in registered scanners (including placeholders) + 3. Tries base name without 'scanner' suffix + 4. Attempts to get implementation from scanner factory + 5. Auto-registers from factory if found + + Args: + scanner_name: Name of the scanner to retrieve + check_enabled: If True, validate against enabled scanners list first + + Returns: + Scanner instance configured with current engine paths + + Raises: + ValueError: If scanner is not found or not enabled + NotImplementedError: If scanner is registered as placeholder but not implemented + """ + # Normalize scanner name and initialize if needed + lookup_name = scanner_name.lower().strip() + if not lookup_name: + raise ValueError("Scanner name cannot be empty") + + self.logger.debug( + f"Looking up scanner: {lookup_name} (check_enabled={check_enabled})" + ) + + # Auto-initialize and register default scanners if needed + if not self._initialized: + self.ensure_initialized() + + # First verify scanner exists + if lookup_name not in self._scanners: + self.logger.warning( + f"Scanner {lookup_name} not found in registered scanners" + ) + raise ValueError(f"Scanner {lookup_name} not registered") + + # Then check if scanner is enabled + if check_enabled and self._enabled_scanners is not None: + base_name = ( + lookup_name[:-7].strip() if lookup_name.endswith("scanner") else "" + ) + names_to_check = {lookup_name} + if base_name: + names_to_check.add(base_name) + if not any(name in self._enabled_scanners for name in names_to_check): + self.logger.warning( + f"Scanner {lookup_name} not enabled (enabled scanners: {self._enabled_scanners})" + ) + raise ValueError(f"Scanner {lookup_name} not enabled") + + # Scanner is considered enabled if: + # 1. Empty enabled list and scanner is registered + # 2. Non-empty enabled list and scanner or its base name is in it + if len(self._enabled_scanners) == 0: + enabled = lookup_name in self._scanners or ( + base_name and base_name in self._scanners + ) + else: + enabled = lookup_name in self._enabled_scanners or ( + base_name and base_name in self._enabled_scanners + ) + + if not enabled: + msg = ( + "No scanners enabled" + if len(self._enabled_scanners) == 0 + else f"Scanner {scanner_name} not enabled" + ) + self.logger.warning(msg) + raise ValueError(msg) + + # Prepare name variations to try + names_to_try = [lookup_name] + + # Add base name without 'scanner' suffix to try + if lookup_name.endswith("scanner"): + base_name = lookup_name[:-7].strip() + if base_name: + names_to_try.append(base_name) + + # Try each possible name + for name in names_to_try: + # Check registered scanners + if name in self._scanners: + scanner_fn = self._scanners[name] + try: + scanner = scanner_fn( + source_dir=self.source_dir, + output_dir=self.output_dir, + logger=self.logger, + ) + return scanner + except (TypeError, ValueError): + # If that fails, try without paths + return scanner_fn() + + # Fall back to factory if not already registered + for fallback_name in names_to_try: + if fallback_name in self._scanner_factory._scanners: + scanner_class = self._scanner_factory._scanners[fallback_name] + try: + self.register_scanner(fallback_name, scanner_class) + scanner_fn = self._scanners[fallback_name] + return scanner_fn( + source_dir=self.source_dir, + output_dir=self.output_dir, + logger=self.logger, + ) + except Exception as e: + self.logger.warning( + f"Failed to register scanner {fallback_name} from factory: {str(e)}" + ) + continue + + raise ValueError(f"Scanner '{scanner_name}' not registered") + + def ensure_initialized(self, config: Optional[ASHConfig] = None) -> None: + """Ensure scanner factory and scanners are properly initialized. + + This method: + 1. Registers and enables all default scanners from factory + 2. Processes config if provided to override scanner settings + 3. Maintains all scanners enabled by default if no explicit config + + Args: + config: ASH configuration object for initialization + """ + if not self._initialized: + self.logger.info("Initializing execution engine") + + # Store config and set initial scanner state + self._config = config + self._enabled_scanners = None # Default to all enabled + + # Process scanners in order: + # 1. Register factory defaults (with force=True) + # 2. Process config overrides if present + self._register_default_scanners() # This registers with force=True + + # Process config if provided to override default scanner settings + if config and hasattr(config, "sast") and hasattr(config.sast, "scanners"): + # Switch to explicit enable list and process scanner configs + self._enabled_scanners = set() + for scanner in vars(config.sast.scanners).values(): + if not hasattr(scanner, "name"): + continue + + name = scanner.name.lower().strip() + enabled = getattr(scanner, "enabled", False) + self.logger.debug( + f"Processing scanner config: {name} (enabled={enabled})" + ) + + # Ensure scanner is registered + if name not in self._scanners and self._scanner_factory: + try: + scanner_class = self._scanner_factory.get_scanner_class( + name + ) + if scanner_class: + self.register_scanner(name, scanner_class, force=True) + except Exception as e: + self.logger.warning( + f"Failed to get scanner class for {name}: {e}" + ) + + # Enable scanner if configured + if enabled: + self._enabled_scanners.add(name) + # Also enable base name without 'scanner' suffix + if name.endswith("scanner"): + base = name[:-7].strip() + if base: + self._enabled_scanners.add(base) + + # If no explicit configuration, enable all registered scanners + if not self._enabled_scanners: + self.logger.debug( + "No explicit scanner configuration - enabling all registered scanners" + ) + self._enabled_scanners = set() + for scanner in vars(config.sast.scanners).values(): + if hasattr(scanner, "name"): + name = scanner.name.lower().strip() + + # Register scanner (as placeholder if not implemented) + self.register_scanner(name, None, force=True) + + # Also handle base name variant + base_name = ( + name[:-7].strip() if name.endswith("scanner") else "" + ) + if base_name: + self.register_scanner(base_name, None, force=True) + + # Track enabled scanners + if getattr(scanner, "enabled", False): + self._enabled_scanners.add(name) + if base_name: + self._enabled_scanners.add(base_name) + + # If no scanners were explicitly enabled, enable all registered ones + if self._enabled_scanners is None: + self._enabled_scanners = list(sorted(self._scanners.keys())) + + # Mark initialization complete + self._initialized = True + + def execute(self, config: Optional[ASHConfig] = None, **kwargs) -> Dict[str, Any]: + """Execute registered scanners based on provided configuration. + + Args: + config: ASHConfig instance containing scanner configuration in sast/sbom sections + **kwargs: Additional execution parameters. Supports: + - mode: ExecutionStrategy value to override execution mode + + Returns: + Dict[str, Any]: Results dictionary with scanner results and ASHARPModel + + Raises: + ValueError: If config is invalid or mode is invalid + RuntimeError: If scanner execution fails critically + """ + self.logger.debug("Entering: ScanExecutionEngine.execute()") + if config: + self._config = config + if not self._config: + self._config = ASHConfig( + project_name="", + sast=SASTScannerConfig(scanners=SASTScannerListConfig()), + sbom=SBOMScannerConfig(scanners=SBOMScannerListConfig()), + ) + if not isinstance(self._config, ASHConfig): + raise ValueError("Configuration must be an ASHConfig instance") + + # Reset state for new execution + self._completed_scanners = [] + self._queue = multiprocessing.Queue() + self._scan_results = {} + + # Create ASHARPModel for this execution + ash_model = ASHARPModel( + name=f"ASH Scan {datetime.now().isoformat()}", + description="Aggregated security scan results", + ) + + # Execute scanners based on mode + enabled_scanners = set() + try: + # Build queue of scanner tuples for execution + self._queue = multiprocessing.Queue() + + # Get all enabled scanners from SAST and SBOM configurations using helper + scanner_configs = self._get_all_scanners() + + # Process SAST and SBOM scanners + if scanner_configs: + for scanner_name, scanner_config in scanner_configs.items(): + scanner = self.get_scanner(scanner_name) + if scanner_config: + scanner.configure(scanner_config) + # Add scanner to execution queue with default target + self._queue.put((scanner_name, ".")) + enabled_scanners.add(scanner_name) + + # Initialize progress tracker for execution + self._progress = ScanProgress(len(enabled_scanners)) + + # Execute scanners based on mode + if self._mode == ExecutionStrategy.PARALLEL: + self._execute_parallel(ash_model) + else: + self._execute_sequential(ash_model) + + # Save ASHARPModel as JSON alongside results if output_dir is configured + output_dir = getattr(self._config, "output_dir", None) + if output_dir: + output_path = Path(output_dir) + ASHARPModelSerializer.save_model(ash_model, output_path) + + # Save aggregated results if not present + results_file = output_path / "ash_aggregated_results.txt" + if not results_file.exists(): + with open(results_file, "w") as f: + json.dump(self._scan_results, f, indent=2, default=str) + + self._scan_results["asharp_model"] = ash_model + return self._scan_results + + except Exception as e: + self.logger.error(f"Execution failed: {str(e)}") + raise + + # # Process registered scanners first + # self.logger.debug("Processing registered scanners") + # for scanner_name, scanner_class in registered_scanners.items(): + # try: + # self.logger.debug(f"Processing registered scanner: {scanner_name}") + # # Get scanner config from class or instance + # if hasattr(scanner_class, "_default_config"): + # # Use class default config + # config = scanner_class._default_config + # else: + # # Create instance to get config + # scanner = scanner_class() + # config = scanner._config or ScannerPluginConfig( + # name=scanner_name, + # type=scanner_name, + # command="", + # invocation_mode="directory", + # enabled=True, + # ) + + # # Ensure name matches registration + # config_dict = config.model_dump() + # config_dict["name"] = scanner_name + # config_dict["enabled"] = True + + # enabled_scanners.append((scanner_name, config_dict)) + + # except Exception as e: + # self.logger.debug( + # f"Failed to get config for scanner {scanner_name}: {str(e)}" + # ) + + # Process scanner configs from ASH config if provided + # if config and hasattr(config, "sast") and config.sast and config.sast.scanners: + # scanner_configs = config.sast.scanners + # if not isinstance(scanner_configs, list): + # scanner_configs = [scanner_configs] + + # # Clear any default configs + # enabled_scanners = [] + + # for scanner_config in scanner_configs: + # if not getattr(scanner_config, "enabled", True): + # continue + + # # Get scanner type from config class name + # scanner_type = scanner_config.type + # scanner_name = scanner_config.name + + # if "scanner" in scanner_type: + # scanner_type = scanner_type.split("scanner")[0] + + # # Try to find scanner in factory + # if scanner_name in self._scanner_factory._scanners: + # # Convert scanner config to dict + # if hasattr(scanner_config, "model_dump"): + # config_dict = scanner_config.model_dump() + # else: + # config_dict = { + # k: v + # for k, v in vars(scanner_config).items() + # if not k.startswith("_") + # } + + # config_dict["type"] = scanner_type + # enabled_scanners.append((scanner_name, config_dict)) + # else: + # # Try normalized name as fallback + # normalized_type = ( + # scanner_type.replace("scanner", "").lower().strip() + # ) + # if normalized_type in self._scanner_factory._scanners: + # if hasattr(scanner_config, "model_dump"): + # config_dict = scanner_config.model_dump() + # else: + # config_dict = { + # k: v + # for k, v in vars(scanner_config).items() + # if not k.startswith("_") + # } + # config_dict["type"] = normalized_type + # enabled_scanners.append((normalized_type, config_dict)) + # else: + # self.logger.debug( + # f"Scanner type '{scanner_type}' not registered. Available scanners: {list(self._scanner_factory.available_scanners())}" + # ) + + # Skip execution mode update here since it's handled at the start + + # # Setup progress tracking + # total = len(enabled_scanners) + # self._progress = ScanProgress(total=total) + # self._completed_scanners = [] + + # # Reset results and track which scanners have been processed + # results = {"scanners": {}} + # processed_scanners = set() + + # # Execute enabled scanners from config first + # if enabled_scanners: + # for scanner_type, scanner_config in enabled_scanners: + # # Skip if already processed this type + # if scanner_type in processed_scanners: + # continue + + # processed_scanners.add(scanner_type) + # try: + # # Validate scanner is registered + # if scanner_type not in self._scanner_factory._scanners: + # self.logger.debug( + # f"Scanner {scanner_type} not registered, skipping" + # ) + # continue + + # # Create new scanner instance + # scanner_class = self._scanner_factory._scanners[scanner_type] + # scanner = scanner_class( + # source_dir=self.source_dir, + # output_dir=self.output_dir, + # logger=self.logger, + # ) + + # try: + # # Configure scanner + # config_obj = ( + # scanner_config + # if isinstance(scanner_config, ScannerPluginConfig) + # else ScannerPluginConfig(**scanner_config) + # ) + # scanner.configure(config_obj) + + # # Execute scan against source directory + # self.logger.info( + # f"Executing {scanner_type} against directory: {self.source_dir.as_posix()}" + # ) + # result = scanner.scan(self.source_dir.as_posix()) + + # # Use scanner name from config for results + # scanner_name = ( + # config_obj.name + # if hasattr(config_obj, "name") + # else scanner_type + # ) + # # Store results + # if result: + # results["scanners"][scanner_name] = result + # self.logger.info( + # f"Scanner {scanner_name} completed successfully" + # ) + + # except Exception as e: + # self.logger.error(f"Scanner config/execution error: {str(e)}") + # results["scanners"][scanner_name] = { + # "status": "error", + # "error": str(e), + # } + + # # Track progress + # self._progress.increment() + # self._completed_scanners.append(scanner) + # except Exception as e: + # self.logger.error( + # f"Failed to execute {scanner_type} scanner: {str(e)}" + # ) + # continue + # # Return results without using any default scanner configs + # return results + + def _execute_scanner(self, scanner_tuple: tuple) -> Dict[str, Dict[str, Any]]: + """Execute a single scanner and process its results. + + Args: + scanner_tuple: Tuple containing (scanner_name, target) + + Returns: + Dict[str, Dict[str, Any]]: Dictionary containing SecurityReport and raw results + + Raises: + ScannerError: If scanner execution fails + + + """ + scanner_plugin: ScannerPlugin = scanner_tuple[0] + scanner_config: ScannerBaseConfig = scanner_tuple[1] + try: + self.logger.debug(f"scanner_name: {scanner_plugin.__class__.__name__}") + self.logger.debug(f"scanner_config: {scanner_config}") + # Create scanner instance + self.logger.debug(f"Getting scanner: {scanner_plugin.__class__.__name__}") + + # Configure if needed + if scanner_config: + self.logger.debug( + f"Configuring scanner {scanner_plugin.__class__.__name__}: {scanner_config}" + ) + scanner_plugin.configure(scanner_config) + + # Execute scan + self.logger.debug(f"Executing {scanner_plugin.__class__.__name__}.scan()") + raw_results = scanner_plugin.scan() + + # Wrap results in container + container = ScanResultsContainer() + container.set_raw_results(raw_results) + + # Extract and add findings if present + if "findings" in raw_results: + container.add_findings(raw_results["findings"]) + + # Extract and add metadata if present + if "metadata" in raw_results: + for key, value in raw_results["metadata"].items(): + container.add_metadata(key, value) + + self.logger.debug("Executing engine.progress.increment()") + if self._progress: + self._progress.increment() + + self.logger.debug( + f"Appending {scanner_plugin.__class__.__name__} to engine.completed_scanners" + ) + self._completed_scanners.append(scanner_plugin) + + return {"container": container, "raw_results": raw_results} + + except Exception as e: + self.logger.error( + f"Failed to execute {scanner_plugin.__class__.__name__} scanner: {e}" + ) + raise + + def _execute_sequential(self, ash_model: ASHARPModel) -> None: + """Execute scanners sequentially and update ASHARPModel.""" + while not self._queue.empty(): + scanner_tuple = self._queue.get() + results = self._execute_scanner(scanner_tuple) + self._process_results(results, ash_model) + if self._progress: + self._progress.increment() + + def _execute_parallel(self, ash_model: ASHARPModel) -> None: + """Execute scanners in parallel and update ASHARPModel.""" + with ThreadPoolExecutor(max_workers=self._max_workers) as executor: + futures = [] + # Submit all scanners to the thread pool + while not self._queue.empty(): + scanner_tuple = self._queue.get() + future = executor.submit(self._execute_scanner, scanner_tuple) + futures.append(future) + + # Wait for all futures to complete and handle any exceptions + for future in as_completed(futures): + try: + results = future.result() + self._process_results(results, ash_model) + if self._progress: + self._progress.increment() + except Exception as e: + self.logger.error(f"Scanner execution failed: {str(e)}") + raise + + def _process_results( + self, results: Dict[str, Dict], ash_model: ASHARPModel + ) -> None: + """Process scanner results and update ASHARPModel. + + Args: + results: Dictionary containing scanner results + ash_model: ASHARPModel instance to update + """ + self._scan_results.update(results) + + # Update ASHARPModel with scanner results + for scanner_name, data in results.items(): + if "container" in data: + container = data["container"] + # Add findings from container to model + ash_model.findings.extend(container.findings) + # Add scanner metadata + ash_model.scanners_used.append( + { + "name": scanner_name, + "version": container.metadata.get("version", "unknown"), + "metadata": container.metadata, + } + ) + + @property + def completed_scanners(self) -> List[ScannerPlugin]: + """Get list of completed scanners.""" + return self._completed_scanners + + @property + def progress(self) -> Optional[ScanProgress]: + """Get current scan progress.""" + return self._progress + + def _get_all_scanners(self) -> Dict[str, Any]: + """Get all enabled scanners from SAST and SBOM configurations. + + Returns: + Dict[str, Any]: Dictionary of scanner name to scanner config mappings. + """ + all_scanners = {} + if not self._config: + return all_scanners + + # Get SAST scanners + if hasattr(self._config, "sast") and hasattr(self._config.sast, "scanners"): + for ( + scanner_name + ) in self._config.sast.scanners.__class__.model_fields.keys(): + config = getattr(self._config.sast.scanners, scanner_name) + if isinstance(config, ScannerBaseConfig): + logger.debug( + f"Found built-in SAST scanner '{scanner_name}' config: {config}" + ) + if config.enabled: + logger.debug( + f"Adding built-in SAST scanner '{scanner_name}' to all_scanners" + ) + all_scanners[scanner_name] = config + else: + logger.debug( + f"Skipping disabled built-in SAST scanner '{scanner_name}'" + ) + else: + logger.debug( + f"Item {scanner_name} does not appear to be a scanner, skipping" + ) + for ( + scanner_name, + config, + ) in self._config.sast.scanners.__pydantic_extra__.items(): + if isinstance(config, ScannerBaseConfig): + logger.debug( + f"Found custom SAST scanner '{scanner_name}' config: {config}" + ) + if config.enabled: + logger.debug( + f"Adding custom SAST scanner '{scanner_name}' to all_scanners" + ) + all_scanners[scanner_name] = config + else: + logger.debug( + f"Skipping disabled custom SAST scanner '{scanner_name}'" + ) + else: + logger.debug( + f"Item {scanner_name} does not appear to be a scanner, skipping" + ) + + # Get SBOM scanners + if hasattr(self._config, "sbom") and hasattr(self._config.sbom, "scanners"): + for ( + scanner_name + ) in self._config.sbom.scanners.__class__.model_fields.keys(): + config = getattr(self._config.sbom.scanners, scanner_name) + if isinstance(config, ScannerBaseConfig): + logger.debug( + f"Found built-in SBOM scanner '{scanner_name}' config: {config}" + ) + if config.enabled: + logger.debug( + f"Adding built-in SBOM scanner '{scanner_name}' to all_scanners" + ) + all_scanners[scanner_name] = config + else: + logger.debug( + f"Skipping disabled built-in SBOM scanner '{scanner_name}'" + ) + else: + logger.debug( + f"Item {scanner_name} does not appear to be a scanner, skipping" + ) + for ( + scanner_name, + config, + ) in self._config.sbom.scanners.__pydantic_extra__.items(): + if isinstance(config, ScannerBaseConfig): + logger.debug( + f"Found custom SBOM scanner '{scanner_name}' config: {config}" + ) + if config.enabled: + logger.debug( + f"Adding custom SBOM scanner '{scanner_name}' to all_scanners" + ) + all_scanners[scanner_name] = config + else: + logger.debug( + f"Skipping disabled custom SBOM scanner '{scanner_name}'" + ) + else: + logger.debug( + f"Item {scanner_name} does not appear to be a scanner, skipping" + ) + + return all_scanners + + def set_max_workers(self, workers: int) -> None: + """Set maximum number of worker threads for parallel execution.""" + self._max_workers = workers diff --git a/src/automated_security_helper/models/__init__.py b/src/automated_security_helper/models/__init__.py index 03f633a..001d2b7 100644 --- a/src/automated_security_helper/models/__init__.py +++ b/src/automated_security_helper/models/__init__.py @@ -1,2 +1,33 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 \ No newline at end of file +# SPDX-License-Identifier: Apache-2.0 + +"""Package models for Automated Security Helper (ASH).""" + +from automated_security_helper.models.core import BaseFinding, Scanner, Location +from automated_security_helper.models.data_interchange import ( + SecurityReport, + ReportMetadata, + ExportFormat, +) +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.security_vulnerability import ( + SecurityVulnerability, + SecurityVulnerabilityReport, +) +from automated_security_helper.models.iac_scan import IaCVulnerability, IaCScanReport +from automated_security_helper.models.sbom import SBOMPackage + +__all__ = [ + "BaseFinding", + "Scanner", + "Location", + "SecurityVulnerability", + "SecurityVulnerabilityReport", + "SecurityReport", + "ReportMetadata", + "ExportFormat", + "ASHARPModel", + "IaCVulnerability", + "IaCScanReport", + "SBOMPackage", +] diff --git a/src/automated_security_helper/models/aggregation.py b/src/automated_security_helper/models/aggregation.py new file mode 100644 index 0000000..1eec4b5 --- /dev/null +++ b/src/automated_security_helper/models/aggregation.py @@ -0,0 +1,151 @@ +"""Module containing aggregation functionality for security findings.""" + +from datetime import datetime +from typing import Dict, List + +from automated_security_helper.models.core import BaseFinding + + +class FindingAggregator: + """Aggregates and correlates findings from multiple scans.""" + + def __init__(self): + self.findings: List[BaseFinding] = [] + + def add_finding(self, finding: BaseFinding) -> None: + """Add a finding to be aggregated.""" + self.findings.append(finding) + + def deduplicate(self) -> List[BaseFinding]: + """Remove duplicate findings based on key attributes. + + Deduplication is based on matching: + - Location information + - Scanner and rule information + - Finding title and description + """ + unique_findings = {} + for finding in self.findings: + key = ( + finding.location.file_path, + finding.location.start_line, + finding.title, + finding.description, + ) + if key not in unique_findings: + unique_findings[key] = finding + return list(unique_findings.values()) + + def group_by_type(self) -> Dict[str, List[BaseFinding]]: + """Group findings by their scanner rule ID.""" + groups = {} + for finding in self.findings: + rule_id = finding.id + if rule_id not in groups: + groups[rule_id] = [] + groups[rule_id].append(finding) + return groups + + def group_by_severity(self) -> Dict[str, List[BaseFinding]]: + """Group findings by their severity level.""" + groups = {} + for finding in self.findings: + if finding.severity not in groups: + groups[finding.severity] = [] + groups[finding.severity].append(finding) + return groups + + +class TrendAnalyzer: + """Analyzes finding trends over time.""" + + def __init__(self): + self.scan_history: Dict[datetime, List[BaseFinding]] = {} + + def add_scan_findings( + self, scan_time: datetime, findings: List[BaseFinding] + ) -> None: + """Add findings from a scan at a specific time.""" + self.scan_history[scan_time] = findings + + def get_finding_counts_over_time(self) -> Dict[datetime, int]: + """Get the count of findings at each scan time.""" + return {time: len(findings) for time, findings in self.scan_history.items()} + + def get_severity_trends(self) -> Dict[str, Dict[datetime, int]]: + """Get finding counts by severity over time.""" + trends = {} + for scan_time, findings in self.scan_history.items(): + for finding in findings: + if finding.severity not in trends: + trends[finding.severity] = {} + if scan_time not in trends[finding.severity]: + trends[finding.severity][scan_time] = 0 + trends[finding.severity][scan_time] += 1 + return trends + + def get_new_findings( + self, previous_scan: datetime, current_scan: datetime + ) -> List[BaseFinding]: + """Get findings that appeared in current scan but not in previous scan.""" + if ( + previous_scan not in self.scan_history + or current_scan not in self.scan_history + ): + raise KeyError("Scan times not found in history") + + prev_findings = set( + ( + f.location.file_path, + f.location.start_line, + f.title, + f.description, + ) + for f in self.scan_history[previous_scan] + ) + current_findings = self.scan_history[current_scan] + + return [ + f + for f in current_findings + if ( + f.location.file_path, + f.location.start_line, + f.title, + f.description, + ) + not in prev_findings + ] + + def get_resolved_findings( + self, previous_scan: datetime, current_scan: datetime + ) -> List[BaseFinding]: + """Get findings that were in previous scan but not in current scan.""" + if ( + previous_scan not in self.scan_history + or current_scan not in self.scan_history + ): + raise KeyError("Scan times not found in history") + + current_findings = set( + ( + f.location.file_path, + f.location.start_line, + f.title, + f.description, + ) + for f in self.scan_history[current_scan] + ) + prev_findings = self.scan_history[previous_scan] + + return [ + f + for f in prev_findings + if ( + f.location.file_path, + f.location.start_line, + f.title, + f.description, + ) + not in current_findings + ] diff --git a/src/automated_security_helper/models/asharp_model.py b/src/automated_security_helper/models/asharp_model.py index 4074077..45b8509 100644 --- a/src/automated_security_helper/models/asharp_model.py +++ b/src/automated_security_helper/models/asharp_model.py @@ -1,18 +1,157 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -from pydantic import BaseModel, Field -from typing import Literal, Optional, List, Dict, Any, Union +from datetime import datetime, timezone +from pydantic import ConfigDict, Field +from automated_security_helper.models.data_interchange import ( + SecurityReport, + ReportMetadata, +) +from typing import Annotated, List, Dict, Any, Union, Optional +from automated_security_helper.models.core import BaseFinding, Scanner +__all__ = ["ASHARPModel"] -class ASHARPModel(BaseModel): - def to_json_schema( - self, - format: Literal["dict", "str"] = "dict", - *args, - **kwargs, - ) -> Dict[str, Any] | str: - if format == "dict": - return self.model_dump(*args, **kwargs) - return self.model_dump_json(*args, **kwargs) +from automated_security_helper.models.aggregation import ( + FindingAggregator, + TrendAnalyzer, +) + + +class ASHARPModel(SecurityReport): + """Main model class for parsing security scan reports from ASH tooling. + + This model is the primary interface for handling aggregated security findings from + various scanners. It supports both legacy scanner formats and the newer + scanners_used format. + + Example: + + ```python + with open('aggregated_results.json', 'r') as f: + report = ASHARPModel.from_json(f.read()) + print(f"Found {len(report.findings)} security findings") + for scanner in report.scanners_used: + print(f"Used scanner: {scanner['name']} v{scanner['version']}") + ``` + """ + + model_config = ConfigDict(str_strip_whitespace=True, arbitrary_types_allowed=True) + + name: str = Field(default="ASHARP Report", description="Name of the report") + description: str = Field( + default="AWS Security Hub Aggregated Report", + description="Description of the report", + ) + metadata: ReportMetadata = Field( + default_factory=lambda: ReportMetadata( + report_id="ASHARP-" + datetime.now(timezone.utc).strftime("%Y%M%d"), + project_name="ASHARP", + tool_name="ASHARP", + tool_version="1.0.0", + description="AWS Security Hub Aggregated Report Post-processor", + ) + ) + findings: List[BaseFinding] = Field( + default_factory=list, description="List of security findings from all scanners" + ) + scanners_used: Annotated[ + List[Scanner], Field(description="List of scanners used in this report") + ] = [] + _scanners_used_raw: Optional[List[Dict[str, str]]] = None + + def __init__(self, **data): + super().__init__(**data) + self._aggregator = FindingAggregator() + self._trend_analyzer = TrendAnalyzer() + for finding in self.findings: + self._aggregator.add_finding(finding) + + def deduplicate_findings(self) -> List[BaseFinding]: + """Remove duplicate findings based on key attributes.""" + deduped = self._aggregator.deduplicate() + self.findings = deduped + return deduped + + def group_findings_by_type(self) -> Dict[str, List[BaseFinding]]: + """Group findings by their scanner rule ID.""" + return self._aggregator.group_by_type() + + def group_findings_by_severity(self) -> Dict[str, List[BaseFinding]]: + """Group findings by their severity level.""" + return self._aggregator.group_by_severity() + + def add_scan_findings(self, scan_time: datetime): + """Add current findings to trend analysis.""" + self._trend_analyzer.add_scan_findings(scan_time, self.findings) + + def get_finding_counts_over_time(self) -> Dict[datetime, int]: + """Get the count of findings at each scan time.""" + return self._trend_analyzer.get_finding_counts_over_time() + + def get_severity_trends(self) -> Dict[str, Dict[datetime, int]]: + """Get finding counts by severity over time.""" + return self._trend_analyzer.get_severity_trends() + + def get_new_findings( + self, previous_scan: datetime, current_scan: datetime + ) -> List[BaseFinding]: + """Get findings that appeared in current scan but not in previous scan.""" + return self._trend_analyzer.get_new_findings(previous_scan, current_scan) + + def get_resolved_findings( + self, previous_scan: datetime, current_scan: datetime + ) -> List[BaseFinding]: + """Get findings that were in previous scan but not in current scan.""" + return self._trend_analyzer.get_resolved_findings(previous_scan, current_scan) + + def _convert_to_scanner(self, scanner_dict: Dict[str, str]) -> Scanner: + """Convert a scanner dictionary to Scanner object.""" + # Ensure required fields with defaults + scanner_dict_copy = scanner_dict.copy() + required_fields = { + "type": "SAST", + "version": "1.0.0", + "description": "Security scanner", + } + for field, default in required_fields.items(): + if field not in scanner_dict_copy: + scanner_dict_copy[field] = default + + return Scanner( + name=scanner_dict_copy["name"], + version=scanner_dict_copy["version"], + type=scanner_dict_copy["type"], + description=scanner_dict_copy["description"], + ) + + @property + def scanners(self) -> List[Scanner]: + """Get scanners as Scanner objects for backward compatibility. + + Returns: + List[Scanner]: List of Scanner objects converted from scanners_used data. + Returns empty list if no scanners are defined. + """ + if not hasattr(self, "_scanners"): + self._scanners = self.scanners_used if self.scanners_used else [] + return self._scanners.copy() # Return copy to prevent modification + + @classmethod + def from_json(cls, json_data: Union[str, Dict[str, Any]]) -> "ASHARPModel": + """Parse JSON data into an ASHARPModel instance. + + Args: + json_data: Either a JSON string or dictionary containing the report data. + Must include metadata and findings fields. + + Returns: + ASHARPModel instance populated with the report data. + + Raises: + ValidationError: If the JSON data is missing required fields or has invalid values. + """ + if isinstance(json_data, str): + return cls.model_validate_json(json_data) + return cls.model_validate(json_data) diff --git a/src/automated_security_helper/models/container_scan.py b/src/automated_security_helper/models/container_scan.py new file mode 100644 index 0000000..88f9f72 --- /dev/null +++ b/src/automated_security_helper/models/container_scan.py @@ -0,0 +1,41 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Models for Container Scanning findings.""" + +from typing import Annotated, List, Optional +from pydantic import BaseModel, Field +from automated_security_helper.models.core import BaseFinding + + +class ContainerVulnerability(BaseFinding): + """Model for container security findings.""" + + package_name: str = Field(..., description="Name of the vulnerable package") + installed_version: str = Field(..., description="Currently installed version") + layer_id: Optional[str] = Field( + None, description="ID of the affected container layer" + ) + fixed_version: Optional[str] = Field( + None, description="Version that fixes the vulnerability" + ) + base_image_name: Optional[str] = Field( + None, description="Base image name if vulnerability is from base image" + ) + + +class ContainerScanReport(BaseModel): + """Container for container scanning findings.""" + + findings: List[ContainerVulnerability] = Field(default_factory=list) + scanner_name: str = Field(..., description="Name of the container scanning tool") + registry: Optional[str] = Field(None, description="Container registry scanned") + scan_timestamp: str = Field(..., description="Timestamp when scan was performed") + image_name: Annotated[str, Field(description="Name of the container image")] = None + image_tag: Annotated[str, Field(description="Tag of the container image")] = None + definition: Annotated[ + str, + Field( + description="Definition of the container image, e.g. the Dockerfile that created it." + ), + ] = None diff --git a/src/automated_security_helper/models/core.py b/src/automated_security_helper/models/core.py new file mode 100644 index 0000000..e08cdff --- /dev/null +++ b/src/automated_security_helper/models/core.py @@ -0,0 +1,208 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Core models for security findings.""" + +from datetime import datetime, timezone +from typing import Any, Optional, Dict, Literal, Annotated, Union +from pydantic import BaseModel, Field, field_validator, ConfigDict + +# Define valid severity levels at module level for use across all finding types +SeverityLevel = Literal["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"] +VALID_SEVERITY_VALUES = frozenset({"CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"}) +SCANNER_TYPES = Literal[ + # Standard scanner types + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM", +] + + +class Location(BaseModel): + """Represents the location of a finding in the codebase.""" + + file_path: Annotated[ + str, Field(..., description="Path to the file containing the finding") + ] + start_line: Annotated[ + Optional[int], Field(None, description="Starting line number of the finding") + ] + end_line: Annotated[ + Optional[int], Field(None, description="Ending line number of the finding") + ] + snippet: Annotated[ + Optional[str], Field(None, description="Code snippet related to the finding") + ] + + +class Scanner(BaseModel): + """Represents metadata about the security scanner.""" + + name: Annotated[ + str, + Field( + min_length=1, pattern=r"^[a-zA-Z][\w-]*$", description="Name of the scanner" + ), + ] + version: Annotated[str, Field(description="Version of the scanner")] = "1.0.0" + type: Annotated[ + SCANNER_TYPES, + Field(description="Type of scanner (e.g., SAST, DAST, SBOM)"), + ] = "SAST" + description: Annotated[str, Field(description="Description of the scanner")] = None + + model_config = ConfigDict( + str_strip_whitespace=True, + extra="allow", + arbitrary_types_allowed=True, + ) + + @field_validator("name") + @classmethod + def validate_name(cls, v: str) -> str: + """Validate scanner name is not empty.""" + if not v.strip(): + raise ValueError("Scanner name cannot be empty") + return v.strip() + + +class BaseFinding(BaseModel): + """Base model for all security findings.""" + + model_config = ConfigDict( + str_strip_whitespace=True, + arbitrary_types_allowed=True, + extra="allow", + json_encoders={datetime: lambda v: v.isoformat()}, + ) + + id: Annotated[ + str, + Field( + ..., + min_length=1, + pattern=r"^[A-Za-z][\/\.\w-]+$", + description="Unique identifier for the finding", + # alias="finding_id" + ), + ] + + # @model_validator(mode='before') + # @classmethod + # def handle_id_finding_id(cls, data: Any) -> Any: + # """Convert between id and finding_id.""" + # if isinstance(data, dict): + # if 'finding_id' in data and 'id' not in data: + # data['id'] = data['finding_id'] + # elif 'id' in data: + # data['finding_id'] = data['id'] + # return data + + title: Annotated[ + str, Field(..., min_length=1, description="Title or name of the finding") + ] + severity: Annotated[ + SeverityLevel, Field(..., description="Severity level of the finding") + ] + location: Annotated[ + Location, Field(..., description="Location information for the finding") + ] + link: Annotated[ + str, Field(description="Link to more information about the finding") + ] = None + timestamp: Annotated[ + str, + Field( + description="When the finding was created", + ), + ] = None + description: Annotated[ + str, Field(description="Detailed description of the finding") + ] = None + status: Annotated[str, Field(description="Current status of the finding")] = "OPEN" + + created_at: Annotated[ + str, + Field( + description="When the finding was first detected (in UTC)", + ), + ] = None + updated_at: Annotated[ + str, + Field( + description="When the finding was last updated (in UTC)", + ), + ] = None + remediation: Annotated[ + str, Field(description="Guidance for fixing the finding") + ] = None + metadata: Annotated[ + Dict, + Field(description="Additional scanner-specific metadata"), + ] = {} + raw: Annotated[ + Any, + Field(description="Raw result from the scanner"), + ] = None + + @field_validator("severity") + @classmethod + def validate_severity(cls, v: str) -> str: + """Validate that severity is one of the allowed values.""" + if v not in VALID_SEVERITY_VALUES: + raise ValueError(f"Severity must be one of {sorted(VALID_SEVERITY_VALUES)}") + return v.upper() + + @field_validator("status") + @classmethod + def validate_status(cls, v: str) -> str: + """Validate finding status.""" + valid_statuses = { + "OPEN", + "CLOSED", + "IN_PROGRESS", + "FALSE_POSITIVE", + "RISK_ACCEPTED", + } + if v.upper() not in valid_statuses: + raise ValueError(f"Status must be one of {sorted(valid_statuses)}") + return v.upper() + + @field_validator("id", "title", "description") + @classmethod + def validate_non_empty_str(cls, v: str, info) -> str: + """Validate string fields are not empty.""" + v = v.strip() + if not v: + raise ValueError(f"{info.field_name} cannot be empty") + return v + + @field_validator("timestamp", "created_at", "updated_at") + @classmethod + def validate_datetime(cls, v: Union[str, datetime] = None) -> str: + """Validate that value is timestamp or, if empty, set to current datetime""" + if not v: + v = datetime.now(timezone.utc) + if isinstance(v, str): + v = datetime.fromisoformat(v.strip()) + return v.isoformat(timespec="seconds") + + def model_post_init(self, context): + super().model_post_init(context) + default_timestamp = datetime.now(timezone.utc).isoformat(timespec="seconds") + if not self.created_at: + self.created_at = default_timestamp + if not self.updated_at: + self.updated_at = default_timestamp + if not self.timestamp: + self.timestamp = default_timestamp + + +# Define exports at the bottom after all classes are defined +__all__ = ["Location", "Scanner", "BaseFinding"] diff --git a/src/automated_security_helper/models/data_interchange.py b/src/automated_security_helper/models/data_interchange.py new file mode 100644 index 0000000..75d60b4 --- /dev/null +++ b/src/automated_security_helper/models/data_interchange.py @@ -0,0 +1,306 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Models and interfaces for data interchange and report generation.""" + +from datetime import datetime, timezone +from typing import Dict, List, Any, Union, Annotated +from enum import Enum +from pydantic import BaseModel, Field, field_validator, ConfigDict + +from automated_security_helper.models.core import BaseFinding, Scanner + + +class ExportFormat(str, Enum): + """Supported export formats.""" + + TEXT = "text" + JSON = "json" + YAML = "yaml" + CSV = "csv" + HTML = "html" + DICT = "dict" + JUNITXML = "junitxml" + SARIF = "sarif" + ASFF = "asff" + CYCLONEDX = "cyclonedx" + SPDX = "spdx" + + +class DataInterchange(BaseModel): + """Base model for data interchange capabilities.""" + + model_config = ConfigDict( + str_strip_whitespace=True, + arbitrary_types_allowed=True, + extra="allow", + ) + + name: Annotated[ + str, Field(..., min_length=1, description="Name of the data interchange format") + ] + description: Annotated[ + str, Field(min_length=1, description="Description of the data") + ] = None + timestamp: Annotated[ + str, + Field( + description="Timestamp of the export in UTC", + ), + ] = None + version: Annotated[str, Field(description="Version of the export format")] = "1.0" + metadata: Annotated[ + Dict[str, Any], + Field(description="Additional metadata about the export"), + ] = {} + + @field_validator("name") + @classmethod + def validate_name(cls, v: str) -> str: + """Validate name is not empty.""" + v = v.strip() + if not v: + raise ValueError("Name cannot be empty") + return v + + @field_validator("timestamp") + @classmethod + def validate_datetime(cls, v: Union[str, datetime] = None) -> str: + """Validate that value is timestamp or, if empty, set to current datetime""" + if not v: + v = datetime.now(timezone.utc) + if isinstance(v, str): + v = datetime.fromisoformat(v.strip()) + return v.isoformat(timespec="seconds") + + def model_post_init(self, context): + super().model_post_init(context) + default_timestamp = datetime.now(timezone.utc).isoformat(timespec="seconds") + if not self.timestamp: + self.timestamp = default_timestamp + + +class ReportMetadata(BaseModel): + """Metadata for security reports.""" + + model_config = ConfigDict( + str_strip_whitespace=True, + extra="allow", + arbitrary_types_allowed=True, + json_encoders={ + datetime: lambda v: v.isoformat(), + ExportFormat: lambda v: str(v), + }, + ) + + report_id: Annotated[ + str, + Field( + min_length=1, + pattern=r"^[A-Za-z][\/\.\w-]+$", + description="Unique identifier for the report", + ), + ] = None + generated_at: Annotated[str, Field()] = None + project_name: Annotated[ + str, Field(min_length=1, description="Name of the project being scanned") + ] = None + tool_version: Annotated[ + str, Field(min_length=1, description="Version of the security tool") + ] = None + description: Annotated[ + str, Field(min_length=1, description="Description of the tool/scan") + ] = None + summary_stats: Annotated[ + Dict[str, int], + Field(description="Summary statistics (e.g., count by severity)"), + ] = {"total": 0, "critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0} + + @field_validator("project_name") + @classmethod + def validate_non_empty_str(cls, v: str, info) -> str: + """Validate string fields are not empty.""" + v = v.strip() + if not v: + raise ValueError(f"{info.field_name} cannot be empty") + return v + + @field_validator("generated_at") + @classmethod + def validate_datetime(cls, v: Union[str, datetime] = None) -> str: + """Validate that value is timestamp or, if empty, set to current datetime""" + if not v: + v = datetime.now(timezone.utc) + if isinstance(v, str): + v = datetime.fromisoformat(v.strip()) + return v.isoformat(timespec="seconds") + + def model_post_init(self, context): + super().model_post_init(context) + default_timestamp = datetime.now(timezone.utc).isoformat(timespec="seconds") + if not self.generated_at: + self.generated_at = default_timestamp + if not self.report_id: + self.report_id = ( + f"ASH-{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}" + ) + + +class ScanStatistics(BaseModel): + """Statistics for static analysis scan results.""" + + files_scanned: Annotated[ + int, Field(description="Total number of files scanned") + ] = 0 + lines_of_code: Annotated[ + int, Field(description="Total number of lines of code") + ] = 0 + total_findings: Annotated[int, Field(description="Total number of findings")] = 0 + findings_by_type: Annotated[ + dict, Field(description="Count of findings by severity level") + ] = {} + scan_duration_seconds: Annotated[ + float, Field(description="Duration of scan in seconds") + ] = 0.0 + + +class SecurityReport(DataInterchange): + """Model for comprehensive security reports.""" + + model_config = ConfigDict( + str_strip_whitespace=True, + extra="allow", + arbitrary_types_allowed=True, + json_encoders={datetime: lambda v: v.isoformat()}, + ) + scanned_paths: Annotated[ + List[str], + Field( + description="The list of paths or path patterns that were scanned as part of this report." + ), + ] = [] + scan_type: Annotated[str, Field(description="Type of security scan")] = "security" + findings: Annotated[ + List[BaseFinding], Field(description="List of security findings") + ] = [] + + description: Annotated[ + str, Field(description="Description of the security scan report") + ] = "Security scan report" + scanners_used: Annotated[ + List[Dict[str, str] | Scanner], + Field(description="List of scanners used in this report"), + ] = [] + statistics: Annotated[ScanStatistics, Field()] = ScanStatistics() + metadata: Annotated[ReportMetadata, Field(description="Report metadata")] = ( + ReportMetadata() + ) + + def add_finding(self, finding: BaseFinding) -> None: + """Add a finding to the report.""" + self.findings.append(finding) + + def merge(self, other: "SecurityReport") -> None: + """Merge another report into this one.""" + self.findings.extend(other.findings) + # Merge metadata, preserving existing values + for key, value in other.metadata.items(): + if key not in self.metadata: + self.metadata[key] = value + elif isinstance(self.metadata[key], list) and isinstance( + other.metadata[key], list + ): + self.metadata[key].extend(other.metadata[key]) + + @field_validator("scan_type") + @classmethod + def validate_scan_type(cls, v: str) -> str: + """Validate scan type.""" + valid_types = { + "security", + "vulnerability", + "container", + "iac", + "sbom", + "dependency", + "sast", + "dast", + } + if v.lower() not in valid_types: + raise ValueError(f"Scan type must be one of {sorted(valid_types)}") + return v.lower() + + def export( + self, format: ExportFormat = ExportFormat.JSON + ) -> Union[str, Dict[str, Any]]: + """Export the report in the specified format.""" + if format == ExportFormat.JSON: + return self.model_dump_json( + exclude={ + "metadata": {"parent_report"}, + "findings": {"__all__": {"id"}}, + }, + exclude_none=True, + exclude_defaults=True, + exclude_unset=True, + by_alias=True, + indent=2, + ) + elif format == ExportFormat.YAML: + import yaml + + return yaml.dump(self.model_dump()) + elif format == ExportFormat.CSV: + # Basic CSV export of findings + import csv + import io + + output = io.StringIO() + if self.findings: + writer = csv.DictWriter(output, fieldnames=self.findings[0].keys()) + writer.writeheader() + writer.writerows(self.findings) + return output.getvalue() + elif format == ExportFormat.HTML: + # Basic HTML report + findings_html = "\n".join( + f"
  • {f.get('title', 'Unknown')}: {f.get('severity', 'Unknown')}
  • " + for f in self.findings + ) + return f""" + + +

    Security Report

    +

    Project: {self.metadata.project_name}

    +

    Generated: {self.metadata.generated_at}

    +

    Findings:

    +
      {findings_html}
    + + + """ + elif format == ExportFormat.DICT: + return self.model_dump() + + raise ValueError(f"Unsupported export format: {format}") + + @classmethod + def from_json(cls, json_data: Union[str, Dict[str, Any]]) -> "SecurityReport": + """Import a report from JSON data.""" + if isinstance(json_data, str): + return cls.model_validate_json(json_data) + return cls.model_validate(json_data) + + def track_history(self, previous_report: "SecurityReport") -> Dict[str, Any]: + """Compare with a previous report to track changes.""" + current_findings_set = {(f.id, f.severity) for f in self.findings} + previous_findings_set = {(f.id, f.severity) for f in previous_report.findings} + + new_findings = current_findings_set - previous_findings_set + resolved_findings = previous_findings_set - current_findings_set + + return { + "new_findings": len(new_findings), + "resolved_findings": len(resolved_findings), + "total_active_findings": len(current_findings_set), + "comparison_date": previous_report.metadata.generated_at, + } diff --git a/src/automated_security_helper/models/dependency_scan.py b/src/automated_security_helper/models/dependency_scan.py new file mode 100644 index 0000000..d59594b --- /dev/null +++ b/src/automated_security_helper/models/dependency_scan.py @@ -0,0 +1,41 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Models for Dependency Scanning findings.""" + +from typing import Annotated, List, Optional, Dict +from pydantic import BaseModel, Field +from automated_security_helper.models.core import BaseFinding + + +class DependencyVulnerability(BaseFinding): + """Model for dependency security findings.""" + + package_name: str = Field(..., description="Name of the vulnerable package") + package_version: str = Field(..., description="Version of the vulnerable package") + ecosystem: str = Field(..., description="Package ecosystem (e.g., npm, pip, maven)") + dependency_type: str = Field(..., description="Direct or transitive dependency") + fixed_version: Optional[str] = Field( + None, description="Version that fixes the vulnerability" + ) + dependency_path: List[str] = Field( + default_factory=list, description="Path to dependency in dependency tree" + ) + cves: List[str] = Field( + default_factory=list, description="Associated CVE identifiers" + ) + + +class DependencyScanReport(BaseModel): + """Container for dependency scanning findings.""" + + scanner_name: str = Field(..., description="Name of the dependency scanning tool") + manifest_file: str = Field(..., description="Path to dependency manifest file") + scan_timestamp: str = Field(..., description="Timestamp when scan was performed") + findings: Annotated[List[DependencyVulnerability], Field()] = [] + dependencies: Annotated[ + Dict[str, str], + Field( + description="Dependencies as keys and their version number as the corresponding value" + ), + ] = {} diff --git a/src/automated_security_helper/models/dynamic_analysis.py b/src/automated_security_helper/models/dynamic_analysis.py new file mode 100644 index 0000000..acd037d --- /dev/null +++ b/src/automated_security_helper/models/dynamic_analysis.py @@ -0,0 +1,45 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Models for Dynamic Analysis findings.""" + +from typing import Annotated, Any, List, Optional, Dict +from pydantic import BaseModel, Field +from automated_security_helper.models.core import BaseFinding + + +class DynamicAnalysisFinding(BaseFinding): + """Model for dynamic analysis security findings.""" + + endpoint: str = Field(..., description="Affected endpoint or URL") + request_method: str = Field(..., description="HTTP method used") + request_headers: Optional[Dict[str, str]] = Field( + None, description="Request headers" + ) + request_body: Optional[str] = Field(None, description="Request body") + response_status: int = Field(..., description="HTTP response status code") + reproduction_steps: Optional[List[str]] = Field( + None, description="Steps to reproduce the issue" + ) + + +class DynamicAnalysisScanCoverage(BaseModel): + endpoints_tested: int = 0 + auth_tested: bool = False + input_vectors_tested: List[Any] = [] + + +class DynamicAnalysisReport(BaseModel): + """Container for dynamic analysis findings.""" + + scanner_name: Annotated[ + str, Field(..., description="Name of the dynamic analysis tool") + ] + target_url: Annotated[str, Field(..., description="Base URL that was scanned")] + scan_timestamp: Annotated[ + str, Field(..., description="Timestamp when scan was performed") + ] + findings: Annotated[List[DynamicAnalysisFinding], Field()] = [] + scan_coverage: Annotated[ + DynamicAnalysisScanCoverage, Field(description="Coverage metrics for the scan") + ] = DynamicAnalysisScanCoverage() diff --git a/src/automated_security_helper/models/iac_scan.py b/src/automated_security_helper/models/iac_scan.py new file mode 100644 index 0000000..5a8c5b7 --- /dev/null +++ b/src/automated_security_helper/models/iac_scan.py @@ -0,0 +1,113 @@ +"""Models for Infrastructure as Code Scanning findings.""" + +from enum import Enum +from typing import Annotated, List, Optional, Dict, Union +from pydantic import Field, ConfigDict +from automated_security_helper.models.core import BaseFinding +from automated_security_helper.models.data_interchange import SecurityReport + +__all__ = [ + "ComplianceFramework", + "IaCFinding", + "IaCVulnerability", + "IaCScanReport", + "CheckResultType", +] + + +class ComplianceFramework(str, Enum): + """Standard compliance frameworks.""" + + CIS = "CIS" + HIPAA = "HIPAA" + NIST = "NIST" + SOC2 = "SOC2" + PCI = "PCI" + + +class CheckResultType(str, Enum): + """Types of check results from IaC scanning.""" + + FAILED = "failed_checks" + PASSED = "passed_checks" + SKIPPED = "skipped_checks" + ERROR = "parsing_errors" + + +class IaCFinding(BaseFinding): + """Model for Infrastructure as Code security findings.""" + + resource_name: str = Field(..., description="Name of the affected resource") + resource_type: Annotated[str, Field(description="Type of cloud resource")] = None + expected_value: Optional[str] = Field( + None, description="Expected configuration value" + ) + actual_value: Optional[str] = Field(None, description="Current configuration value") + compliance_frameworks: Annotated[ + List[Union[ComplianceFramework, str]], + Field(description="Compliance frameworks this violation impacts"), + ] = [] + remediation_terraform: Optional[str] = Field( + None, description="Terraform remediation steps" + ) + remediation_cloudformation: Optional[str] = Field( + None, description="CloudFormation remediation steps" + ) + resource_impact: str = Field( + default="Unknown", + description="Description of how this violation impacts the resource", + ) + dependent_resources: List[str] = Field( + default_factory=list, + description="Resources that may be impacted by this violation", + ) + estimated_cost_impact: Optional[float] = Field( + None, description="Estimated monthly cost impact in USD" + ) + rule_id: str = Field(..., description="Identifier of the violated rule") + template_validation_errors: List[str] = Field( + default_factory=list, + description="Any template validation errors related to this finding", + ) + + +class IaCVulnerability(IaCFinding): + """Model for Infrastructure as Code vulnerabilities, extending IaCFinding.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + violation_details: Dict[str, str] = Field( + default_factory=dict, description="Details about the security violation" + ) + check_result_type: CheckResultType = Field( + default=CheckResultType.FAILED, + description="Type of check result (failed, passed, skipped, error)", + ) + + +class IaCScanReport(SecurityReport): + """Report containing Infrastructure as Code scan results.""" + + findings: Annotated[ + List[IaCVulnerability], + Field(default_factory=list, description="List of IaC vulnerabilities"), + ] = [] + iac_framework: Annotated[ + str, + Field( + description="Infrastructure-as-Code framework (e.g., Amazon CloudFormation, Terraform, AWS CDK, Helm)", + examples=[ + "cloudformation", + "terraform", + "kubernetes", + "docker", + "helm", + "cdk", + ], + ), + ] = None + resources_checked: Annotated[ + Dict[str, int], + Field( + description="Count of each type of resource checked if the information is available from the scanner." + ), + ] = {} diff --git a/src/automated_security_helper/models/interfaces.py b/src/automated_security_helper/models/interfaces.py new file mode 100644 index 0000000..f6b4cee --- /dev/null +++ b/src/automated_security_helper/models/interfaces.py @@ -0,0 +1,61 @@ +from abc import ABC, abstractmethod +from typing import Any, Dict, List + +from automated_security_helper.models.asharp_model import ASHARPModel +from ..config.config import ScannerPluginConfig, ParserConfig +from .core import Location + + +class IScannerParser(ABC): + """Interface for parsing scanner results.""" + + @abstractmethod + def configure(self, config: ParserConfig) -> None: + """Configure the parser with provided configuration.""" + pass + + @abstractmethod + def parse(self, raw_results: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """Parse raw scanner results into a standardized format.""" + pass + + @abstractmethod + def get_finding_locations(self, finding: Dict[str, Any]) -> List[Location]: + """Extract location information from a finding.""" + pass + + +class IScanner(ABC): + """Interface for security scanners.""" + + @abstractmethod + def configure(self, config: ScannerPluginConfig) -> None: + """Configure the scanner with provided configuration.""" + pass + + @abstractmethod + def scan(self, target: str) -> List[Dict[str, Any]]: + """Execute the security scan on the target.""" + pass + + @abstractmethod + def validate(self) -> bool: + """Validate scanner configuration and requirements.""" + pass + + def _set_parser(self, parser: IScannerParser) -> None: + """Set the parser for the scanner.""" + self.parser = parser + + def _parse_results(self, raw_results: Any): + """Parse raw scanner output.""" + return self.parser.parse(raw_results) + + +class IOutputReporter(ABC): + """Interface for output formatters.""" + + @abstractmethod + def format(self, model: ASHARPModel) -> str: + """Format ASH model into output string.""" + pass diff --git a/src/automated_security_helper/models/json_serializer.py b/src/automated_security_helper/models/json_serializer.py new file mode 100644 index 0000000..41677bc --- /dev/null +++ b/src/automated_security_helper/models/json_serializer.py @@ -0,0 +1,32 @@ +"""JSON serialization utilities for ASHARPModel.""" + +import json +from pathlib import Path +from typing import Optional +from .asharp_model import ASHARPModel + + +class ASHARPModelSerializer: + """Serializer for ASHARPModel instances.""" + + @staticmethod + def save_model(model: ASHARPModel, output_dir: Path) -> None: + """Save ASHARPModel as JSON alongside aggregated results.""" + if not output_dir.exists(): + output_dir.mkdir(parents=True) + + # Save model as JSON + json_path = output_dir / "asharp_model.json" + with open(json_path, "w") as f: + json.dump(model.dict(), f, indent=2, default=str) + + @staticmethod + def load_model(json_path: Path) -> Optional[ASHARPModel]: + """Load ASHARPModel from JSON file.""" + if not json_path.exists(): + return None + + with open(json_path) as f: + json_data = json.load(f) + + return ASHARPModel.from_json(json_data) diff --git a/src/automated_security_helper/models/parser.py b/src/automated_security_helper/models/parser.py new file mode 100644 index 0000000..800e13e --- /dev/null +++ b/src/automated_security_helper/models/parser.py @@ -0,0 +1,55 @@ +"""Parser interface implementation for ASH scanners.""" + +from typing import Dict, List, Any, Optional + +from automated_security_helper.config.config import ParserConfig +from .interfaces import IScannerParser +from .core import Location +from .asharp_model import ASHARPModel + + +class ScannerParser(IScannerParser): + """Parser implementation for scanner results.""" + + def __init__(self): + """Initialize the parser.""" + self._config: Optional[ParserConfig] = None + self._model: Optional[ASHARPModel] = None + + def configure(self, config: ParserConfig) -> None: + """Configure the parser with scanner-specific settings.""" + self._config = config + + def parse(self, raw_results: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """Parse raw scanner results into a standardized format.""" + parsed_results = [] + for result in raw_results: + parsed_result = self._parse_result(result) + if parsed_result: + parsed_results.append(parsed_result) + return parsed_results + + def get_finding_locations(self, finding: Dict[str, Any]) -> List[Location]: + """Extract location information from a finding.""" + locations = [] + if "file" in finding: + location = Location( + file_path=finding["file"], + line_number=finding.get("line_number", 0), + column=finding.get("column", 0), + ) + locations.append(location) + return locations + + def _parse_result(self, result: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """Parse a single result into the standardized format.""" + if not result: + return None + + parsed = { + "type": result.get("type", "unknown"), + "severity": result.get("severity", "INFO"), + "description": result.get("description", ""), + "locations": self.get_finding_locations(result), + } + return parsed diff --git a/src/automated_security_helper/models/sbom.py b/src/automated_security_helper/models/sbom.py new file mode 100644 index 0000000..71e11c5 --- /dev/null +++ b/src/automated_security_helper/models/sbom.py @@ -0,0 +1,179 @@ +"""Models for Software Bill of Materials (SBOM).""" + +from __future__ import annotations + +import datetime +from typing import Any, List, Optional, Dict, Union +from typing_extensions import Annotated +from .data_interchange import DataInterchange, ExportFormat + +from pydantic import BaseModel, Field, ConfigDict, field_validator + +__all__ = ["SBOMComponent", "SBOMMetadata", "SBOMReport", "SBOMPackage"] + + +class SBOMComponent(BaseModel): + """Represents a software component in the SBOM.""" + + model_config = ConfigDict( + str_strip_whitespace=True, + arbitrary_types_allowed=True, + extra="forbid", + ) + + name: Annotated[str, Field(..., min_length=1, description="Name of the component")] + version: Annotated[ + str, Field(min_length=1, description="Version of the component") + ] = None + type: Annotated[ + str, Field(description="Type of the component (e.g., npm, pypi)") + ] = None + license: Annotated[str, Field(description="License of the component")] = None + publisher: Annotated[str, Field(description="Publisher of the component")] = None + metadata: Annotated[ + Dict[str, Any], Field(description="Additional metadata about the component") + ] = {} + dependencies: Annotated[ + List["SBOMComponent"], + Field(default_factory=list, description="List of dependencies"), + ] + + @field_validator("type") + @classmethod + def validate_type(cls, v: str) -> str: + """Validate component type.""" + valid_types = {"npm", "pypi", "maven", "nuget", "cargo", "composer", "docker"} + if v.lower() not in valid_types: + raise ValueError(f"Component type must be one of {sorted(valid_types)}") + return v.lower() + + @field_validator("version") + @classmethod + def validate_version(cls, v: str) -> str: + """Validate version format.""" + if not v or not v.strip(): + raise ValueError("Version cannot be empty") + return v.strip() + + +class SBOMMetadata(BaseModel): + """Represents vulnerability correlation information for a component.""" + + component: "SBOMComponent" = Field( + ..., description="The component this metadata refers to" + ) + known_vulnerabilities: Annotated[ + List[str], Field(description="Known vulnerabilities") + ] = [] + version_constraints: Annotated[ + Dict[str, str], Field(description="Version constraints") + ] = {} + dependency_chain: Annotated[ + List["SBOMComponent"], Field(description="Chain of dependencies") + ] = [] + + model_config = ConfigDict(arbitrary_types_allowed=True) + + +class SBOMPackage(SBOMComponent): + """Extended model for SBOM package information.""" + + package_dependencies: Annotated[ + List[Dict[str, str]], Field(description="List of package dependencies") + ] = [] + metadata: Annotated[ + Dict[str, str], Field(description="Additional package metadata") + ] = {} + + +class SBOMReport(DataInterchange): + """Represents an SBOM report containing all components and their metadata.""" + + project_name: Annotated[str, Field(None, description="Alias for name")] = None + version: Annotated[str, Field(..., description="Version of the report")] + generated_at: Annotated[str, Field()] = datetime.datetime.now( + datetime.timezone.utc + ).isoformat(timespec="seconds") + components: Annotated[List[SBOMComponent], Field(validation_alias="packages")] = [] + packages: Annotated[List[SBOMComponent], Field(None)] = None + metadata: Annotated[List[SBOMMetadata], Field()] = [] + description: Annotated[Optional[str], Field(None)] + + model_config = ConfigDict( + arbitrary_types_allowed=True, + extra="allow", + ser_json_timedelta="iso8601", + revalidate_instances="always", + allow_methods=True, + ) + + def export( + self, format: ExportFormat = ExportFormat.JSON + ) -> Union[str, Dict[str, Any]]: + """Export the SBOM report in the specified format.""" + if format == ExportFormat.JSON: + return self.model_dump_json(indent=2, serialize_as_any=True) + elif format == ExportFormat.YAML: + import yaml + + return yaml.dump(self.model_dump()) + elif format == ExportFormat.CSV: + import csv + import io + + output = io.StringIO() + if self.components: + component_dicts = [comp.model_dump() for comp in self.components] + writer = csv.DictWriter(output, fieldnames=component_dicts[0].keys()) + writer.writeheader() + writer.writerows(component_dicts) + return output.getvalue() + elif format == ExportFormat.HTML: + components_html = "\n".join( + f"
  • {c.name} (v{c.version}): {c.license}
  • " + for c in self.components + ) + return f""" + + +

    SBOM Report: {self.name}

    +

    Version: {self.version}

    +

    Generated: {self.generated_at}

    +

    Components:

    +
      {components_html}
    + + + """ + elif format == ExportFormat.DICT: + return self.model_dump() + elif format == ExportFormat.CYCLONEDX: + return self.model_dump() + elif format == ExportFormat.SPDX: + return self.model_dump() + + raise ValueError(f"Unsupported export format: {format}") + + def get_dependency_tree(self) -> Dict[str, Any]: + """Generate a tree structure of package dependencies.""" + + def build_tree(component: SBOMComponent) -> Dict[str, Any]: + tree = { + "name": component.name, + "version": component.version, + "dependencies": {}, + } + for dep in component.dependencies: + tree["dependencies"][dep.name] = build_tree(dep) + return tree + + result = {} + packages = self.packages or [] + for package in packages: + result[package.name] = build_tree(package) + return result + + +# Initialize types at module level +SBOMComponent.model_rebuild() +SBOMMetadata.model_rebuild() +SBOMReport.model_rebuild() diff --git a/src/automated_security_helper/models/scan_results.py b/src/automated_security_helper/models/scan_results.py new file mode 100644 index 0000000..2fb48a9 --- /dev/null +++ b/src/automated_security_helper/models/scan_results.py @@ -0,0 +1,65 @@ +"""Module containing the ScanResultsContainer class for wrapping scanner results.""" + +from typing import Any, Dict, List, Optional + + +class ScanResultsContainer: + """Container for scanner results with metadata.""" + + def __init__(self): + """Initialize an empty container.""" + self._findings: List[Any] = [] + self._metadata: Dict[str, Any] = {} + self._raw_results: Optional[Dict[str, Any]] = None + + def add_findings(self, findings: List[Any]) -> None: + """Add findings to the container. + + Args: + findings: List of findings to add + """ + self._findings.extend(findings) + + def add_metadata(self, key: str, value: Any) -> None: + """Add metadata to the container. + + Args: + key: Metadata key + value: Metadata value + """ + self._metadata[key] = value + + def set_raw_results(self, results: Dict[str, Any]) -> None: + """Set raw scanner results. + + Args: + results: Raw scanner results + """ + self._raw_results = results + + @property + def findings(self) -> List[Any]: + """Get findings. + + Returns: + List of findings + """ + return self._findings + + @property + def metadata(self) -> Dict[str, Any]: + """Get metadata. + + Returns: + Metadata dictionary + """ + return self._metadata + + @property + def raw_results(self) -> Optional[Dict[str, Any]]: + """Get raw results. + + Returns: + Raw scanner results if set, None otherwise + """ + return self._raw_results diff --git a/src/automated_security_helper/models/scanner_plugin.py b/src/automated_security_helper/models/scanner_plugin.py new file mode 100644 index 0000000..818bd10 --- /dev/null +++ b/src/automated_security_helper/models/scanner_plugin.py @@ -0,0 +1,412 @@ +"""Module containing the ScannerPlugin base class.""" + +from abc import abstractmethod +import logging +from pathlib import Path +from queue import SimpleQueue +import shutil +from threading import Thread +from typing import Any, Dict, List, Optional +import subprocess + +from automated_security_helper.config.config import ScannerPluginConfig +from automated_security_helper.models.core import BaseFinding +from automated_security_helper.models.data_interchange import ( + ExportFormat, + SecurityReport, +) +from automated_security_helper.exceptions import ScannerError +from automated_security_helper.models.interfaces import IScanner + + +class ScannerPlugin(IScanner): + """Abstract base class for ASH scanner plugins.""" + + source_dir: Path + output_dir: Path + work_dir: Path + results_dir: Path + tool_version: str | None = None + + logger: logging.Logger = logging.Logger(__name__) + + _name: str = "" + _type: str = "" + _is_valid: bool = False + _output: List[str] = [] + _output_format: ExportFormat = ExportFormat.TEXT + _errors: List[str] = [] + _options: Dict[str, Any] = {} + _config: ScannerPluginConfig | None = None + _default_config: ScannerPluginConfig | None = None + _protected_config_properties: List[str] = ["name", "type", "options"] + + def __init__( + self, + source_dir: Optional[Path] = None, + output_dir: Optional[Path] = None, + logger: Optional[logging.Logger] = None, + config: Optional[ScannerPluginConfig] = None, + ) -> None: + """Initialize scanner plugin. + + Note: config parameter takes precedence over default_config(). + If no config is provided, default_config() will be called. + """ + """Initialize the scanner. + + Args: + source_dir: Source directory to scan + output_dir: Output directory for results + logger: Optional logger instance + config: Optional scanner configuration + """ + # Convert paths to Path objects if they're strings + if source_dir and not isinstance(source_dir, Path): + source_dir = Path(source_dir) + if output_dir and not isinstance(output_dir, Path): + output_dir = Path(output_dir) + + # Use default paths if none provided + if not source_dir: + source_dir = Path(".") + if not output_dir: + output_dir = Path("output") + self.source_dir = source_dir + self.output_dir = output_dir + self.work_dir = self.output_dir.joinpath("work") + self.logger = logger if logger else logging.getLogger(__name__) + # Use provided config, otherwise try to get default config + self._config = config if config is not None else self.default_config + + # Set up results directory using scanner name from config + scanner_name = ( + getattr(self._config, "name", "unknown") if self._config else "unknown" + ) + self.results_dir = self.output_dir.joinpath("scanners").joinpath(scanner_name) + output_name = f"{self._config.name if self._config else 'unknown'}_output.json" + self.results_file = self.results_dir.joinpath(output_name).as_posix() + if self.results_dir.exists(): + shutil.rmtree(self.results_dir) + self.results_dir.mkdir(parents=True, exist_ok=True) + + def _set_config(self, config: ScannerPluginConfig = None) -> None: + """Set scanner configuration.""" + self._config = config + + @property + def name(self) -> str: + """Get scanner name from config.""" + if self._config and hasattr(self._config, "name"): + return self._config.name + return self._name + + @property + def type(self) -> str: + """Get the scanner type.""" + return self._type + + @property + def config(self) -> Optional[ScannerPluginConfig]: + """Get the scanner configuration.""" + return self._config + + @property + def default_config(self) -> ScannerPluginConfig: + """Get the scanner default configuration.""" + return self._default_config + + @property + def options(self) -> Dict[str, Any]: + """Get the scanner options.""" + return self._options + + @property + def output(self) -> List[str]: + """Get the scanner output.""" + return self._output + + @property + def output_format(self) -> List[str]: + """Get the scanner output format.""" + return self._output_format + + @property + def errors(self) -> List[str]: + """Get the scanner errors.""" + return self._errors + + def configure(self, config: ScannerPluginConfig | None = None): + """Configure the scanner with the provided config, doing a deep merge if config already exists. + + Args: + config: Scanner configuration as ScannerPluginConfig object or dict + """ + # Return if no new config and no existing config + if config is None and ( + self._config is None or len(self._config.command.__str__()) == 0 + ): + raise ScannerError( + "No configuration provided for this scanner, unable to run scanner" + ) + + # Convert dict config to ScannerPluginConfig + if isinstance(config, dict) and "name" in config: + config = ScannerPluginConfig(**config) + + # If we have a new config object + if isinstance(config, ScannerPluginConfig): + if self._config is None: + # No existing config, just set directly + self._name = config.name + self._type = config.type + if hasattr(config, "options"): + self._options.update(config.options) + self._config = config + else: + # Merge with existing config + self._name = config.name or self._name + self._type = config.type + if hasattr(config, "options"): + self._options.update(config.options) + + # Deep merge other attributes from new config into existing + # Prevent overridding of protected configuration values. + for attr, value in vars(config).items(): + if ( + value is not None + and f"{value}" != "" + and attr not in self._protected_config_properties + ): + setattr(self._config, attr, value) + + if config is None and ( + self._config is None or len(self._config.command.__str__()) == 0 + ): + raise ScannerError( + "No configuration provided for this scanner, unable to run scanner" + ) + + if isinstance(config, dict) and "name" in config: + config = ScannerPluginConfig(**config) + if isinstance(config, ScannerPluginConfig): + self._name = config.name + self._type = config.type + if hasattr(config, "options"): + self._options.update(config.options) + + self._config = config + + def validate(self) -> bool: + """Verify scanner configuration and requirements.""" + exists = shutil.which(self.config.command) is not None + if self._default_config.get_tool_version_command: + self.tool_version = self._run_subprocess( + self._default_config.get_tool_version_command + ) + self._is_valid = ( + exists and self._config is not None and self.tool_version is not None + ) + return self._is_valid + + @abstractmethod + def scan( + self, target: str, options: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """Execute the security scan. + + Args: + target: The target to scan (file, directory, etc.) + options: Optional dictionary of scan options specific to the scanner + + Returns: + Dict[str, Any]: Raw scan results with findings and metadata + + Raises: + ScannerError: If there is an error during scanning + """ + # Pre-scan setup and validation + self._pre_scan(target, options) + + # Build and execute scan command + command = self._resolve_arguments(target) + if options: + command.extend(self._build_option_args(options)) + + try: + output = self._run_subprocess(command) + if self.parser: + parsed_results = self.parser.parse(output) + else: + parsed_results = output + + return { + "findings": parsed_results.get("findings", []), + "metadata": { + "scanner_name": self.name, + "scanner_version": self.tool_version, + "scan_type": self.type, + **parsed_results.get("metadata", {}), + }, + } + except Exception as e: + raise ScannerError(f"Error during scan execution: {str(e)}") + + def _build_security_report(self, scan_results: Any) -> SecurityReport: + """Convert scan results into a SecurityReport.""" + + report = SecurityReport( + name=self.name, + scanner_version=self.version, + metadata={"scan_type": self.type}, + ) + + # Handle both parsed and raw results + results_list = ( + scan_results if isinstance(scan_results, list) else [scan_results] + ) + + for result in results_list: + if isinstance(result, dict): + finding = BaseFinding( + **result, + ) + report.add_finding(finding) + + return report + + def _resolve_arguments(self, target: str) -> List[str]: + """Resolve command arguments for the scanner. + + Args: + target: The target to scan + + Returns: + List of command arguments + """ + if not target: + raise ScannerError("No target specified") + if not self._config: + raise ScannerError("Scanner configuration not set") + if not self._config.command: + raise ScannerError("No command specified") + + path_arg = [ + item + for item in [ + self._config.scan_path_arg, + target, + ] + if item + ] + output_arg = [ + item + for item in [ + self._config.output_arg, + self.results_file, + ] + if item + ] + format_arg = [ + item + for item in [ + self._config.format_arg, + self._config.format_arg_value, + ] + if item + ] + + args = [ + item + for item in [ + self._config.command, + *( + format_arg + if self._config.format_arg_position == "before_args" + else [] + ), + *( + output_arg + if self._config.output_arg_position == "before_args" + else [] + ), + *( + path_arg + if self._config.scan_path_arg_position == "before_args" + else [] + ), + *self._config.args, + *( + format_arg + if self._config.format_arg_position == "after_args" + else [] + ), + *( + output_arg + if self._config.output_arg_position == "after_args" + else [] + ), + *( + path_arg + if self._config.scan_path_arg_position == "after_args" + else [] + ), + ] + if item + ] + return args + + def _pre_scan(self, target: str, options: Optional[Dict[str, Any]] = None) -> None: + """Validates that `target` is a valid path and sets options. + + Args: + target: The target directory or file to scan. + options: A dictionary of arbitrary options to attach to the scanner. + + Raises: + ScannerError: If there is an error during scanning + """ + if not target: + raise ScannerError("No target specified") + if options: + self._options.update(options) + + def _consume_output(self, p, q): + line_count = 0 + while p.poll() is None: + line = p.stdout.readline() + q.put(line) + line_count += 1 + + def _run_subprocess(self, command: List[str]) -> None: + """Run a subprocess with the given command. + + Args: + command: Command to execute as list of strings + + Raises: + ScannerError: If subprocess execution fails + """ + + try: + program_output = [] + line_count = 0 + with subprocess.Popen( + command, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True + ) as process: + queue = SimpleQueue() + t = Thread(target=self._consume_output, args=(process, queue)) + t.start() + t.join() + process.terminate() + while not queue.empty(): + program_output.append(queue.get()) + line_count += 1 + self._output = program_output + return self._output + except subprocess.CalledProcessError as e: + raise ScannerError(f"Scanner {self._name} failed: {e.stderr}") from e + except Exception as e: + raise ScannerError( + f"Exception running scanner {self._name}: {str(e)}" + ) from e diff --git a/src/automated_security_helper/models/security_vulnerability.py b/src/automated_security_helper/models/security_vulnerability.py new file mode 100644 index 0000000..c355ff0 --- /dev/null +++ b/src/automated_security_helper/models/security_vulnerability.py @@ -0,0 +1,165 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Models for security vulnerability findings.""" + +from typing import List, Optional, Dict, Annotated +import re +from pydantic import Field, field_validator +from automated_security_helper.models.core import BaseFinding +from automated_security_helper.models.data_interchange import SecurityReport + + +class SecurityVulnerability(BaseFinding): + """Model representing a security vulnerability finding.""" + + vulnerability_type: Annotated[ + str, + Field( + description="Type of vulnerability (e.g., secret_exposure, sql_injection)", + ), + ] = None + cwe_id: Annotated[ + Optional[str], + Field(description="Common Weakness Enumeration ID"), + ] = None + cwe_link: Annotated[ + Optional[str], + Field(description="Link for the CWE description, if available"), + ] = None + cvss_score: Annotated[ + Optional[float], + Field(description="Common Vulnerability Scoring System score"), + ] = None + remediation_steps: Annotated[ + List[str], + Field(description="Steps to remediate the vulnerability"), + ] = [] + references: Annotated[ + List[str], + Field(description="Related documentation, articles, etc."), + ] = [] + + @field_validator("cwe_id") + @classmethod + def validate_cwe_id(cls, v: Optional[str]) -> Optional[str]: + """Validate CWE ID format.""" + if v is not None and not re.match(r"^CWE-\d+$", v): + raise ValueError("CWE ID must be in format 'CWE-'") + return v + + @field_validator("cvss_score") + @classmethod + def validate_cvss_score(cls, v: Optional[float]) -> Optional[float]: + """Validate CVSS score range.""" + if v is not None and not (0.0 <= v <= 10.0): + raise ValueError("CVSS score must be between 0.0 and 10.0") + return v + + +class SecurityVulnerabilityReport(SecurityReport): + """A report containing multiple security vulnerabilities.""" + + findings: Annotated[ + List[SecurityVulnerability], + Field( + default_factory=list, description="List of security vulnerabilities found" + ), + ] + scan_type: Annotated[str, Field(description="Type of security scan")] = ( + "vulnerability" + ) + risk_metrics: Annotated[ + Dict[str, float], + Field( + default_factory=lambda: { + "total_cvss": 0.0, + "avg_cvss": 0.0, + "max_cvss": 0.0, + }, + description="Risk metrics calculated from vulnerability CVSS scores", + ), + ] + + def model_post_init(self, __context) -> None: + """Post initialization validation and setup.""" + super().model_post_init(__context) + if not hasattr(self, "name"): + self.name = "Security Vulnerability Report" + if not hasattr(self, "risk_metrics"): + self.risk_metrics = {"total_cvss": 0.0, "avg_cvss": 0.0, "max_cvss": 0.0} + self.calculate_risk_metrics() + + def calculate_risk_metrics(self) -> None: + """Calculate risk metrics based on vulnerabilities.""" + if not self.findings: + self.risk_metrics = {"total_cvss": 0.0, "avg_cvss": 0.0, "max_cvss": 0.0} + return + + cvss_scores = [v.cvss_score for v in self.findings if v.cvss_score is not None] + if not cvss_scores: + self.risk_metrics = {"total_cvss": 0.0, "avg_cvss": 0.0, "max_cvss": 0.0} + return + + total_cvss = sum(cvss_scores) + avg_cvss = total_cvss / len(cvss_scores) + max_cvss = max(cvss_scores) + + self.risk_metrics = { + "total_cvss": round(total_cvss, 2), + "avg_cvss": round(avg_cvss, 2), + "max_cvss": round(max_cvss, 2), + } + + # Update findings list for SecurityReport base class + self.findings = self.findings.copy() + + # Update metadata summary stats + if hasattr(self, "metadata") and hasattr(self.metadata, "summary_stats"): + self.metadata.summary_stats.update( + { + "total_vulnerabilities": len(self.findings), + "critical": len( + [v for v in self.findings if v.severity == "CRITICAL"] + ), + "high": len([v for v in self.findings if v.severity == "HIGH"]), + "medium": len([v for v in self.findings if v.severity == "MEDIUM"]), + "low": len([v for v in self.findings if v.severity == "LOW"]), + "info": len([v for v in self.findings if v.severity == "INFO"]), + } + ) + + def add_vulnerability(self, vulnerability: SecurityVulnerability) -> None: + """Add a vulnerability to the report.""" + self.findings.append(vulnerability) + + def get_vulnerabilities_by_severity( + self, severity: str + ) -> List[SecurityVulnerability]: + """Get all vulnerabilities of a specific severity.""" + return [v for v in self.findings if v.severity == severity] + + def get_highest_cvss_score(self) -> Optional[float]: + """Get the highest CVSS score among all vulnerabilities.""" + scores = [v.cvss_score for v in self.findings if v.cvss_score is not None] + return max(scores) if scores else None + + def summary(self) -> dict: + """Generate a summary of the vulnerability report.""" + severity_counts = {} + total_cvss = 0 + cvss_count = 0 + + for vuln in self.findings: + severity_counts[vuln.severity] = severity_counts.get(vuln.severity, 0) + 1 + if vuln.cvss_score is not None: + total_cvss += vuln.cvss_score + cvss_count += 1 + + return { + "total_vulnerabilities": len(self.findings), + "severity_distribution": severity_counts, + "average_cvss_score": total_cvss / cvss_count if cvss_count > 0 else 0, + "scan_timestamp": self.timestamp, + "project_name": self.project_name, + } diff --git a/src/automated_security_helper/models/static_analysis.py b/src/automated_security_helper/models/static_analysis.py new file mode 100644 index 0000000..75cbb62 --- /dev/null +++ b/src/automated_security_helper/models/static_analysis.py @@ -0,0 +1,41 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Models for Static Analysis findings.""" + +from typing import Annotated, List +from pydantic import Field + +# Import base classes using relative imports +from automated_security_helper.models.core import BaseFinding +from automated_security_helper.models.data_interchange import SecurityReport + + +class StaticAnalysisFinding(BaseFinding): + """Model for static analysis security findings.""" + + source_file: Annotated[ + str, Field(description="Source file where the issue was found") + ] + line_number: Annotated[ + int, Field(description="Line number where the issue was found") + ] = None + code_snippet: Annotated[str, Field(description="Relevant code snippet")] = None + remediation_advice: Annotated[ + str, Field(description="Suggested fix for the issue") + ] = None + + +class StaticAnalysisReport(SecurityReport): + """Container for static analysis findings.""" + + findings: Annotated[List[StaticAnalysisFinding], Field()] = [] + + def group_findings_by_file(self): + """Group findings by source file.""" + grouped_findings = {} + for finding in self.findings: + if finding.source_file not in grouped_findings: + grouped_findings[finding.source_file] = [] + grouped_findings[finding.source_file].append(finding) + return grouped_findings diff --git a/src/automated_security_helper/models/validation.py b/src/automated_security_helper/models/validation.py new file mode 100644 index 0000000..4677e55 --- /dev/null +++ b/src/automated_security_helper/models/validation.py @@ -0,0 +1,266 @@ +"""Configuration validation framework for ASH.""" + +from typing import Any, Dict, List, Optional, Type, Union + +from pydantic import BaseModel, ValidationError + +from automated_security_helper.config.config import ParserConfig, ScannerPluginConfig + + +class ConfigurationValidator: + """Validates ASH configurations.""" + + VALID_SCANNER_TYPES = ("SAST", "DAST", "SBOM", "CONTAINER", "IAC", "DEPENDENCY") + + @staticmethod + def _normalize_scanner_type(scanner_type: str) -> str: + """Convert legacy scanner types to their modern equivalents.""" + if not isinstance(scanner_type, str): + return scanner_type + scanner_type = str(scanner_type).upper().strip() + # Handle legacy types + if scanner_type == "STATIC": + return "SAST" + return scanner_type + + @staticmethod + def _is_valid_scanner_type(scanner_type: str) -> bool: + """Check if scanner type is valid after normalization.""" + try: + if not scanner_type or not isinstance(scanner_type, str): + return False + normalized = ConfigurationValidator._normalize_scanner_type(scanner_type) + return normalized in ConfigurationValidator.VALID_SCANNER_TYPES + except Exception: + return False + + @staticmethod + def _validate_scanner_type( + scanner_type: Any, + ) -> tuple[bool, Optional[str], Optional[str]]: + """Validate and normalize a scanner type. + + Returns: + Tuple of (is_valid, error_message, normalized_type) + """ + if not scanner_type: + return False, "Scanner type is required", None + + if not isinstance(scanner_type, str): + return False, f"Scanner type must be string, got {type(scanner_type)}", None + + # Normalize and validate type + scanner_type = str(scanner_type).strip().upper() + + # Handle legacy STATIC -> SAST mapping + if scanner_type == "STATIC": + scanner_type = "SAST" + + if scanner_type not in ConfigurationValidator.VALID_SCANNER_TYPES: + return ( + False, + f"Scanner type must be one of: {', '.join(ConfigurationValidator.VALID_SCANNER_TYPES)}", + None, + ) + + return True, None, scanner_type + + @staticmethod + def validate_config( + config: Union[Dict[str, Any], BaseModel], config_type: Type[BaseModel] + ) -> tuple[bool, Optional[str]]: + """Validate a configuration against its expected type. + + Args: + config: Configuration to validate as dict or BaseModel + config_type: Expected configuration type (e.g., ScannerConfig) + + Returns: + Tuple of (is_valid: bool, error_message: Optional[str]) + """ + try: + # Basic validation + if config is None or (isinstance(config, dict) and not config): + return False, "Empty configuration" + + if not isinstance(config, (dict, BaseModel)): + return False, f"Expected dict or BaseModel, got {type(config).__name__}" + + # Handle already validated models + if isinstance(config, BaseModel): + if not isinstance(config, config_type): + return ( + False, + f"Expected {config_type.__name__}, got {type(config).__name__}", + ) + return True, None + + # Dict validation + if not isinstance(config, dict): + return False, f"Expected dict, got {type(config).__name__}" + + # Check required name + if not config.get("name"): + return False, "Name is required" + + # Special handling for scanner configs + if config_type is ScannerPluginConfig: + return ConfigurationValidator.validate_scanner_config(config) + + # Regular validation + try: + config_type.model_validate(config) + return True, None + except ValidationError as e: + return False, str(e) + + except Exception as e: + return False, str(e) + + @staticmethod + def _normalize_config( + config: Union[Dict[str, Any], BaseModel], config_type: Type[BaseModel] = None + ) -> Dict[str, Any]: + """Convert config to dictionary format and normalize values. + + Args: + config: Configuration to normalize + config_type: Optional type information for special handling + + Returns: + Normalized configuration dictionary + """ + # Convert to dict + config_dict = ( + config.model_dump() if isinstance(config, BaseModel) else dict(config) + ) + + # Special handling for scanner configs + if config_type is ScannerPluginConfig and "type" in config_dict: + scanner_type = config_dict.get("type") + if isinstance(scanner_type, str): + scanner_type = scanner_type.strip().upper() + if scanner_type == "STATIC": + config_dict = dict(config_dict) # Make a copy + config_dict["type"] = "SAST" + elif scanner_type not in ConfigurationValidator.VALID_SCANNER_TYPES: + # Keep original for validation error + pass + else: + config_dict = dict(config_dict) + config_dict["type"] = scanner_type + + return config_dict + + @staticmethod + def validate_scanner_config( + config: Union[Dict[str, Any], ScannerPluginConfig], + ) -> tuple[bool, Optional[str]]: + """Validate scanner configuration. + + Args: + config: Scanner configuration to validate + + Returns: + Tuple of (is_valid: bool, error_message: Optional[str]) + """ + try: + # Basic validation + if not config: + return False, "Empty configuration" + + if isinstance(config, ScannerPluginConfig): + return True, None + + if not isinstance(config, dict): + return ( + False, + f"Expected dict or ScannerConfig, got {type(config).__name__}", + ) + + # Check required fields + if not config.get("name"): + return False, "Name is required" + + # Handle type normalization + if "type" in config: + scanner_type = config.get("type") + if scanner_type and isinstance(scanner_type, str): + scanner_type = str(scanner_type).strip().upper() + if scanner_type == "STATIC": + config = dict(config) + config["type"] = "SAST" + + # Validate config + try: + ScannerPluginConfig.model_validate(config) + return True, None + except ValidationError as e: + return False, f"Invalid scanner config: {str(e)}" + + except Exception as e: + return False, str(e) + + @staticmethod + def validate_parser_config( + config: Union[Dict[str, Any], ParserConfig], + ) -> tuple[bool, Optional[str]]: + """Validate parser configuration. + + Args: + config: Parser configuration to validate + + Returns: + Tuple of (is_valid: bool, error_message: Optional[str]) + """ + return ConfigurationValidator.validate_config(config, ParserConfig) + + @staticmethod + @staticmethod + def validate_configs( + configs: List[Union[Dict[str, Any], BaseModel]], config_type: Type[BaseModel] + ) -> List[tuple[bool, Optional[str]]]: + """Validate multiple configurations against their expected type. + + Args: + configs: List of configurations to validate + config_type: Expected configuration type for all configs + + Returns: + List of (is_valid: bool, error_message: Optional[str]) tuples + """ + if not configs: + return [(False, "Empty configuration list")] + + validator = ConfigurationValidator() + results = [] + + for config in configs: + if config is None or (isinstance(config, dict) and not config): + results.append((False, "Empty configuration")) + continue + + # Validate config + if config_type is ScannerPluginConfig: + results.append(validator.validate_scanner_config(config)) + else: + # Handle non-scanner configs + try: + if isinstance(config, dict): + if not config.get("name"): + results.append((False, "Name is required")) + continue + config_type.model_validate(config) + elif not isinstance(config, config_type): + results.append( + ( + False, + f"Invalid configuration type: {type(config).__name__}", + ) + ) + continue + results.append((True, None)) + except ValidationError as e: + results.append((False, str(e))) + + return results diff --git a/src/automated_security_helper/orchestrator.py b/src/automated_security_helper/orchestrator.py new file mode 100644 index 0000000..565d8a8 --- /dev/null +++ b/src/automated_security_helper/orchestrator.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python3 +"""Main entry point for ASH multi-scanner execution.""" + +import argparse +import json +import logging +import shutil +import sys +from pathlib import Path +from typing import Annotated, Any, Dict, List, Optional + +import yaml +from pydantic import BaseModel, ConfigDict, Field + +from automated_security_helper.config.default_config import DEFAULT_ASH_CONFIG +from automated_security_helper.execution_engine import ( + ExecutionStrategy, + ScanExecutionEngine, +) +from automated_security_helper.config.config import ASHConfig +from automated_security_helper.models.data_interchange import ExportFormat +from automated_security_helper.result_processor import ResultProcessor +from automated_security_helper.utils.get_scan_set import scan_set +from automated_security_helper.utils.log import ASH_LOGGER, get_logger + + +class ASHScanOrchestrator(BaseModel): + """Orchestrator class for ASH security scanning operations.""" + + model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow") + + source_dir: Annotated[Path, Field(..., description="Source directory to scan")] + output_dir: Annotated[Path, Field(..., description="Output directory for results")] + work_dir: Annotated[ + Path, Field(description="Working directory for scan operations") + ] = None + config: Annotated[ + ASHConfig | None, Field(description="The resolved ASH configuration") + ] = None + + strategy: Annotated[ + ExecutionStrategy, + Field(description="Whether to execute scanners in parallel or sequentially"), + ] = ExecutionStrategy.PARALLEL + scan_output_format: Annotated[ + List[ExportFormat], + Field(description="Output format for results", alias="scan_output_formats"), + ] = [ + ExportFormat.JSON, + ExportFormat.HTML, + ExportFormat.CSV, + ] + config_path: Annotated[ + Optional[Path], Field(None, description="Path to configuration file") + ] + verbose: Annotated[bool, Field(False, description="Enable verbose logging")] + debug: Annotated[bool, Field(False, description="Enable debug logging")] + offline: Annotated[bool, Field(False, description="Run in offline mode")] + no_run: Annotated[bool, Field(False, description="Only build container image")] + build_target: Annotated[ + str, Field("default", description="Build target for container image") + ] + enabled_scanners: Annotated[ + List[str], + Field( + description="List of enabled scanners. Defaults to all registered.", + ), + ] = [] + oci_runner: Annotated[str, Field("docker", description="OCI runner to use")] + no_cleanup: Annotated[ + bool, Field(False, description="Keep work directory after scan") + ] + metadata: Annotated[ + Dict[str, Any], + Field(default_factory=dict, description="Additional metadata for the scan"), + ] + + # Core components + result_processor: Annotated[ + ResultProcessor | None, Field(description="Result processor") + ] = None + execution_engine: Annotated[ScanExecutionEngine | None, Field()] = None + logger: Annotated[logging.Logger | None, Field()] = logging.Logger(name=__name__) + + def ensure_directories(self): + """Ensure required directories exist. + + Creates work_dir if it doesn't exist or if no_cleanup + is True, and output_dir if it doesn't exist. + """ + # Create work directory if it doesn't exist or if no_cleanup is True + self.logger.debug( + f"Creating work directory if it does not exist: {self.source_dir}" + ) + if not self.work_dir.exists() or self.no_cleanup: + # Remove existing work dir if no_cleanup is True to ensure clean state + if self.work_dir.exists() and self.no_cleanup: + shutil.rmtree(self.work_dir) + self.work_dir.mkdir(parents=True, exist_ok=True) + + # Create output directory if it doesn't exist + self.logger.debug( + f"Creating output directory if it does not exist: {self.source_dir}" + ) + if not self.output_dir.exists(): + self.output_dir.mkdir(parents=True, exist_ok=True) + + def model_post_init(self, context): + super().model_post_init(context) + self.logger = get_logger( + level=logging.DEBUG if self.verbose else logging.INFO, + ) + if self.work_dir is None: + self.work_dir = self.output_dir.joinpath("work") + self.result_processor = ResultProcessor( + logger=self.logger, + ) + self.scan_set = scan_set( + source=self.source_dir, + output=self.work_dir, + debug=self.verbose, + ) + self.config = self._load_config() + + self.execution_engine = ScanExecutionEngine( + source_dir=self.source_dir, + output_dir=self.output_dir, + strategy=self.strategy, + logger=self.logger, + enabled_scanners=self.enabled_scanners, + config=self.config, + ) + + def _get_logger(self) -> logging.Logger: + """Configure and return a logger instance.""" + return self.logger + + def _load_config(self) -> ASHConfig: + """Load configuration from file or return default configuration.""" + if not self.config_path: + self.logger.debug( + "No configuration file provided, using default configuration" + ) + return DEFAULT_ASH_CONFIG + + self.logger.debug(f"Loading configuration from {self.config_path}") + try: + with open(self.config_path, "r") as f: + if str(self.config_path).endswith(".json"): + config_data = json.load(f) + else: + config_data = yaml.safe_load(f) + + # Transform loaded data into ASHConfig + if isinstance(config_data, dict): + self.logger.debug("Transforming file config") + try: + # Validate and create ASHConfig from processed data + config = ASHConfig(**config_data) + self.logger.debug("Config transformed, returning config") + return config + + except Exception as e: + self.logger.warning(f"Failed to parse configuration: {e}") + return DEFAULT_ASH_CONFIG + return DEFAULT_ASH_CONFIG + + except Exception as e: + self.logger.warning( + f"Failed to load configuration from {self.config_path}: {e}. Using default configuration." + ) + return DEFAULT_ASH_CONFIG + + def execute_scan(self) -> Dict: + """Execute the security scan and return results.""" + self.logger.info("Starting ASH scan") + + try: + # Ensure required directories exist + self.ensure_directories() + # Load configuration + config = self._load_config() + + # Ensure we have ASHConfig instance after loading + if not isinstance(config, ASHConfig): + self.logger.warning("Invalid configuration format, using default") + config = DEFAULT_ASH_CONFIG + + # Create execution engine with default scanners + if not hasattr(self, "execution_engine"): + self.execution_engine = ScanExecutionEngine( + source_dir=self.source_dir, + output_dir=self.output_dir, + work_dir=self.work_dir, + strategy=ExecutionStrategy.PARALLEL, + # logger=self.logger, + ) + + # self.execution_engine._scanner_factory + + # Execute scanners with config + self.logger.info("Starting ASH execution engine") + results = self.execution_engine.execute(config) + + # Add empty results if none returned + results = results or {"scanners": {}} + return results + + except Exception as e: + self.logger.error(f"Error during scan execution: {e}") + raise + + +def parse_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser(description="ASH Multi-Scanner") + parser.add_argument( + "-s", "--source-dir", dest="source", help="Source directory to scan" + ) + parser.add_argument( + "-o", "--output-dir", dest="output", required=False, help="Output file path" + ) + parser.add_argument("-c", "--config", help="Path to configuration file") + parser.add_argument( + "-f", + "--format", + default="json", + help="Output format (default: json)", + choices=[ + "json", + "text", + "html", + "csv", + "yaml", + "junitxml", + "sarif", + "asff", + "cyclonedx", + "spdx", + ], + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Enable verbose logging" + ) + parser.add_argument("--debug", action="store_true", help="Enable debug logging") + parser.add_argument( + "--offline", + action="store_true", + help="Run in offline mode (skips NPM/PNPM/Yarn Audit checks)", + ) + parser.add_argument( + "--no-run", + action="store_true", + help="Only build the container image, do not run scans", + ) + parser.add_argument( + "--build-target", + default="default", + help="Specify build target for container image (e.g. 'ci' for elevated access)", + ) + parser.add_argument( + "--oci-runner", + default="docker", + help="Specify OCI runner to use (e.g. 'docker', 'finch')", + ) + parser.add_argument( + "--strategy", + default="sequential", + help="Whether to run scanners in parallel or sequential", + choices=[ + "sequential", + "parallel", + ], + ) + parser.add_argument( + "--scanners", + help="Specific scanner names to run", + ) + parser.add_argument( + "--no-cleanup", + action="store_true", + help="Keep working directory after scan completes", + ) + + return parser.parse_args() + + +def main(): + """Main entry point.""" + args = parse_args() + + try: + # Create orchestrator instance + source_dir = (Path(args.source) if args.source else Path().cwd()).absolute() + output_dir = ( + Path(args.output) if args.output else source_dir.joinpath("ash_output") + ).absolute() + enabled_scanners = (args.scanners).split(",") if args.scanners else [] + ASH_LOGGER.info(f"Enabled scanners: {enabled_scanners}") + orchestrator = ASHScanOrchestrator( + source_dir=source_dir, + output_dir=output_dir, + work_dir=output_dir.joinpath("work"), + scan_output_format=[ + ExportFormat.HTML, + ExportFormat.JSON, + ExportFormat.TEXT, + ExportFormat.YAML, + ExportFormat.JUNITXML, + ], + enabled_scanners=enabled_scanners, + config_path=Path(args.config) if args.config else None, + verbose=args.verbose or args.debug, + strategy=( + ExecutionStrategy.SEQUENTIAL + if args.strategy == "sequential" + else ExecutionStrategy.PARALLEL + ), + ) + + # Execute scan + results = orchestrator.execute_scan() + results["scanners"] = { + k: v.model_dump() if hasattr(v, "model_dump") else v + for k, v in results["scanners"].items() + } + + # Write results to output file + output_file = output_dir.joinpath( + f"ash_aggregated_results.{'yaml' if args.format == 'yaml' else 'json'}" + ) + if args.format == "yaml": + content = yaml.safe_dump(results) + else: + content = json.dumps(results, default=str) + with open(output_file, "w") as f: + f.write(content) + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/src/automated_security_helper/output_formatter.py b/src/automated_security_helper/output_formatter.py new file mode 100644 index 0000000..d0a78a0 --- /dev/null +++ b/src/automated_security_helper/output_formatter.py @@ -0,0 +1,55 @@ +"""Output formatter module for ASH. + +This module contains the OutputFormatter class and related formatters for: +- JSON output +- HTML output +- CSV output +""" + +import logging +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.data_interchange import ExportFormat +from automated_security_helper.reporters import ( + ASFFReporter, + CSVReporter, + CycloneDXReporter, + HTMLReporter, + JSONReporter, + JUnitXMLReporter, + SARIFReporter, + SPDXReporter, + TextReporter, + YAMLReporter, +) + + +class OutputFormatter: + """Main formatter class that manages different output formats.""" + + def __init__( + self, + logger: logging.Logger = logging.Logger(name=__name__), + ): + logger.info("Initializing OutputFormatter") + self.logger = logger + + self._formatters = { + "asff": ASFFReporter(), + "csv": CSVReporter(), + "cyclonedx": CycloneDXReporter(), + "dict": JSONReporter(), + "html": HTMLReporter(), + "json": JSONReporter(), + "junitxml": JUnitXMLReporter(), + "sarif": SARIFReporter(), + "spdx": SPDXReporter(), + "text": TextReporter(), + "yaml": YAMLReporter(), + } + + def format(self, model: ASHARPModel, output_format: ExportFormat) -> str: + """Format ASH model using specified formatter.""" + if f"{output_format}" not in self._formatters: + raise ValueError(f"Unsupported output format: {output_format}") + + return self._formatters[f"{output_format}"].format(model) diff --git a/src/automated_security_helper/reporters/__init__.py b/src/automated_security_helper/reporters/__init__.py new file mode 100644 index 0000000..07cb4ea --- /dev/null +++ b/src/automated_security_helper/reporters/__init__.py @@ -0,0 +1,25 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +from automated_security_helper.reporters.asff_reporter import ASFFReporter +from automated_security_helper.reporters.csv_reporter import CSVReporter +from automated_security_helper.reporters.cyclonedx_reporter import CycloneDXReporter +from automated_security_helper.reporters.html_reporter import HTMLReporter +from automated_security_helper.reporters.json_reporter import JSONReporter +from automated_security_helper.reporters.junitxml_reporter import JUnitXMLReporter +from automated_security_helper.reporters.sarif_reporter import SARIFReporter +from automated_security_helper.reporters.spdx_reporter import SPDXReporter +from automated_security_helper.reporters.text_reporter import TextReporter +from automated_security_helper.reporters.yaml_reporter import YAMLReporter + +__all__ = [ + "ASFFReporter", + "CSVReporter", + "CycloneDXReporter", + "HTMLReporter", + "JSONReporter", + "JUnitXMLReporter", + "SARIFReporter", + "SPDXReporter", + "TextReporter", + "YAMLReporter", +] diff --git a/src/automated_security_helper/reporters/asff_reporter.py b/src/automated_security_helper/reporters/asff_reporter.py new file mode 100644 index 0000000..1092543 --- /dev/null +++ b/src/automated_security_helper/reporters/asff_reporter.py @@ -0,0 +1,15 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import yaml + +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.interfaces import IOutputReporter + + +class ASFFReporter(IOutputReporter): + """Formats results as Amazon Security Finding Format (ASFF).""" + + def format(self, model: ASHARPModel) -> str: + """Format ASH model in Amazon Security Finding Format (ASFF).""" + # TODO - Replace with ASFF adapter + return yaml.dump(model.model_dump(), indent=2) diff --git a/src/automated_security_helper/reporters/csv_reporter.py b/src/automated_security_helper/reporters/csv_reporter.py new file mode 100644 index 0000000..5d6ec5b --- /dev/null +++ b/src/automated_security_helper/reporters/csv_reporter.py @@ -0,0 +1,23 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import csv +from io import StringIO + +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.interfaces import IOutputReporter + + +class CSVReporter(IOutputReporter): + """Formats results as CSV.""" + + def format(self, model: ASHARPModel) -> str: + """Format ASH model as CSV string.""" + output = StringIO() + writer = csv.writer(output) + + # Write headers + writer.writerow(["Finding ID", "Severity", "Description", "Location"]) + + # TODO: Implement CSV row writing based on findings + + return output.getvalue() diff --git a/src/automated_security_helper/reporters/cyclonedx_reporter.py b/src/automated_security_helper/reporters/cyclonedx_reporter.py new file mode 100644 index 0000000..a632628 --- /dev/null +++ b/src/automated_security_helper/reporters/cyclonedx_reporter.py @@ -0,0 +1,15 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import yaml + +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.interfaces import IOutputReporter + + +class CycloneDXReporter(IOutputReporter): + """Formats results as CycloneDX.""" + + def format(self, model: ASHARPModel) -> str: + """Format ASH model in CycloneDX.""" + # TODO - Replace with CycloneDX adapter + return yaml.dump(model.model_dump(), indent=2) diff --git a/src/automated_security_helper/reporters/html_reporter.py b/src/automated_security_helper/reporters/html_reporter.py new file mode 100644 index 0000000..94b33c7 --- /dev/null +++ b/src/automated_security_helper/reporters/html_reporter.py @@ -0,0 +1,172 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import html + +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.interfaces import IOutputReporter + + +class HTMLReporter(IOutputReporter): + """Formats results as HTML.""" + + def format(self, model: ASHARPModel) -> str: + """Format ASH model as HTML string with comprehensive styling and organization.""" + findings_by_severity = model.group_findings_by_severity() + findings_by_type = model.group_findings_by_type() + + findings_table = self._format_findings_table(model.findings) + severity_summary = self._format_severity_summary(findings_by_severity) + type_summary = self._format_type_summary(findings_by_type) + metadata_section = self._format_metadata(model.metadata) + + template = f""" + + + + ASH Results + + + +
    +

    Security Scan Results

    + +

    Summary

    +
    + {severity_summary} + {type_summary} +
    + +

    Detailed Findings

    + {findings_table} + +

    Scan Metadata

    + {metadata_section} +
    + + + """ + + return template + + def _format_severity_summary(self, findings_by_severity: dict) -> str: + """Format the severity summary section.""" + summary = "

    Findings by Severity

      " + for severity, findings in findings_by_severity.items(): + summary += f'
    • {severity}: {len(findings)} finding(s)
    • ' + summary += "
    " + return summary + + def _format_type_summary(self, findings_by_type: dict) -> str: + """Format the type summary section.""" + summary = "

    Findings by Type

      " + for type_name, findings in findings_by_type.items(): + summary += f"
    • {type_name}: {len(findings)} finding(s)
    • " + summary += "
    " + return summary + + def _format_findings_table(self, findings: list) -> str: + """Format the findings table.""" + if not findings: + return "

    No findings to display.

    " + + table = """ + + + + + + + + + """ + + for finding in findings: + finding_data = finding.model_dump() + severity_class = f"severity-{finding_data['severity'].lower()}" + + location = finding_data.get("location", {}) + location_str = ( + f"{location.get('path', 'N/A')}:{location.get('line', 'N/A')}" + ) + + table += f""" + + + + + + + + """ + + table += "
    SeverityTitleDescriptionLocationRule ID
    {html.escape(finding_data["severity"])}{html.escape(finding_data["title"])}{html.escape(finding_data["description"])}{html.escape(location_str)}{html.escape(finding_data["rule_id"])}
    " + return table + + def _format_metadata(self, metadata) -> str: + """Format the metadata section.""" + metadata_dict = metadata.model_dump() + formatted = "" + return formatted diff --git a/src/automated_security_helper/reporters/json_reporter.py b/src/automated_security_helper/reporters/json_reporter.py new file mode 100644 index 0000000..4d02395 --- /dev/null +++ b/src/automated_security_helper/reporters/json_reporter.py @@ -0,0 +1,13 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.interfaces import IOutputReporter + + +class JSONReporter(IOutputReporter): + """Formats results as JSON.""" + + def format(self, model: ASHARPModel) -> str: + """Format ASH model as JSON string.""" + return model.model_dump_json(indent=2, serialize_as_any=True) diff --git a/src/automated_security_helper/reporters/junitxml_reporter.py b/src/automated_security_helper/reporters/junitxml_reporter.py new file mode 100644 index 0000000..807938a --- /dev/null +++ b/src/automated_security_helper/reporters/junitxml_reporter.py @@ -0,0 +1,72 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import junit_xml + +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.interfaces import IOutputReporter + + +class JUnitXMLReporter(IOutputReporter): + """Formats results as JUnitXML.""" + + def format(self, model: ASHARPModel) -> str: + """Format ASH model in JUnitXML. + + Creates a test suite for each finding type, with individual findings as test cases. + Failed findings are represented as failed tests with appropriate error messages. + """ + all_test_suites = [] + grouped_findings = model.group_findings_by_type() + + for finding_type, findings in grouped_findings.items(): + test_cases = [] + for finding in findings: + # Create test case name from finding details + test_name = ( + f"{finding.name} [{finding.rule_id}]" + if finding.rule_id + else finding.name + ) + test_case = junit_xml.TestCase( + name=test_name, + classname=finding_type, + elapsed_sec=0, + timestamp=finding.detection_time.isoformat() + if finding.detection_time + else None, + ) + + # Add failure details for findings that need remediation + if finding.status != "resolved": + test_case.add_failure_info( + message=f"Security finding: {finding.severity} severity", + output=finding.description, + stdout="\n".join(finding.remediation_steps) + if hasattr(finding, "remediation_steps") + else finding.remediation, + ) + + # Add additional metadata in system-out + metadata = [] + if hasattr(finding, "vulnerability_type"): + metadata.append(f"Vulnerability Type: {finding.vulnerability_type}") + if hasattr(finding, "cwe_id") and finding.cwe_id: + metadata.append(f"CWE: {finding.cwe_id}") + if hasattr(finding, "cvss_score") and finding.cvss_score is not None: + metadata.append(f"CVSS Score: {finding.cvss_score}") + if metadata: + test_case.stdout = "\n".join(metadata) + + test_cases.append(test_case) + + # Create test suite for this finding type + test_suite = junit_xml.TestSuite( + name=finding_type, + test_cases=test_cases, + package=model.name, + timestamp=model.scan_time.isoformat() if model.scan_time else None, + ) + all_test_suites.append(test_suite) + + # Return the XML string representation of all test suites + return junit_xml.to_xml_string(all_test_suites) diff --git a/src/automated_security_helper/reporters/sarif_reporter.py b/src/automated_security_helper/reporters/sarif_reporter.py new file mode 100644 index 0000000..23df849 --- /dev/null +++ b/src/automated_security_helper/reporters/sarif_reporter.py @@ -0,0 +1,15 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import yaml + +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.interfaces import IOutputReporter + + +class SARIFReporter(IOutputReporter): + """Formats results as SARIF.""" + + def format(self, model: ASHARPModel) -> str: + """Format ASH model in SARIF.""" + # TODO - Replace with SARIF adapter + return yaml.dump(model.model_dump(), indent=2) diff --git a/src/automated_security_helper/reporters/spdx_reporter.py b/src/automated_security_helper/reporters/spdx_reporter.py new file mode 100644 index 0000000..0824c5b --- /dev/null +++ b/src/automated_security_helper/reporters/spdx_reporter.py @@ -0,0 +1,15 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import yaml + +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.interfaces import IOutputReporter + + +class SPDXReporter(IOutputReporter): + """Formats results as SPDX.""" + + def format(self, model: ASHARPModel) -> str: + """Format ASH model in SPDX.""" + # TODO - Replace with SPDX adapter + return yaml.dump(model.model_dump(), indent=2) diff --git a/src/automated_security_helper/reporters/text_reporter.py b/src/automated_security_helper/reporters/text_reporter.py new file mode 100644 index 0000000..3b60404 --- /dev/null +++ b/src/automated_security_helper/reporters/text_reporter.py @@ -0,0 +1,15 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import yaml + +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.interfaces import IOutputReporter + + +class TextReporter(IOutputReporter): + """Formats results as text.""" + + def format(self, model: ASHARPModel) -> str: + """Format ASH model as text string.""" + # TODO - Replace with aggregated_results.txt simulator + return yaml.dump(model.model_dump(), indent=2) diff --git a/src/automated_security_helper/reporters/yaml_reporter.py b/src/automated_security_helper/reporters/yaml_reporter.py new file mode 100644 index 0000000..c2a954a --- /dev/null +++ b/src/automated_security_helper/reporters/yaml_reporter.py @@ -0,0 +1,14 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +import yaml + +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.interfaces import IOutputReporter + + +class YAMLReporter(IOutputReporter): + """Formats results as YAML.""" + + def format(self, model: ASHARPModel) -> str: + """Format ASH model as YAML string.""" + return yaml.dump(model.model_dump(), indent=2) diff --git a/src/automated_security_helper/result_processor.py b/src/automated_security_helper/result_processor.py new file mode 100644 index 0000000..991ed36 --- /dev/null +++ b/src/automated_security_helper/result_processor.py @@ -0,0 +1,87 @@ +"""Result processor module for ASH. + +This module contains the ResultProcessor class which is responsible for: +1. Resolving appropriate parsers for scanner results +2. Implementing the parsing pipeline +3. Building ASH models from parsed results +""" + +import logging +from pathlib import Path +from typing import Any, Dict, List, Type, Union +from abc import ABC, abstractmethod +from automated_security_helper.models.asharp_model import ASHARPModel + + +class IResultParser(ABC): + """Interface for result parsers.""" + + @abstractmethod + def parse( + self, raw_results: str + ) -> Dict[str, Union[List[Dict[str, Any]], Dict[str, str]]]: + """Parse raw scanner results into a structured format.""" + pass + + +class ResultProcessor: + """Processes scanner results through parsing pipeline.""" + + def __init__( + self, + logger: logging.Logger = logging.Logger(name=__file__), + ): + logger.info("Initializing ResultProcessor") + self.logger = logger + self._parsers: Dict[str, Type[IResultParser]] = {} + + def register_parser(self, scanner_type: str, parser_class: Type[IResultParser]): + """Register a parser for a specific scanner type.""" + self._parsers[scanner_type] = parser_class + + def get_parser(self, scanner_type: str) -> IResultParser: + """Get the appropriate parser for a scanner type.""" + if scanner_type not in self._parsers: + raise ValueError(f"No parser registered for scanner type: {scanner_type}") + return self._parsers[scanner_type]() + + def process_results( + self, scanner_type: str, raw_results: Union[str, Dict[str, Any]] + ) -> ASHARPModel: + """Process raw scanner results through the parsing pipeline. + + Args: + scanner_type: Type of scanner that produced the results + raw_results: Raw results from scanner execution or path to JSON model + + Returns: + ASHARPModel: Processed security findings model + + Raises: + ValueError: If scanner type not provided when processing raw results + """ + if isinstance(raw_results, str) and raw_results.endswith(".json"): + # Load existing ASHARPModel from JSON + from .models.json_serializer import ASHARPModelSerializer + + return ASHARPModelSerializer.load_model(Path(raw_results)) + + if not scanner_type: + raise ValueError("Scanner type required when processing raw results") + + parser = self.get_parser(scanner_type) + parsed_results = parser.parse(raw_results) + model = self._build_ash_model(parsed_results) + return model + + def _build_ash_model( + self, parsed_results: Dict[str, Union[List[Dict[str, Any]], Dict[str, str]]] + ) -> ASHARPModel: + """Build an ASH model from parsed results.""" + model = ASHARPModel() + if "findings" in parsed_results: + findings: List[Dict[str, Any]] = parsed_results["findings"] + model.findings = findings + if "metadata" in parsed_results: + model.metadata = parsed_results["metadata"] + return model diff --git a/src/automated_security_helper/scanner_factory.py b/src/automated_security_helper/scanner_factory.py new file mode 100644 index 0000000..bca5fd0 --- /dev/null +++ b/src/automated_security_helper/scanner_factory.py @@ -0,0 +1,263 @@ +"""Module containing the ScannerFactory class for creating scanner instances.""" + +from pathlib import Path +import re +import logging +from typing import Callable, Dict, Optional, Type, Union +from importlib import import_module + +from automated_security_helper.config.config import ScannerPluginConfig +from automated_security_helper.models.scanner_plugin import ScannerPlugin +from automated_security_helper.scanners.bandit_scanner import BanditScanner +from automated_security_helper.scanners.cdk_nag_scanner import CDKNagScanner +from automated_security_helper.scanners.jupyter_scanner import JupyterScanner +from automated_security_helper.utils.log import ASH_LOGGER + +# Core scanners that must be available +_CORE_SCANNERS = [ + BanditScanner, + CDKNagScanner, + JupyterScanner, +] # Required always-available scanners + +# Configuration for optional scanners that will be loaded dynamically if available +_OPTIONAL_SCANNER_CONFIGS = { + "jupyter": { + "module_path": "automated_security_helper.scanners.jupyter_scanner", + "class_name": "JupyterScanner", + } +} + + +class ScannerFactory: + """Factory class for creating and configuring scanner instances.""" + + def __init__(self, logger: Optional[logging.Logger] = None) -> None: + """Initialize the scanner factory with empty scanner registry.""" + self.default_scanners = set() + self._scanners: Dict[ + str, Union[Type[ScannerPlugin], Callable[[], ScannerPlugin]] + ] = {} + self._register_default_scanners() + + def _register_default_scanners(self) -> None: + """Register the default set of scanners.""" + registered = set() # Track registered scanner names + + # First ensure core scanners are registered in scanner dict + for scanner_class in _CORE_SCANNERS: + scanner_name = scanner_class.__name__.lower().strip() + if scanner_name not in self._scanners: + # Register the scanner class directly first + self._scanners[scanner_name] = scanner_class + self.default_scanners.add(scanner_class) + registered.add(scanner_name) + + # Also register base name if 'scanner' suffix present + if scanner_name.endswith("scanner"): + base_name = scanner_name[:-7].strip() + if base_name and base_name not in self._scanners: + self._scanners[base_name] = scanner_class + registered.add(base_name) + + # Try to register optional scanners + for scanner_type, config in _OPTIONAL_SCANNER_CONFIGS.items(): + try: + module = import_module(config["module_path"]) + scanner_class = getattr(module, config["class_name"]) + scanner_name = scanner_class.__name__.lower().strip() + if scanner_name not in self._scanners: + self._scanners[scanner_name] = scanner_class + self.default_scanners.add(scanner_class) + registered.add(scanner_name) + except (ImportError, AttributeError): + # Skip if scanner module is not available + continue + + # Process all default scanners + for scanner_class in self.default_scanners: + try: + # Skip invalid scanner classes + if not issubclass(scanner_class, ScannerPlugin): + ASH_LOGGER.warning( + f"Invalid scanner class: {scanner_class.__name__}" + ) + continue + + # Check for required config + if not hasattr(scanner_class, "_default_config"): + ASH_LOGGER.warning( + f"Scanner {scanner_class.__name__} missing _default_config" + ) + continue + + config = scanner_class._default_config + if not hasattr(config, "name") or not config.name: + ASH_LOGGER.warning( + f"Scanner {scanner_class.__name__} missing required name in default config" + ) + continue + + scanner_name = scanner_class.__name__.lower().strip() + + # Only register if not already present + if scanner_name not in self._scanners: + self._scanners[scanner_name] = scanner_class + registered.add(scanner_name) + + # Also register base name if 'scanner' suffix present + if scanner_name.endswith("scanner"): + base_name = scanner_name[:-7].strip() + if base_name and base_name not in self._scanners: + self._scanners[base_name] = scanner_class + registered.add(base_name) + + except Exception as e: + ASH_LOGGER.warning( + f"Failed to register scanner {scanner_class.__name__}: {str(e)}" + ) + + @staticmethod + def _normalize_scanner_name(scanner_name: str) -> str: + """Normalize the scanner name for consistent lookup.""" + normalized_name = scanner_name.lower() + normalized_name = re.sub( + pattern=r"scannerconfig?$", + repl="", + string=normalized_name, + flags=re.IGNORECASE, + ) + return normalized_name + + def register_scanner( + self, + scanner_name: str, + scanner_input: Union[Type[ScannerPlugin], Callable[[], ScannerPlugin]], + ) -> None: + """Register a scanner with the factory. + + The scanner name will be normalized to lowercase. Both the original name and base + name without 'scanner' suffix (if present) will be registered. + + Args: + scanner_name: Name of scanner to register (will be normalized) + scanner_input: Scanner class or factory function to register + + Raises: + ValueError: If scanner name is empty or already registered + TypeError: If scanner input is not valid + """ + if not scanner_name: + raise ValueError("Scanner name cannot be empty") + + # Normalize scanner name + normalized_name = self._normalize_scanner_name(scanner_name) + + # Check for duplicate registration + if normalized_name in self._scanners: + raise ValueError(f"Scanner '{normalized_name}' is already registered") + + # Validate input + try: + if isinstance(scanner_input, type): + if not issubclass(scanner_input, ScannerPlugin): + raise TypeError("Scanner class must inherit from ScannerPlugin") + self._scanners[normalized_name] = scanner_input + elif callable(scanner_input): + # For factory functions, validate they return a proper scanner instance + test_instance = scanner_input() + if not isinstance(test_instance, ScannerPlugin): + raise TypeError("Scanner must be a ScannerPlugin instance") + self._scanners[normalized_name] = type(test_instance) + else: + raise TypeError("Invalid scanner input") + except Exception as e: + raise TypeError(f"Scanner '{normalized_name}' validation failed: {str(e)}") + + def create_scanner( + self, + scanner_name: str, + config: Optional[ScannerPluginConfig] = None, + source_dir: Optional[Path] = None, + output_dir: Optional[Path] = None, + logger: Optional[logging.Logger] = None, + ) -> ScannerPlugin: + """Create a scanner instance of the specified type with optional configuration. + + Args: + scanner_type: Type of scanner to create (name, class, config object, or dict) + config: Optional configuration for the scanner + + Returns: + An instance of the requested scanner type + + Raises: + ValueError: If scanner type is not registered + TypeError: If scanner type is invalid + """ + if scanner_name is None: + raise ValueError("Unable to determine scanner class") + + # Get scanner class if not already determined + scanner_class = None + if scanner_name: + normalized_name = self._normalize_scanner_name(scanner_name) + if normalized_name not in self._scanners: + raise ValueError("Unable to determine scanner class") + scanner_class = self._scanners[normalized_name] + + if not scanner_class: + raise ValueError("Unable to determine scanner class") + + if not config: + raise ValueError("No scanner config provided, unable to create scanner") + + # Create and configure scanner instance + instance = scanner_class( + source_dir=source_dir, + output_dir=output_dir, + ) + instance.configure(config) + + return instance + + def get_scanner_class(self, scanner_name: str) -> Type[ScannerPlugin]: + """Get the scanner class for a given name. + + Args: + scanner_name: Name of scanner to retrieve (will be normalized) + + Returns: + The scanner class + + Raises: + ValueError: If scanner_name is not registered + TypeError: If stored value is not a scanner class + """ + normalized_name = self._normalize_scanner_name(scanner_name) + if normalized_name not in self._scanners: + raise ValueError("Unable to determine scanner class") + scanner = self._scanners[normalized_name] + if not isinstance(scanner, type): + raise TypeError("Stored scanner must be a class") + return scanner + + def available_scanners(self) -> Dict[str, Type[ScannerPlugin]]: + """Get dictionary of all registered scanners. + + Returns: + Dictionary mapping scanner names to scanner classes + + Raises: + TypeError: If scanner class cannot be determined for any scanner + """ + result = {} + for name, scanner_input in self._scanners.items(): + try: + if isinstance(scanner_input, type): + result[name] = scanner_input + else: + result[name] = scanner_input.__class__ + except Exception as e: + raise TypeError(f"Failed to get class for scanner '{name}': {str(e)}") + return result diff --git a/src/automated_security_helper/scanners/__init__.py b/src/automated_security_helper/scanners/__init__.py new file mode 100644 index 0000000..04f8b7b --- /dev/null +++ b/src/automated_security_helper/scanners/__init__.py @@ -0,0 +1,2 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/src/automated_security_helper/scanners/bandit_scanner.py b/src/automated_security_helper/scanners/bandit_scanner.py new file mode 100644 index 0000000..0a08d20 --- /dev/null +++ b/src/automated_security_helper/scanners/bandit_scanner.py @@ -0,0 +1,204 @@ +"""Module containing the Bandit security scanner implementation.""" + +from importlib.metadata import version +import json +from datetime import datetime, timezone +import logging +from pathlib import Path +from typing import Any, Dict, Optional +from automated_security_helper.models.core import Location +from automated_security_helper.models.data_interchange import ( + ExportFormat, + ScanStatistics, +) +from automated_security_helper.models.security_vulnerability import ( + SecurityVulnerability, +) +from automated_security_helper.exceptions import ScannerError +from automated_security_helper.models.scanner_plugin import ( + ScannerPlugin, +) +from automated_security_helper.config.config import ( + ScannerPluginConfig, +) +from automated_security_helper.models.static_analysis import ( + StaticAnalysisReport, +) + + +class BanditScanner(ScannerPlugin): + """Implementation of a Python security scanner using Bandit. + + This scanner uses Bandit to perform static security analysis of Python code + and returns results in a structured format using the StaticAnalysisReport model. + """ + + _default_config = ScannerPluginConfig( + name="bandit", + type="SAST", + command="bandit", + output_arg="-o", + output_arg_position="before_args", + scan_path_arg="-r", + scan_path_arg_position="after_args", + format_arg="-f", + format_arg_value="json", + format_arg_position="before_args", + invocation_mode="directory", + get_tool_version_command=["bandit", "--version"], + output_stream="file", + enabled=True, + output_format="json", + ) + _output_format = ExportFormat.JSON + tool_version = version("bandit") + + def __init__( + self, + source_dir: Path, + output_dir: Path, + logger: Optional[logging.Logger] = logging.Logger(__name__), + ) -> None: + super().__init__(source_dir=source_dir, output_dir=output_dir, logger=logger) + + def configure( + self, + config: ScannerPluginConfig = None, + ) -> None: + """Configure the scanner with provided settings.""" + # Allow output format override through config + super().configure(config) + + def validate(self) -> bool: + """Verify scanner configuration and requirements.""" + self._is_valid = self._config is not None and self.tool_version is not None + return self._is_valid + + def scan( + self, target: str, options: Optional[Dict[str, Any]] = None + ) -> StaticAnalysisReport: + """Execute Bandit scan and return results. + + Args: + target: Path to scan + + Returns: + StaticAnalysisReport containing the scan findings and metadata + + Raises: + ScannerError: If the scan fails or results cannot be parsed + """ + try: + self._pre_scan(target, options) + except ScannerError as exc: + raise exc + + possible_config_paths = { + f"{self.source_dir}/.bandit": [ + "--ini", + f"{self.source_dir}/.bandit", + ], + f"{self.source_dir}/bandit.yaml": [ + "-c", + f"{self.source_dir}/bandit.yaml", + ], + f"{self.source_dir}/bandit.toml": [ + "-c", + f"{self.source_dir}/bandit.toml", + ], + } + + for conf_path, new_args in possible_config_paths.items(): + if Path(conf_path).exists(): + self._config.args.extend(new_args) + break + self._config.args.extend( + ['--exclude="*venv/*"', '--exclude=".venv/*"', "--severity-level=all"] + ) + + start_time = datetime.now() + + # Add config-specific args if provided + if self._config: + if "confidence" in self._config: + self._config.args.extend(["-l", self._config["confidence"]]) + if "severity" in self._config: + self._config.args.extend(["-i", self._config["severity"]]) + + try: + Path(self.results_file).parent.mkdir(exist_ok=True, parents=True) + final_args = self._resolve_arguments(target=target) + self.logger.debug(f"Running Bandit with args: {final_args}") + self._run_subprocess(final_args) + end_time = datetime.now() + scan_duration = (end_time - start_time).total_seconds() + self.logger.debug(f"Bandit completed in {scan_duration} seconds") + + self._output = self._parse_outputs(scan_duration=scan_duration) + return self._output + + except Exception as e: + # Check if there are useful error details + raise ScannerError(f"Bandit scan failed: {str(e)}") + + def _parse_outputs(self, *args, **kwargs): + # Parse Bandit JSON output + with open(self.results_file, "r") as f: + bandit_results = json.load(f) + + # Create findings list + findings = [] + for result in bandit_results.get("results", []): + finding = SecurityVulnerability( + id=result.get("filename") + "/" + result.get("test_id"), + location=Location( + file_path=result.get("filename", ""), + start_line=result.get("line_range", [0, 0])[0], + end_line=result.get("line_range", [0, 0])[1], + snippet=result.get("code", None), + ), + title=result.get("test_name", "Unknown Issue"), + description=" ".join( + [item for item in [result.get("issue_text", False)] if item] + ), + link=result.get("more_info", None), + cwe_id=result.get("issue_cwe", {}).get("id", None), + cwe_link=result.get("issue_cwe", {}).get("link", None), + severity=result.get("issue_severity", "UNKNOWN").upper(), + source_file=result.get("filename", None), + line_number=result.get("line_number", None), + code_snippet=result.get("code", None), + remediation_advice=result.get("more_info", None), + confidence=result.get("issue_confidence", "UNKNOWN").upper(), + # raw=result, + ) + findings.append(finding) + + # Create statistics + metrics = bandit_results.get("metrics", {}) + + # Count findings by severity + severity_counts = {} + for finding in findings: + severity = finding.severity + severity_counts[severity] = severity_counts.get(severity, 0) + 1 + + stats = ScanStatistics( + files_scanned=metrics.get("_totals", {}).get("loc", 0), + lines_of_code=metrics.get("_totals", {}).get("loc", 0), + total_findings=len(findings), + findings_by_type=severity_counts, + scan_duration_seconds=kwargs["scan_duration"], + ) + + # Create and return report + return StaticAnalysisReport( + name=self.name, + scanner_name="bandit", + scanners_used=[{"bandit": version("bandit")}], + project_name=self.name, + findings=findings, + statistics=stats, + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + scan_config=self._config, + ) diff --git a/src/automated_security_helper/scanners/cdk_nag_scanner.py b/src/automated_security_helper/scanners/cdk_nag_scanner.py new file mode 100644 index 0000000..02c43b6 --- /dev/null +++ b/src/automated_security_helper/scanners/cdk_nag_scanner.py @@ -0,0 +1,259 @@ +"""Module containing the CDK Nag security scanner implementation.""" + +from importlib.metadata import version +import logging +from datetime import datetime, timezone +import re +from typing import Dict, Any, List, Optional +import csv +from pathlib import Path + +from automated_security_helper.exceptions import ScannerError +from automated_security_helper.models.core import Location, Scanner +from automated_security_helper.models.data_interchange import ( + ExportFormat, + ReportMetadata, +) +from automated_security_helper.models.iac_scan import ( + IaCScanReport, + IaCVulnerability, +) +from automated_security_helper.models.scanner_plugin import ( + ScannerPlugin, +) +from automated_security_helper.config.config import ScannerPluginConfig +from automated_security_helper.utils.cdk_nag_wrapper import ( + run_cdk_nag_against_cfn_template, +) +from automated_security_helper.utils.get_scan_set import scan_set +from automated_security_helper.utils.log import ASH_LOGGER + + +class CDKNagScanner(ScannerPlugin): + """CDK Nag security scanner implementation.""" + + _default_config = ScannerPluginConfig( + name="cdknag", + enabled=True, + type="SAST", + command="cdk", + args=[ + "synth", + "--quiet", + ], + scan_path_arg="--context", + scan_path_arg_position="after_args", + invocation_mode="directory", + output_stream="file", + output_format=ExportFormat.CSV, + ) + + def __init__( + self, + source_dir: Path, + output_dir: Path, + logger: Optional[logging.Logger] = logging.Logger(__name__), + ) -> None: + super().__init__(source_dir=source_dir, output_dir=output_dir, logger=logger) + + def configure( + self, + config: ScannerPluginConfig = None, + ) -> None: + """Configure the scanner with provided settings.""" + super().configure(config) + + def validate(self) -> bool: + """Validate the scanner configuration and requirements. + + Returns: + True if validation passes, False otherwise + + Raises: + ScannerError: If validation fails + """ + # CDK Nag scanner is built into this Python module, if the Python import got + # this far then we know we're in a valid runtime for this scanner. + return True + + def scan( + self, target: str, options: Optional[Dict[str, Any]] = None + ) -> IaCScanReport: + """Scan the target and return findings. + + Args: + target: Path to scan. Can be a file or directory. + + Returns: + IaC scan report containing findings + + Raises: + ScannerError: If scanning fails + """ + try: + self._pre_scan(target, options) + except ScannerError as exc: + raise exc + target_path = Path(target) + if not target_path.exists(): + raise ScannerError(f"Target {target} does not exist") + + # Find all JSON/YAML files to scan from the scan set + cfn_files = scan_set( + source=self.source_dir, + output=self.output_dir, + # filter_pattern=r"\.(yaml|yml|json)$", + ) + ASH_LOGGER.debug( + f"Found {len(cfn_files)} files in scan set. Checking for possible CloudFormation templates" + ) + cfn_files = [ + f.strip() + for f in cfn_files + if ( + f.strip().endswith(".json") + or f.strip().endswith(".yaml") + or f.strip().endswith(".yml") + ) + ] + joined_files = "\n- ".join(cfn_files) + ASH_LOGGER.debug( + f"Found {len(cfn_files)} possible CloudFormation templates:\n- {joined_files}" + ) + + if len(cfn_files) == 0: + raise ScannerError(f"No CloudFormation templates found in {target}") + + # Process each template file + all_findings = [] + failed_files = [] + + for cfn_file in cfn_files: + try: + # Copy template to work dir with clean filename + outdir = self.output_dir.joinpath("scanners").joinpath("cdknag") + + # Run CDK synthesis for this file + nag_result = run_cdk_nag_against_cfn_template( + template_path=cfn_file, + nag_packs=[ + "AwsSolutionsChecks", + # "HIPAASecurityChecks", + # "NIST80053R4Checks", + # "NIST80053R5Checks", + # "PCIDSS321Checks", + ], + outdir=outdir, + ) + if nag_result is None: + ASH_LOGGER.debug(f"Not a CloudFormation file: {cfn_file}") + failed_files.append(cfn_file) + continue + + # Add findings from this file + # file_findings = self._parse_findings() + for pack_name, findings in nag_result.items(): + ASH_LOGGER.debug(f"Found {len(findings)} findings in {pack_name}") + all_findings.extend(findings) + except Exception as e: + failed_files.append((cfn_file, str(e))) + + # If any scans failed, include in report + # if failed_files: + # for f in failed_files: + # all_findings.append( + # IaCVulnerability( + # id=re.sub(pattern=r"\W+", repl="-", string=f[0].as_posix()), + # severity="CRITICAL", + # scanner=Scanner( + # name="cdk-nag", + # type="IAC", + # rule_id="CDK_NAG_SCAN_ERROR", + # ), + # resource_name=f[0].as_posix(), + # rule_id="CDK_NAG_SCAN_ERROR", + # title="Scan failures occurred", + # level="ERROR", + # description=f"Failed to scan file {f[0]}: {f[1]}", + # location=Location( + # file_path=f[0].as_posix(), + # ), + # ) + # ) + + return IaCScanReport( + name="CDK Nag", + iac_framework="CloudFormation", + scanners_used=[ + Scanner( + name="cdk-nag", + description="CDK Nag - AWS Solutions Rules applied against rendered CloudFormation templates.", + type="IAC", + version=version("cdk_nag"), + ), + ], + resources_checked={}, + scanner_name="cdk-nag", + template_path=str(target_path), + findings=all_findings, + template_format="CloudFormation", + metadata=ReportMetadata( + report_id=f"ASH-CDK-Nag-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%s')}", + tool_name="cdk-nag", + ), + ) + + def _parse_findings(self, specific_csv: str = None) -> List[IaCVulnerability]: + """Parse CDK nag findings from output CSV files. + + Returns: + List of IaC findings + """ + findings = [] + if specific_csv: + files = [specific_csv] + else: + files = self.work_dir.glob("*NagReport.csv") + for csv_file in files: + with open(csv_file, "r") as f: + reader = csv.DictReader(f) + for row in reader: + finding_id = re.sub( + pattern=r"\W+", + repl="-", + string=row.get("Rule ID", "UnknownRule"), + flags=re.IGNORECASE, + ) + finding = IaCVulnerability( + id=finding_id, + title=row.get("Rule ID", "Unknown Rule"), + description=row.get("Rule Info", ""), + resource_id=row.get("Resource ID", ""), + rule_id=row.get("Rule ID", ""), + resource_name=row.get("Resource ID", "NA").split("/")[-1], + compliance_frameworks=[ + "CDKNag.AwsSolutionsChecks", + ], + location=Location( + file_path=row.get("Resource ID", csv_file), + ), + severity=( + "CRITICAL" + if ( + row.get("Compliance", "Non-Compliant") + == "Non-Compliant" + and row.get("Rule Level", "Error") == "Error" + ) + else ( + "MEDIUM" + if ( + row.get("Compliance", "Non-Compliant") + == "Non-Compliant" + and row.get("Rule Level", "Error") != "Error" + ) + else "INFO" + ) + ), + ) + findings.append(finding) + return findings diff --git a/src/automated_security_helper/scanners/checkov_scanner.py b/src/automated_security_helper/scanners/checkov_scanner.py new file mode 100644 index 0000000..b301b83 --- /dev/null +++ b/src/automated_security_helper/scanners/checkov_scanner.py @@ -0,0 +1,206 @@ +"""Module containing the Checkov security scanner implementation.""" + +from importlib.metadata import version +import json +from datetime import datetime +import logging +from pathlib import Path +from typing import Any, Dict, List, Optional +from automated_security_helper.models.core import Location, Scanner +from automated_security_helper.models.data_interchange import ExportFormat +from automated_security_helper.models.iac_scan import ( + IaCScanReport, + IaCVulnerability, + CheckResultType, +) +from automated_security_helper.models.scanner_plugin import ( + ScannerPlugin, +) +from automated_security_helper.exceptions import ScannerError +from automated_security_helper.config.config import ScannerPluginConfig +from automated_security_helper.models.static_analysis import ( + StaticAnalysisReport, + ScanStatistics, +) + + +class CheckovScanner(ScannerPlugin): + """CheckovScanner implements IaC scanning using Checkov.""" + + _default_config = ScannerPluginConfig( + name="checkov", + type="IAC", + command="checkov", + output_arg="-o", + scan_path_arg="-r", + scan_path_arg_position="before_args", + format_arg="-f", + format_arg_value="json", + format_arg_position="before_args", + invocation_mode="directory", + output_stream="file", + enabled=True, + output_format="json", + ) + _output_format = ExportFormat.JSON + + def __init__( + self, + source_dir: Path, + output_dir: Path, + logger: Optional[logging.Logger] = logging.Logger(__name__), + ) -> None: + """Initialize the scanner. + + Args: + source_dir: Source directory to scan + output_dir: Output directory for results + logger: Optional logger instance + """ + super().__init__(source_dir=source_dir, output_dir=output_dir, logger=logger) + self._output_format = "json" + + def configure(self, config: ScannerPluginConfig = None) -> None: + """Configure the scanner with provided settings.""" + super().configure(config) + + def _create_finding_from_check( + self, result: Dict[str, Any], check_type: CheckResultType + ) -> IaCVulnerability: + """Create an IaCVulnerability from a check result.""" + finding_id = "/".join( + [ + item + for item in [ + result.get("check_id", None), + result.get("repo_file_path", None), + result.get("resource", None), + result.get("resource_address", None), + ] + if item + ] + ) + + # Extract location information from the result + file_path = result.get("file_path", "") + file_line_range = result.get("file_line_range", [0, 0]) + location = Location( + path=file_path, + start_line=file_line_range[0] if file_line_range else 0, + end_line=file_line_range[1] if file_line_range else 0, + ) + + return IaCVulnerability( + id=finding_id, + title=result.get("check_name", "Unknown Check"), + description=result.get("check_name", ""), + location=location, + resource_name=result.get("resource", ""), + resource_type=( + result.get("resource", "").split(".")[0] + if result.get("resource", "") + else None + ), + rule_id=result.get("check_id", ""), + check_result_type=check_type, + violation_details={ + "check_class": result.get("check_class", ""), + "guideline": result.get("guideline", ""), + "evaluated_keys": result.get("check_result", {}).get( + "evaluated_keys", [] + ), + "result_details": result.get("check_result", {}).get("result", ""), + "bc_category": result.get("bc_category", ""), + }, + ) + + def scan( + self, target: str, options: Optional[Dict[str, Any]] = None + ) -> StaticAnalysisReport: + """Execute Checkov scan and return results. + + Args: + target: Path to scan + + Returns: + StaticAnalysisReport containing the scan findings and metadata + + Raises: + ScannerError: If the scan fails or results cannot be parsed + """ + try: + self._pre_scan(target, options) + except ScannerError as exc: + raise exc + + try: + start_time = datetime.now() + final_args = self._resolve_arguments(target=target) + self._run_subprocess(final_args) + end_time = datetime.now() + scan_duration = (end_time - start_time).total_seconds() + + # Parse Checkov JSON output + checkov_results = json.loads("".join(self.output)) + results = checkov_results.get("results", {}) + + # Create findings list for all result types + findings: List[IaCVulnerability] = [] + + # Process failed checks + for result in results.get("failed_checks", []): + findings.append( + self._create_finding_from_check(result, CheckResultType.FAILED) + ) + + # Process passed checks + for result in results.get("passed_checks", []): + findings.append( + self._create_finding_from_check(result, CheckResultType.PASSED) + ) + + # Process skipped checks + for result in results.get("skipped_checks", []): + findings.append( + self._create_finding_from_check(result, CheckResultType.SKIPPED) + ) + + # Process parsing errors + for result in results.get("parsing_errors", []): + findings.append( + self._create_finding_from_check(result, CheckResultType.ERROR) + ) + + # Create statistics + metrics = checkov_results.get("metrics", {}) + + # Count findings by severity + severity_counts = {} + for finding in findings: + severity = finding.severity + severity_counts[severity] = severity_counts.get(severity, 0) + 1 + + stats = ScanStatistics( + files_scanned=metrics.get("_totals", {}).get("loc", 0), + lines_of_code=metrics.get("_totals", {}).get("loc", 0), + total_findings=len(findings), + findings_by_type=severity_counts, + scan_duration_seconds=scan_duration, + ) + + # Create and return report + return IaCScanReport( + name="checkov", + description="Checkov security scan report", + scanners_used=[ + Scanner(name="checkov", version=version("checkov"), type="IAC"), + ], + findings=findings, + statistics=stats, + scan_config=self._config, + ) + + except Exception as e: + # Check if there are useful error details + error_output = "".join(self.errors()) + raise ScannerError(f"Checkov scan failed: {str(e)}\nErrors: {error_output}") diff --git a/src/automated_security_helper/scanners/jupyter_scanner.py b/src/automated_security_helper/scanners/jupyter_scanner.py new file mode 100644 index 0000000..33d1ac9 --- /dev/null +++ b/src/automated_security_helper/scanners/jupyter_scanner.py @@ -0,0 +1,54 @@ +"""Module containing the JupyterScanner implementation.""" + +import logging +from pathlib import Path +from typing import Dict, Any, Optional + +from automated_security_helper.config.config import ScannerPluginConfig +from automated_security_helper.models.data_interchange import ExportFormat +from automated_security_helper.models.scanner_plugin import ScannerPlugin +from automated_security_helper.utils.get_ash_version import get_ash_version + + +class JupyterScanner(ScannerPlugin): + """Scanner implementation for Jupyter notebooks security scanning.""" + + _default_config = ScannerPluginConfig( + name="jupyter", + type="SAST", + command="self.scan_jupyter_notebooks()", + invocation_mode="file", + get_tool_version_command=get_ash_version, + enabled=True, + output_format="dict", + ) + _output_format = ExportFormat.DICT + tool_version = get_ash_version() + + def __init__( + self, + source_dir: Optional[Path] = None, + output_dir: Optional[Path] = None, + logger: Optional[logging.Logger] = None, + ) -> None: + """Initialize the scanner. + + Args: + source_dir: Source directory to scan + output_dir: Output directory for results + logger: Optional logger instance + """ + super().__init__(source_dir, output_dir, logger) + + def scan(self, source_path: Path, output_path: Path) -> Dict[str, Any]: + """Run security scan on Jupyter notebooks. + + Args: + source_path: Path to source code to scan. + output_path: Path where to store scan results. + + Returns: + Dict[str, Any]: Raw scan results. + """ + # Return empty results for now + return {"findings": [], "metadata": {}} diff --git a/src/automated_security_helper/schemas/ASHARPModel.json b/src/automated_security_helper/schemas/ASHARPModel.json new file mode 100644 index 0000000..de517fc --- /dev/null +++ b/src/automated_security_helper/schemas/ASHARPModel.json @@ -0,0 +1,373 @@ +{ + "$defs": { + "BaseFinding": { + "additionalProperties": true, + "description": "Base model for all security findings.", + "properties": { + "created_at": { + "default": null, + "description": "When the finding was first detected (in UTC)", + "title": "Created At", + "type": "string" + }, + "description": { + "default": null, + "description": "Detailed description of the finding", + "title": "Description", + "type": "string" + }, + "id": { + "description": "Unique identifier for the finding", + "minLength": 1, + "pattern": "^[A-Za-z][\\/\\.\\w-]+$", + "title": "Id", + "type": "string" + }, + "link": { + "default": null, + "description": "Link to more information about the finding", + "title": "Link", + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location", + "description": "Location information for the finding" + }, + "metadata": { + "additionalProperties": true, + "default": {}, + "description": "Additional scanner-specific metadata", + "title": "Metadata", + "type": "object" + }, + "raw": { + "default": null, + "description": "Raw result from the scanner", + "title": "Raw" + }, + "remediation": { + "default": null, + "description": "Guidance for fixing the finding", + "title": "Remediation", + "type": "string" + }, + "severity": { + "description": "Severity level of the finding", + "enum": [ + "CRITICAL", + "HIGH", + "MEDIUM", + "LOW", + "INFO" + ], + "title": "Severity", + "type": "string" + }, + "status": { + "default": "OPEN", + "description": "Current status of the finding", + "title": "Status", + "type": "string" + }, + "timestamp": { + "default": null, + "description": "When the finding was created", + "title": "Timestamp", + "type": "string" + }, + "title": { + "description": "Title or name of the finding", + "minLength": 1, + "title": "Title", + "type": "string" + }, + "updated_at": { + "default": null, + "description": "When the finding was last updated (in UTC)", + "title": "Updated At", + "type": "string" + } + }, + "required": [ + "id", + "title", + "severity", + "location" + ], + "title": "BaseFinding", + "type": "object" + }, + "Location": { + "description": "Represents the location of a finding in the codebase.", + "properties": { + "end_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Ending line number of the finding", + "title": "End Line" + }, + "file_path": { + "description": "Path to the file containing the finding", + "title": "File Path", + "type": "string" + }, + "snippet": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Code snippet related to the finding", + "title": "Snippet" + }, + "start_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Starting line number of the finding", + "title": "Start Line" + } + }, + "required": [ + "file_path" + ], + "title": "Location", + "type": "object" + }, + "ReportMetadata": { + "additionalProperties": true, + "description": "Metadata for security reports.", + "properties": { + "description": { + "default": null, + "description": "Description of the tool/scan", + "minLength": 1, + "title": "Description", + "type": "string" + }, + "generated_at": { + "default": null, + "title": "Generated At", + "type": "string" + }, + "project_name": { + "default": null, + "description": "Name of the project being scanned", + "minLength": 1, + "title": "Project Name", + "type": "string" + }, + "report_id": { + "default": null, + "description": "Unique identifier for the report", + "minLength": 1, + "pattern": "^[A-Za-z][\\/\\.\\w-]+$", + "title": "Report Id", + "type": "string" + }, + "summary_stats": { + "additionalProperties": { + "type": "integer" + }, + "default": { + "critical": 0, + "high": 0, + "info": 0, + "low": 0, + "medium": 0, + "total": 0 + }, + "description": "Summary statistics (e.g., count by severity)", + "title": "Summary Stats", + "type": "object" + }, + "tool_version": { + "default": null, + "description": "Version of the security tool", + "minLength": 1, + "title": "Tool Version", + "type": "string" + } + }, + "title": "ReportMetadata", + "type": "object" + }, + "ScanStatistics": { + "description": "Statistics for static analysis scan results.", + "properties": { + "files_scanned": { + "default": 0, + "description": "Total number of files scanned", + "title": "Files Scanned", + "type": "integer" + }, + "findings_by_type": { + "additionalProperties": true, + "default": {}, + "description": "Count of findings by severity level", + "title": "Findings By Type", + "type": "object" + }, + "lines_of_code": { + "default": 0, + "description": "Total number of lines of code", + "title": "Lines Of Code", + "type": "integer" + }, + "scan_duration_seconds": { + "default": 0.0, + "description": "Duration of scan in seconds", + "title": "Scan Duration Seconds", + "type": "number" + }, + "total_findings": { + "default": 0, + "description": "Total number of findings", + "title": "Total Findings", + "type": "integer" + } + }, + "title": "ScanStatistics", + "type": "object" + }, + "Scanner": { + "additionalProperties": true, + "description": "Represents metadata about the security scanner.", + "properties": { + "description": { + "default": null, + "description": "Description of the scanner", + "title": "Description", + "type": "string" + }, + "name": { + "description": "Name of the scanner", + "minLength": 1, + "pattern": "^[a-zA-Z][\\w-]*$", + "title": "Name", + "type": "string" + }, + "type": { + "default": "SAST", + "description": "Type of scanner (e.g., SAST, DAST, SBOM)", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + }, + "version": { + "default": "1.0.0", + "description": "Version of the scanner", + "title": "Version", + "type": "string" + } + }, + "required": [ + "name" + ], + "title": "Scanner", + "type": "object" + } + }, + "additionalProperties": true, + "description": "Main model class for parsing security scan reports from ASH tooling.\n\nThis model is the primary interface for handling aggregated security findings from\nvarious scanners. It supports both legacy scanner formats and the newer\nscanners_used format.\n\nExample:\n\n```python\nwith open('aggregated_results.json', 'r') as f:\n report = ASHARPModel.from_json(f.read())\nprint(f\"Found {len(report.findings)} security findings\")\nfor scanner in report.scanners_used:\n print(f\"Used scanner: {scanner['name']} v{scanner['version']}\")\n```", + "properties": { + "description": { + "default": "AWS Security Hub Aggregated Report", + "description": "Description of the report", + "title": "Description", + "type": "string" + }, + "findings": { + "description": "List of security findings from all scanners", + "items": { + "$ref": "#/$defs/BaseFinding" + }, + "title": "Findings", + "type": "array" + }, + "metadata": { + "$ref": "#/$defs/ReportMetadata" + }, + "name": { + "default": "ASHARP Report", + "description": "Name of the report", + "title": "Name", + "type": "string" + }, + "scan_type": { + "default": "security", + "description": "Type of security scan", + "title": "Scan Type", + "type": "string" + }, + "scanned_paths": { + "default": [], + "description": "The list of paths or path patterns that were scanned as part of this report.", + "items": { + "type": "string" + }, + "title": "Scanned Paths", + "type": "array" + }, + "scanners_used": { + "default": [], + "description": "List of scanners used in this report", + "items": { + "$ref": "#/$defs/Scanner" + }, + "title": "Scanners Used", + "type": "array" + }, + "statistics": { + "$ref": "#/$defs/ScanStatistics", + "default": { + "files_scanned": 0, + "findings_by_type": {}, + "lines_of_code": 0, + "scan_duration_seconds": 0.0, + "total_findings": 0 + } + }, + "timestamp": { + "default": null, + "description": "Timestamp of the export in UTC", + "title": "Timestamp", + "type": "string" + }, + "version": { + "default": "1.0", + "description": "Version of the export format", + "title": "Version", + "type": "string" + } + }, + "title": "ASHARPModel", + "type": "object" +} diff --git a/src/automated_security_helper/schemas/ASHConfig.json b/src/automated_security_helper/schemas/ASHConfig.json new file mode 100644 index 0000000..b5f22cc --- /dev/null +++ b/src/automated_security_helper/schemas/ASHConfig.json @@ -0,0 +1,1340 @@ +{ + "$defs": { + "BanditScannerConfig": { + "additionalProperties": true, + "description": "Bandit SAST scanner configuration.", + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "const": "bandit", + "default": "bandit", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Configure Bandit scanner" + }, + "type": { + "default": "SAST", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "BanditScannerConfig", + "type": "object" + }, + "BaseScannerOptions": { + "additionalProperties": true, + "description": "Base class for scanner options.", + "properties": {}, + "title": "BaseScannerOptions", + "type": "object" + }, + "BuildConfig": { + "additionalProperties": true, + "description": "Configuration model for build-time settings.", + "properties": { + "custom_scanners": { + "default": [], + "description": "Scanner configurations by type", + "items": { + "$ref": "#/$defs/ScannerPluginConfig" + }, + "title": "Custom Scanners", + "type": "array" + }, + "mode": { + "default": "ASH_MODE_ONLINE", + "description": "Build mode for the container", + "enum": [ + "ASH_MODE_ONLINE", + "ASH_MODE_OFFLINE" + ], + "title": "Mode", + "type": "string" + }, + "tool_install_scripts": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "default": {}, + "description": "Map of tool names to their installation scripts", + "title": "Tool Install Scripts", + "type": "object" + } + }, + "title": "BuildConfig", + "type": "object" + }, + "CdkNagPacks": { + "additionalProperties": true, + "properties": { + "AwsSolutionsChecks": { + "default": true, + "description": "Runs the AwsSolutionsChecks NagPack included with CDK Nag.", + "title": "Awssolutionschecks", + "type": "boolean" + }, + "HIPAASecurityChecks": { + "default": false, + "description": "Runs the HIPAASecurityChecks NagPack included with CDK Nag.", + "title": "Hipaasecuritychecks", + "type": "boolean" + }, + "NIST80053R4Checks": { + "default": false, + "description": "Runs the NIST80053R4Checks NagPack included with CDK Nag.", + "title": "Nist80053R4Checks", + "type": "boolean" + }, + "NIST80053R5Checks": { + "default": false, + "description": "Runs the NIST80053R5Checks NagPack included with CDK Nag.", + "title": "Nist80053R5Checks", + "type": "boolean" + }, + "PCIDSS321Checks": { + "default": false, + "description": "Runs the PCIDSS321Checks NagPack included with CDK Nag.", + "title": "Pcidss321Checks", + "type": "boolean" + } + }, + "title": "CdkNagPacks", + "type": "object" + }, + "CdkNagScannerConfig": { + "additionalProperties": true, + "description": "CDK Nag IAC SAST scanner configuration.", + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "const": "cdknag", + "default": "cdknag", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/CdkNagScannerConfigOptions", + "default": { + "nag_packs": { + "AwsSolutionsChecks": true, + "HIPAASecurityChecks": false, + "NIST80053R4Checks": false, + "NIST80053R5Checks": false, + "PCIDSS321Checks": false + } + }, + "description": "Configure CDK Nag scanner" + }, + "type": { + "default": "IAC", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "CdkNagScannerConfig", + "type": "object" + }, + "CdkNagScannerConfigOptions": { + "additionalProperties": true, + "description": "CDK Nag IAC SAST scanner options.", + "properties": { + "nag_packs": { + "$ref": "#/$defs/CdkNagPacks", + "default": { + "AwsSolutionsChecks": true, + "HIPAASecurityChecks": false, + "NIST80053R4Checks": false, + "NIST80053R5Checks": false, + "PCIDSS321Checks": false + }, + "description": "CDK Nag packs to enable" + } + }, + "title": "CdkNagScannerConfigOptions", + "type": "object" + }, + "CfnNagScannerConfig": { + "additionalProperties": true, + "description": "CFN Nag IAC SAST scanner configuration.", + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "const": "cfnnag", + "default": "cfnnag", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Enable CFN Nag IAC scanner" + }, + "type": { + "default": "IAC", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "CfnNagScannerConfig", + "type": "object" + }, + "CheckovScannerConfig": { + "additionalProperties": true, + "description": "Checkov SAST/IaC scanner configuration.", + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "const": "checkov", + "default": "checkov", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Configure Checkov scanner" + }, + "type": { + "default": "IAC", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "CheckovScannerConfig", + "type": "object" + }, + "CustomScannerConfig": { + "additionalProperties": true, + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "description": "The name of the custom scanner.", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Configure custom scanner" + }, + "type": { + "default": "CUSTOM", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "required": [ + "name" + ], + "title": "CustomScannerConfig", + "type": "object" + }, + "ExportFormat": { + "description": "Supported export formats.", + "enum": [ + "text", + "json", + "yaml", + "csv", + "html", + "dict", + "junitxml", + "sarif", + "asff", + "cyclonedx", + "spdx" + ], + "title": "ExportFormat", + "type": "string" + }, + "FileInvocationConfig": { + "additionalProperties": false, + "description": "Configuration for file scanning.", + "properties": { + "exclude": { + "default": [], + "description": "List of file patterns to exclude. Defaults to an empty list, which excludes no files.", + "examples": [ + "tests/" + ], + "items": { + "type": "string" + }, + "title": "Exclude", + "type": "array" + }, + "include": { + "default": [], + "description": "List of file patterns to include. Defaults to an empty list, which includes all files.", + "examples": [ + "**/*" + ], + "items": { + "type": "string" + }, + "title": "Include", + "type": "array" + } + }, + "title": "FileInvocationConfig", + "type": "object" + }, + "GitSecretsScannerConfig": { + "additionalProperties": true, + "description": "Git Secrets scanner configuration.", + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "const": "gitsecrets", + "default": "gitsecrets", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Enable Git Secrets scanner" + }, + "type": { + "default": "SECRETS", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "GitSecretsScannerConfig", + "type": "object" + }, + "GrypeScannerConfig": { + "additionalProperties": true, + "description": "Grype SAST scanner configuration.", + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "const": "grype", + "default": "grype", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Configure Grype scanner" + }, + "type": { + "default": "SAST", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "GrypeScannerConfig", + "type": "object" + }, + "JupyterNotebookScannerConfig": { + "additionalProperties": true, + "description": "Jupyter Notebook (.ipynb) SAST scanner configuration.", + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "const": "jupyter", + "default": "jupyter", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Configure Jupyter Notebook scanner" + }, + "type": { + "default": "SAST", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "JupyterNotebookScannerConfig", + "type": "object" + }, + "NpmAuditScannerConfig": { + "additionalProperties": true, + "description": "JS/TS Dependency scanner configuration.", + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "const": "npmaudit", + "default": "npmaudit", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Enable NPM/PNPM/Yarn Audit dependency scanner" + }, + "type": { + "default": "DEPENDENCY", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "NpmAuditScannerConfig", + "type": "object" + }, + "SASTScannerConfig": { + "additionalProperties": true, + "description": "Configuration model for SAST scanners.", + "properties": { + "output_formats": { + "default": [ + "text", + "json", + "junitxml", + "html" + ], + "description": "Format for SAST scan results output", + "items": { + "$ref": "#/$defs/ExportFormat" + }, + "title": "Output Formats", + "type": "array" + }, + "scanners": { + "$ref": "#/$defs/SASTScannerListConfig", + "default": { + "bandit": { + "enabled": true, + "name": "bandit", + "options": {}, + "type": "SAST" + }, + "cdknag": { + "enabled": true, + "name": "cdknag", + "options": { + "nag_packs": { + "AwsSolutionsChecks": true, + "HIPAASecurityChecks": false, + "NIST80053R4Checks": false, + "NIST80053R5Checks": false, + "PCIDSS321Checks": false + } + }, + "type": "IAC" + }, + "cfnnag": { + "enabled": true, + "name": "cfnnag", + "options": {}, + "type": "IAC" + }, + "checkov": { + "enabled": true, + "name": "checkov", + "options": {}, + "type": "IAC" + }, + "gitsecrets": { + "enabled": true, + "name": "gitsecrets", + "options": {}, + "type": "SECRETS" + }, + "grype": { + "enabled": true, + "name": "grype", + "options": {}, + "type": "SAST" + }, + "jupyter": { + "enabled": true, + "name": "jupyter", + "options": {}, + "type": "SAST" + }, + "npmaudit": { + "enabled": true, + "name": "npmaudit", + "options": {}, + "type": "DEPENDENCY" + }, + "semgrep": { + "enabled": true, + "name": "semgrep", + "options": {}, + "type": "SAST" + } + }, + "description": "SAST scanners to enable and their corresponding configurations." + } + }, + "title": "SASTScannerConfig", + "type": "object" + }, + "SASTScannerListConfig": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/$defs/CustomScannerConfig" + }, + { + "type": "boolean" + } + ] + }, + "properties": { + "bandit": { + "anyOf": [ + { + "$ref": "#/$defs/BanditScannerConfig" + }, + { + "type": "boolean" + } + ], + "default": { + "enabled": true, + "name": "bandit", + "options": {}, + "type": "SAST" + }, + "title": "Bandit" + }, + "cdknag": { + "anyOf": [ + { + "$ref": "#/$defs/CdkNagScannerConfig" + }, + { + "type": "boolean" + } + ], + "default": { + "enabled": true, + "name": "cdknag", + "options": { + "nag_packs": { + "AwsSolutionsChecks": true, + "HIPAASecurityChecks": false, + "NIST80053R4Checks": false, + "NIST80053R5Checks": false, + "PCIDSS321Checks": false + } + }, + "type": "IAC" + }, + "title": "Cdknag" + }, + "cfnnag": { + "anyOf": [ + { + "$ref": "#/$defs/CfnNagScannerConfig" + }, + { + "type": "boolean" + } + ], + "default": { + "enabled": true, + "name": "cfnnag", + "options": {}, + "type": "IAC" + }, + "title": "Cfnnag" + }, + "checkov": { + "anyOf": [ + { + "$ref": "#/$defs/CheckovScannerConfig" + }, + { + "type": "boolean" + } + ], + "default": { + "enabled": true, + "name": "checkov", + "options": {}, + "type": "IAC" + }, + "title": "Checkov" + }, + "gitsecrets": { + "anyOf": [ + { + "$ref": "#/$defs/GitSecretsScannerConfig" + }, + { + "type": "boolean" + } + ], + "default": { + "enabled": true, + "name": "gitsecrets", + "options": {}, + "type": "SECRETS" + }, + "title": "Gitsecrets" + }, + "grype": { + "anyOf": [ + { + "$ref": "#/$defs/GrypeScannerConfig" + }, + { + "type": "boolean" + } + ], + "default": { + "enabled": true, + "name": "grype", + "options": {}, + "type": "SAST" + }, + "title": "Grype" + }, + "jupyter": { + "anyOf": [ + { + "$ref": "#/$defs/JupyterNotebookScannerConfig" + }, + { + "type": "boolean" + } + ], + "default": { + "enabled": true, + "name": "jupyter", + "options": {}, + "type": "SAST" + }, + "title": "Jupyter" + }, + "npmaudit": { + "anyOf": [ + { + "$ref": "#/$defs/NpmAuditScannerConfig" + }, + { + "type": "boolean" + } + ], + "default": { + "enabled": true, + "name": "npmaudit", + "options": {}, + "type": "DEPENDENCY" + }, + "title": "Npmaudit" + }, + "semgrep": { + "anyOf": [ + { + "$ref": "#/$defs/SemgrepScannerConfig" + }, + { + "type": "boolean" + } + ], + "default": { + "enabled": true, + "name": "semgrep", + "options": {}, + "type": "SAST" + }, + "title": "Semgrep" + } + }, + "title": "SASTScannerListConfig", + "type": "object" + }, + "SBOMScannerConfig": { + "additionalProperties": true, + "description": "Configuration model for SBOM scanners.", + "properties": { + "output_formats": { + "default": [ + "json", + "html", + "cyclonedx" + ], + "description": "Format for SBOM scan results output", + "items": { + "enum": [ + "text", + "yaml", + "json", + "junitxml", + "html", + "cyclonedx", + "spdx" + ], + "type": "string" + }, + "title": "Output Formats", + "type": "array" + }, + "scanners": { + "$ref": "#/$defs/SBOMScannerListConfig", + "default": { + "syft": { + "enabled": true, + "name": "syft", + "options": {}, + "type": "SBOM" + } + }, + "description": "SBOM scanners to enable and their corresponding configurations." + } + }, + "title": "SBOMScannerConfig", + "type": "object" + }, + "SBOMScannerListConfig": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/$defs/CustomScannerConfig" + }, + { + "type": "boolean" + } + ] + }, + "properties": { + "syft": { + "anyOf": [ + { + "$ref": "#/$defs/SyftScannerConfig" + }, + { + "type": "boolean" + } + ], + "default": { + "enabled": true, + "name": "syft", + "options": {}, + "type": "SBOM" + }, + "title": "Syft" + } + }, + "title": "SBOMScannerListConfig", + "type": "object" + }, + "ScannerPluginConfig": { + "additionalProperties": true, + "description": "Configuration model for scanner plugins.", + "properties": { + "args": { + "default": [], + "description": "List of arguments to pass to the scanner command. Defaults to an empty list.", + "items": { + "type": "string" + }, + "title": "Args", + "type": "array" + }, + "command": { + "default": null, + "description": "The command to invoke the scanner, typically the binary or path to a script", + "title": "Command", + "type": "string" + }, + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "file_config": { + "$ref": "#/$defs/FileInvocationConfig", + "default": { + "exclude": [], + "include": [] + }, + "description": "Configuration for file scanning. Required if invocation_mode is 'file'." + }, + "format_arg": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Argument to pass the format option to when invoking the scanner command. Defaults to not including an arg for the format value, which results in the format option being passed to the scanner as a positional argument at the format_arg_position specified. If a value is provided, the value will be passed into the runtime args prior to the format option.", + "examples": [ + "--format", + "-f", + "--output-format" + ], + "title": "Format Arg" + }, + "format_arg_position": { + "default": "before_args", + "description": "Whether to place the format argument before or after the scanner command args. Defaults to 'before_args'.", + "enum": [ + "before_args", + "after_args" + ], + "title": "Format Arg Position", + "type": "string" + }, + "format_arg_value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "json", + "description": "Value to pass to the format argument when invoking the scanner command. Defaults to 'json', but typically is explicitly set in ScannerPlugin implementations as a frozen property.", + "examples": [ + "json", + "sarif", + "cyclonedx" + ], + "title": "Format Arg Value" + }, + "get_tool_version_command": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Command to run that should return the scanner version", + "title": "Get Tool Version Command" + }, + "invocation_mode": { + "default": "directory", + "description": "Whether to run the scanner on a directory or a file. Defaults to 'directory' to scan the entire directory. If set to 'file', uses the file_config values to identify the files to scan and scan each one individually.", + "enum": [ + "directory", + "file" + ], + "title": "Invocation Mode", + "type": "string" + }, + "name": { + "default": null, + "description": "Name of the component using letters, numbers, underscores and hyphens. Must begin with a letter.", + "minLength": 1, + "pattern": "^[a-zA-Z][\\w-]+$", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Scanner options" + }, + "output_arg": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Argument to pass the output option to when invoking the scanner command. Defaults to not including an arg for the output value, which results in the output option being passed to the scanner as a positional argument at the format_arg_position specified. If a value is provided, the value will be passed into the runtime args prior to the output option.", + "examples": [ + "--output", + "-o", + "--outfile" + ], + "title": "Output Arg" + }, + "output_arg_position": { + "default": "before_args", + "description": "Whether to place the output argument before or after the scanner command args. Defaults to 'before_args'.", + "enum": [ + "before_args", + "after_args" + ], + "title": "Output Arg Position", + "type": "string" + }, + "output_format": { + "anyOf": [ + { + "$ref": "#/$defs/ExportFormat" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Expected output format from the scanner itself." + }, + "output_stream": { + "default": "stdout", + "description": "Where to read scanner output from. Can be 'stdout', 'stderr' or 'file'. Defaults to 'stdout' to capture the output of the scanner directly.", + "enum": [ + "stdout", + "stderr", + "file" + ], + "title": "Output Stream", + "type": "string" + }, + "scan_path_arg": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Argument to pass the scan path to when invoking the scanner command. Defaults to not including an arg for the scan path value, which results in the path being passed to the scanner as a positional argument at the scan_path_arg_position specified. If the ", + "examples": [ + "-f", + "--file", + "-p", + "--path" + ], + "title": "Scan Path Arg" + }, + "scan_path_arg_position": { + "default": "after_args", + "description": "Whether to place the scan path argument before or after the scanner command args. Defaults to 'after_args'.", + "enum": [ + "before_args", + "after_args" + ], + "title": "Scan Path Arg Position", + "type": "string" + }, + "type": { + "default": "UNKNOWN", + "description": "Type of scanner. Valid options include: typing.Literal['CONTAINER', 'DAST', 'DEPENDENCY', 'IAC', 'SAST', 'SBOM', 'SECRETS', 'UNKNOWN', 'CUSTOM']", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "ScannerPluginConfig", + "type": "object" + }, + "SemgrepScannerConfig": { + "additionalProperties": true, + "description": "Semgrep SAST scanner configuration.", + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "const": "semgrep", + "default": "semgrep", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Configure Semgrep scanner" + }, + "type": { + "default": "SAST", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "SemgrepScannerConfig", + "type": "object" + }, + "SyftScannerConfig": { + "additionalProperties": true, + "description": "Syft SBOM scanner configuration.", + "properties": { + "enabled": { + "default": true, + "description": "Whether the component is enabled", + "title": "Enabled", + "type": "boolean" + }, + "name": { + "const": "syft", + "default": "syft", + "title": "Name", + "type": "string" + }, + "options": { + "$ref": "#/$defs/BaseScannerOptions", + "default": {}, + "description": "Configure Syft scanner" + }, + "type": { + "default": "SBOM", + "enum": [ + "CONTAINER", + "DAST", + "DEPENDENCY", + "IAC", + "SAST", + "SBOM", + "SECRETS", + "UNKNOWN", + "CUSTOM" + ], + "title": "Type", + "type": "string" + } + }, + "title": "SyftScannerConfig", + "type": "object" + } + }, + "additionalProperties": true, + "description": "Main configuration model for Automated Security Helper.", + "properties": { + "build": { + "$ref": "#/$defs/BuildConfig", + "default": { + "custom_scanners": [], + "mode": "ASH_MODE_ONLINE", + "tool_install_scripts": {} + }, + "description": "Build-time configuration settings" + }, + "fail_on_findings": { + "default": true, + "description": "Whether to exit with non-zero code if findings are detected", + "title": "Fail On Findings", + "type": "boolean" + }, + "ignore_paths": { + "default": [], + "description": "List of paths to ignore during scanning", + "items": { + "type": "string" + }, + "title": "Ignore Paths", + "type": "array" + }, + "max_concurrent_scanners": { + "default": 4, + "description": "Maximum number of scanners to run concurrently", + "minimum": 1, + "title": "Max Concurrent Scanners", + "type": "integer" + }, + "output_dir": { + "default": "ash_output", + "description": "Directory to store scan outputs", + "title": "Output Dir", + "type": "string" + }, + "project_name": { + "description": "Name of the project being scanned", + "title": "Project Name", + "type": "string" + }, + "sast": { + "$ref": "#/$defs/SASTScannerConfig", + "default": { + "output_formats": [ + "text", + "json", + "junitxml", + "html" + ], + "scanners": { + "bandit": { + "enabled": true, + "name": "bandit", + "options": {}, + "type": "SAST" + }, + "cdknag": { + "enabled": true, + "name": "cdknag", + "options": { + "nag_packs": { + "AwsSolutionsChecks": true, + "HIPAASecurityChecks": false, + "NIST80053R4Checks": false, + "NIST80053R5Checks": false, + "PCIDSS321Checks": false + } + }, + "type": "IAC" + }, + "cfnnag": { + "enabled": true, + "name": "cfnnag", + "options": {}, + "type": "IAC" + }, + "checkov": { + "enabled": true, + "name": "checkov", + "options": {}, + "type": "IAC" + }, + "gitsecrets": { + "enabled": true, + "name": "gitsecrets", + "options": {}, + "type": "SECRETS" + }, + "grype": { + "enabled": true, + "name": "grype", + "options": {}, + "type": "SAST" + }, + "jupyter": { + "enabled": true, + "name": "jupyter", + "options": {}, + "type": "SAST" + }, + "npmaudit": { + "enabled": true, + "name": "npmaudit", + "options": {}, + "type": "DEPENDENCY" + }, + "semgrep": { + "enabled": true, + "name": "semgrep", + "options": {}, + "type": "SAST" + } + } + }, + "description": "SAST scanner configuration" + }, + "sbom": { + "$ref": "#/$defs/SBOMScannerConfig", + "default": { + "output_formats": [ + "json", + "html", + "cyclonedx" + ], + "scanners": { + "syft": { + "enabled": true, + "name": "syft", + "options": {}, + "type": "SBOM" + } + } + }, + "description": "SBOM scanner configuration" + }, + "scan_paths": { + "default": [ + "." + ], + "description": "List of paths to scan", + "items": { + "type": "string" + }, + "title": "Scan Paths", + "type": "array" + }, + "severity_threshold": { + "default": "LOW", + "description": "Minimum severity level to report", + "title": "Severity Threshold", + "type": "string" + } + }, + "required": [ + "project_name" + ], + "title": "ASHConfig", + "type": "object" +} diff --git a/src/automated_security_helper/schemas/__init__.py b/src/automated_security_helper/schemas/__init__.py new file mode 100644 index 0000000..04f8b7b --- /dev/null +++ b/src/automated_security_helper/schemas/__init__.py @@ -0,0 +1,2 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/src/automated_security_helper/schemas/generate_schemas.py b/src/automated_security_helper/schemas/generate_schemas.py new file mode 100644 index 0000000..f635a17 --- /dev/null +++ b/src/automated_security_helper/schemas/generate_schemas.py @@ -0,0 +1,35 @@ +from pathlib import Path +from typing import Literal +from automated_security_helper.config.config import ASHConfig +from automated_security_helper.models.asharp_model import ASHARPModel +import json + + +def generate_schemas(output: Literal["file", "json", "dict"] = "file"): + """Generate JSON schemas for the models.""" + cur_file_path = Path(__file__) + # create schemas dir if not existing + schemas_dir = cur_file_path.parent + resp = {} + for model in [ASHConfig, ASHARPModel]: + json_schema_path = schemas_dir.joinpath(f"{model.__name__}.json").resolve() + schema = model.model_json_schema() + if output == "dict": + resp[model.__name__] = schema + elif output == "json": + resp[model.__name__] = json.dumps(schema, indent=2, sort_keys=True) + else: + resp = None + with open(json_schema_path, "w") as f: + json.dump(schema, f, indent=2, sort_keys=True) + # add final new line so pre-commit doesn't see changes on every run + f.writelines("\n") + return resp + + +def main(): + generate_schemas("file") + + +if __name__ == "__main__": + main() diff --git a/src/automated_security_helper/utils/__init__.py b/src/automated_security_helper/utils/__init__.py new file mode 100644 index 0000000..04f8b7b --- /dev/null +++ b/src/automated_security_helper/utils/__init__.py @@ -0,0 +1,2 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/src/automated_security_helper/utils/cdk_nag_wrapper.py b/src/automated_security_helper/utils/cdk_nag_wrapper.py new file mode 100644 index 0000000..fcc8811 --- /dev/null +++ b/src/automated_security_helper/utils/cdk_nag_wrapper.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python3 +import inspect +import os +import re +import shutil + +from pydantic import BaseModel, ConfigDict, Field +from cfn_tools import load_yaml + +from automated_security_helper.models.core import Location +from automated_security_helper.models.iac_scan import IaCVulnerability + +# noqa +os.environ["JSII_SILENCE_WARNING_DEPRECATED_NODE_VERSION"] = "1" + +import cdk_nag +import json +from pathlib import Path +from typing import Annotated, Any, Dict, List, Literal, Optional +from automated_security_helper.utils.log import ASH_LOGGER + +from aws_cdk import ( + App, + Aspects, + Stack, +) +from aws_cdk.assertions import ( + Match, + Annotations, +) +from aws_cdk.cloudformation_include import ( + CfnInclude, +) +from aws_cdk.cx_api import ( + SynthesisMessage, + SynthesisMessageLevel, +) + +from constructs import Construct + + +class CloudFormationResource(BaseModel): + model_config = ConfigDict( + extra="allow", + validate_assignment=True, + validate_default=True, + ) + + Type: Annotated[str, Field(pattern=r"^([a-zA-Z0-9:]+)$")] + Properties: Dict[str, Any] + + +class CloudFormationTemplateModel(BaseModel): + model_config = ConfigDict( + extra="allow", + validate_assignment=True, + validate_default=True, + ) + + AWSTemplateFormatVersion: Optional[Literal["2010-09-09"] | None] = None + Resources: Dict[str, CloudFormationResource] + + +class WrapperStack(Stack): + def __init__( + self, + scope: Construct | None = None, + id: str | None = None, + template_path: Path | None = None, + ): + super().__init__(scope, id) + # Get the relative path to use as the logical ID + # CDK will replace path separators with + try: + logical_id = ( + Path(template_path).absolute().relative_to(Path.cwd()).as_posix() + ) + except ValueError: + logical_id = Path(template_path).as_posix() + CfnInclude( + self, + id=logical_id, + template_file=Path(template_path).as_posix(), + ) + + +def get_model_from_template( + template_path: Path | None = None, +) -> CloudFormationTemplateModel | None: + if template_path is None: + return None + + with open(template_path, "r") as f: + template = load_yaml(f.read()) + + return CloudFormationTemplateModel.model_validate(template) + + +# Enumerate all classes in `cdk_nag`, identify any that extend `NagPack` +def get_nag_packs(): + nag_packs = {} + for item in dir(cdk_nag): + # get class from cdk_nag + pack = getattr(cdk_nag, item) + if inspect.isclass(pack) and issubclass(pack, cdk_nag.NagPack): + nag_packs[item] = { + "packType": pack, + } + return nag_packs + + +def run_cdk_nag_against_cfn_template( + template_path: Path, + nag_packs: List[ + Literal[ + "AwsSolutionsChecks", + "HIPAASecurityChecks", + "NIST80053R4Checks", + "NIST80053R5Checks", + "PCIDSS321Checks", + ] + ] = [ + "AwsSolutionsChecks", + ], + outdir: Path = None, +) -> Dict[str, List[IaCVulnerability]] | None: + results: Dict[str, List[dict]] = {} + + model = get_model_from_template(template_path) + if model is None: + ASH_LOGGER.debug( + "No model validated from template, skipping CDK Nag. This does not seem to be a valid CloudFormation template" + ) + return None + + ASH_LOGGER.debug(f"Validated model from template: {model}") + ASH_LOGGER.debug(f"outdir before check: {outdir.as_posix() if outdir else 'None'}") + if outdir is None: + outdir = ( + Path.cwd().joinpath("ash_output").joinpath("scanners").joinpath("cdknag") + ) + try: + clean_template_filename = Path(template_path).relative_to(Path.cwd()).as_posix() + except ValueError as e: + ASH_LOGGER.debug(f"Could not get relative path to template: {e}") + clean_template_filename = Path(template_path).as_posix() + except Exception as e: + ASH_LOGGER.debug(f"Could not get relative path to template: {e}") + ASH_LOGGER.debug(f"clean_template_filename: {clean_template_filename}") + clean_template_filename = re.sub( + r"(\/|\\|\.)+", "--", clean_template_filename.lstrip("/") + ) + ASH_LOGGER.debug(f"clean_template_filename: {clean_template_filename}") + ASH_LOGGER.debug(f"cdk nag outdir pre: {outdir.__str__()}") + outdir = outdir.joinpath(clean_template_filename) + ASH_LOGGER.debug(f"cdk nag outdir post: {outdir.__str__()}") + ASH_LOGGER.debug("Cleaning up outdir") + if outdir.exists(): + shutil.rmtree(outdir) + outdir.mkdir(parents=True, exist_ok=True) + ASH_LOGGER.debug("outdir cleaned, creating CDK wrapper app") + + app = App( + outdir=outdir.as_posix(), + ) + + nag_pack_lookup = get_nag_packs() + # nag_pack_lookup = { + # "AwsSolutionsChecks": { + # "pack": cdk_nag.AwsSolutionsChecks(), + # "packName": "AwsSolutions", + # }, + # "HIPAASecurityChecks": { + # "pack": cdk_nag.HIPAASecurityChecks(), + # "packName": "HIPAA.Security", + # }, + # "NIST80053R4Checks": { + # "pack": cdk_nag.NIST80053R4Checks(), + # "packName": "NIST.800.53.R4", + # }, + # "NIST80053R5Checks": { + # "pack": cdk_nag.NIST80053R5Checks(), + # "packName": "NIST.800.53.R5", + # }, + # "PCIDSS321Checks": { + # "pack": cdk_nag.PCIDSS321Checks(), + # "packName": "PCI.DSS.321", + # }, + # } + stack = WrapperStack( + app, + "ASHCDKNagScanner", + template_path=template_path, + ) + + # loggers: Sequence[MemoryLogger] = [MemoryLogger()] + for pack in nag_packs: + ASH_LOGGER.debug(f"Adding nag pack '{pack}'") + pack_type: type[cdk_nag.NagPack] = nag_pack_lookup[pack]["packType"] + pack_instance = pack_type( + # additional_loggers=loggers, + reports=True, + report_formats=[ + cdk_nag.NagReportFormat.JSON, + ], + ) + Aspects.of(stack).add(pack_instance) + + app.synth() + outdir = app.outdir + ASH_LOGGER.debug(f"outdir: {outdir}") + + # cfn_inc: CfnInclude = item in stack.node.children[0] + included = [item for item in stack.node.children if isinstance(item, CfnInclude)] + ASH_LOGGER.debug(json.dumps(included, default=str, indent=2)) + + results: Dict[str, List[IaCVulnerability]] = {} + + item: SynthesisMessage + for pack in nag_packs: + results[pack] = [] + pack_type: type[cdk_nag.NagPack] = nag_pack_lookup[pack]["packType"] + pack_name = pack_type().read_pack_name + ASH_LOGGER.debug(f"\n=== Pack '{pack_name}' Annotations ===") + cdk_nag_annotations = [ + *( + Annotations.from_stack(stack).find_info( + "*", + Match.string_like_regexp(rf"{re.escape(pack_name)}-.*"), + ) + ), + *( + Annotations.from_stack(stack).find_warning( + "*", + Match.string_like_regexp(rf"{re.escape(pack_name)}-.*"), + ) + ), + *( + Annotations.from_stack(stack).find_error( + "*", + Match.string_like_regexp(rf"{re.escape(pack_name)}-.*"), + ) + ), + ] + ASH_LOGGER.debug(f"Pack '{pack}' annotations: {len(cdk_nag_annotations)}") + ASH_LOGGER.debug( + f"Pack '{pack}' annotations: {json.dumps(cdk_nag_annotations, default=str, indent=2)}" + ) + for item in cdk_nag_annotations: + finding_ids: re.Match[str] | None = re.search( + rf"({re.escape(pack_name)}-\w+):", item.entry.data + ) + if finding_ids is not None: + finding_id = finding_ids.group(1) + + resource_log_id = item.id.split("/")[-1] + + finding = IaCVulnerability( + compliance_frameworks=[pack_name], + id=f"{item.id}/{finding_id}".lstrip("/"), + title=item.id.lstrip("/"), + rule_id=finding_id.lstrip("/"), + severity=( + "CRITICAL" + if item.level == SynthesisMessageLevel.ERROR + else ( + "MEDIUM" + if item.level == SynthesisMessageLevel.WARNING + else "INFO" + ) + ), + resource_name=resource_log_id, + resource_type=[ + item.Type + for resource_id, item in model.Resources.items() + if resource_id == resource_log_id + ][0], + description=item.entry.data, + location=Location( + file_path=Path(template_path).as_posix(), + ), + raw=dict( + id=item.id, + level=item.level, + entry=json.loads(json.dumps(item.entry.__dict__, default=str)), + ), + ) + results[pack].append(finding) + ASH_LOGGER.debug(f"Finding: {finding.model_dump_json(indent=2)}") + + jsonnable_res = {k: [item.model_dump() for item in v] for k, v in results.items()} + + with open( + Path(outdir).joinpath("IaCVulnerabilities_from_Annotations.json"), "w" + ) as f: + json.dump(jsonnable_res, f, default=str) + + return results + + +if __name__ == "__main__": + ASH_LOGGER.debug("Running cdk_nag against test template") + template_path = ( + Path(__file__) + .parent.parent.parent.parent.joinpath("tests") + .joinpath("test_data") + .joinpath("scanners") + .joinpath("cdk") + .joinpath("secure-s3-template") + .joinpath("secure-s3-template.yaml") + ) + res = run_cdk_nag_against_cfn_template( + template_path=template_path, + nag_packs=[ + "AwsSolutionsChecks", + "HIPAASecurityChecks", + "NIST80053R4Checks", + "NIST80053R5Checks", + "PCIDSS321Checks", + ], + outdir=Path(__file__) + .parent.parent.parent.parent.joinpath("ash_output") + .joinpath("scanners") + .joinpath("cdknag"), + ) + + # jsonnable_res = {k: [item.model_dump_json() for item in v] for k, v in res.items()} diff --git a/src/automated_security_helper/utils/get_ash_version.py b/src/automated_security_helper/utils/get_ash_version.py new file mode 100644 index 0000000..bcc6cd5 --- /dev/null +++ b/src/automated_security_helper/utils/get_ash_version.py @@ -0,0 +1,8 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +from importlib.metadata import version + + +def get_ash_version(): + return version("automated_security_helper") diff --git a/src/automated_security_helper/utils/get_scan_set.py b/src/automated_security_helper/utils/get_scan_set.py new file mode 100755 index 0000000..56559b8 --- /dev/null +++ b/src/automated_security_helper/utils/get_scan_set.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import re +import sys +from datetime import datetime +from typing import List, Optional +from pathspec import PathSpec +from pathlib import Path +import argparse +import os +from glob import glob + +ASH_INCLUSIONS = [ + ".git", + "**/cdk.out/asset.*", + "!**/*.template.json", # CDK output template default path pattern +] + + +def red(msg) -> str: + return "\033[91m{}\033[00m".format(msg) + + +def green(msg) -> str: + return "\033[92m{}\033[00m".format(msg) + + +def yellow(msg) -> str: + return "\033[33m{}\033[00m".format(msg) + + +def lightPurple(msg) -> str: + return "\033[94m{}\033[00m".format(msg) + + +def purple(msg) -> str: + return "\033[95m{}\033[00m".format(msg) + + +def cyan(msg) -> str: + return "\033[96m{}\033[00m".format(msg) + + +def gray(msg) -> str: + return "\033[97m{}\033[00m".format(msg) + + +def black(msg) -> str: + return "\033[98m{}\033[00m".format(msg) + + +def debug_echo(*msg, debug: bool = False) -> str: + if debug: + print( + yellow( + f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] [get-scan-set.py] DEBUG:" + ), + *msg, + file=sys.stderr, + ) + + +def get_ash_ignorespec_lines( + path, + ignorefiles: List[str] = [], + debug: bool = False, +) -> List[str]: + dotignores = [f"{path}/.ignore", *[item for item in glob(f"{path}/**/.ignore")]] + # ashignores = [ + # f"{path}/.ashignore", + # *[ + # item + # for item in glob(f"{path}/**/.ashignore") + # ] + # ] + gitignores = [ + f"{path}/.gitignore", + *[item for item in glob(f"{path}/**/.gitignore")], + ] + all_ignores = list( + set( + [ + *dotignores, + *gitignores, + # *ashignores, + *[f"{path}/{file}" for file in ignorefiles], + ] + ) + ) + lines = [] + for ignorefile in all_ignores: + if os.path.isfile(ignorefile): + clean = re.sub( + rf"^{re.escape(Path(path).as_posix())}", "${SOURCE_DIR}", ignorefile + ) + debug_echo(f"Found .ignore file: {clean}", debug=debug) + lines.append(f"######### START CONTENTS: {clean} #########") + with open(ignorefile) as f: + lines.extend(f.readlines()) + lines.append(f"######### END CONTENTS: {clean} #########") + lines.append("") + lines = [line.strip() for line in lines] + lines.append("######### START CONTENTS: ASH_INCLUSIONS #########") + lines.extend(ASH_INCLUSIONS) + lines.append("######### END CONTENTS: ASH_INCLUSIONS #########") + return lines + + +def get_ash_ignorespec( + lines: List[str], + debug: bool = False, +) -> PathSpec: + debug_echo("Generating spec from collected ignorespec lines", debug=debug) + spec = PathSpec.from_lines("gitwildmatch", lines) + return spec + + +def get_files_not_matching_spec( + path, + spec, + debug: bool = False, +): + full = [] + included = [] + for item in os.walk(path): + for file in item[2]: + full.append(os.path.join(item[0], file)) + inc_full = os.path.join(item[0], file) + clean = re.sub( + rf"^{re.escape(Path(path).as_posix())}", "${SOURCE_DIR}", inc_full + ) + if not spec.match_file(inc_full): + if "/node_modules/aws-cdk" not in inc_full: + debug_echo(f"Matched file for scan set: {clean}", debug=debug) + included.append(inc_full) + # elif '/.git/' not in inc_full: + # debug_echo(f"Ignoring file matching spec: {clean}", debug=debug) + included = sorted(set(included)) + return included + + +def parse_args() -> argparse.Namespace: + """Parse command line arguments. + + Returns: + Parsed command line arguments. + """ + parser = argparse.ArgumentParser( + description="Get list of files not matching .gitignore underneath SourceDir arg path" + ) + parser.add_argument("--source", help="path to scan", default=os.getcwd(), type=str) + parser.add_argument( + "--output", + help="output path to save the ash-ignore-report.txt and ash-scan-set-files-list.txt files to", + default=None, + type=str, + ) + parser.add_argument( + "--filter-pattern", + help="Filter results against a regular expression pattern. Defaults to returning empty which returns the full list of files to be included in the scan.", + default=None, + type=str, + ) + parser.add_argument( + "--ignorefile", + help="ignore file to use in addition to the standard gitignore", + default=[], + type=str, + nargs="*", + ) + parser.add_argument( + "--debug", help="Enables debug logging", action=argparse.BooleanOptionalAction + ) + return parser.parse_args() + + +def scan_set( + source: str = os.getcwd(), + output: Optional[str] = None, + ignorefile: Optional[list[str]] = None, + debug: bool = False, + print_results: bool = False, + filter_pattern: Optional[re.Pattern] = None, +) -> list[str]: + """Get list of files not matching .gitignore underneath source path. + + Args: + source: Path to scan. Defaults to current working directory. + output: Output path to save the ash-ignore-report.txt and ash-scan-set-files-list.txt files. + ignorefile: List of ignore files to use in addition to the standard gitignore. + debug: Enable debug logging. + print_results: Print results to stdout. Defaults to False for library usage. + filter_pattern: Filter results against a re.Pattern. Defaults to returning the full scan set. + + Returns: + List of files not matching ignore specifications. + """ + if ignorefile is None: + ignorefile = [] + + ashignore_content = None + ashscanset_list = None + ashignore_imported = False + ashscanset_imported = False + + if output: + ashignore_path = Path(output).joinpath("ash-ignore-report.txt") + ashscanset_path = Path(output).joinpath("ash-scan-set-files-list.txt") + if ashignore_path.exists(): + with open(ashignore_path) as f: + ashignore_content = f.readlines() + ashignore_imported = True + print( + cyan(f"Imported ash-ignore-report.txt from {output}"), + file=sys.stderr, + ) + if ashscanset_path.exists(): + with open(ashscanset_path) as f: + ashscanset_list = f.readlines() + ashscanset_imported = True + print( + cyan(f"Imported ash-scan-set-files-list.txt from {output}"), + file=sys.stderr, + ) + + if not ashignore_content: + ashignore_content = get_ash_ignorespec_lines(source, ignorefile, debug=debug) + + if not ashscanset_list: + spec = get_ash_ignorespec(ashignore_content, debug=debug) + ashscanset_list = get_files_not_matching_spec(source, spec, debug=debug) + + if output: + if not ashignore_imported: + debug_echo(f"Writing ash-ignore-report.txt to {output}", debug=debug) + if not ashignore_path.parent.exists(): + ashignore_path.parent.mkdir(parents=True) + with open(ashignore_path, "w") as f: + f.write("\n".join(ashignore_content)) + + if not ashscanset_imported: + debug_echo( + f"Writing ash-scan-set-files-list.txt to {output}", + debug=debug, + ) + if not ashscanset_path.parent.exists(): + ashscanset_path.parent.mkdir(parents=True) + with open(ashscanset_path, "w") as f: + f.write("\n".join(ashscanset_list)) + + if print_results: + for file in ashscanset_list: + print(file, file=sys.stdout) + + if filter_pattern: + ashscanset_list = [ + file + for file in ashscanset_list + if re.match(pattern=filter_pattern, string=file) + ] + + return ashscanset_list + + +def main() -> int: + """Main entry point for CLI usage. + + Returns: + Exit code (0 for success). + """ + args = parse_args() + + file_list = scan_set( + source=args.source, + output=args.output, + ignorefile=args.ignorefile, + debug=args.debug, + print_results=True, + ) + print(file_list, file=sys.stderr) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/automated_security_helper/utils/log.py b/src/automated_security_helper/utils/log.py new file mode 100644 index 0000000..5b2ca44 --- /dev/null +++ b/src/automated_security_helper/utils/log.py @@ -0,0 +1,36 @@ +import logging +from typing import Any + + +def get_logger(name: str = "ash", level: Any = None): + logger = logging.getLogger(name) + + if level is not None: + logger.setLevel(level) + logger.debug("Logger level set to: %s", level) + else: + logger.debug("Using existing logger level: %s", logger.level) + + formatter = logging.Formatter( + fmt="%(asctime)s - %(levelname)s - %(module)s - %(message)s" + ) + handler = logging.StreamHandler() + handler.setFormatter(formatter) + + if not logger.handlers: + handler = logging.StreamHandler() + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + handler.setFormatter(formatter) + logger.addHandler(handler) + return logger + + +ASH_LOGGER = get_logger( + name="ash", + # Default to debug when running scripts that only import ASH_LOGGER + # Otherwise if running through orchestrator.py, use level passed in from caller with + # default of INFO + level=logging.DEBUG, +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..04f8b7b --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,2 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..f6cfe6e --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,251 @@ +"""Common test fixtures for ASHARP tests.""" + +from pathlib import Path +import sys +import pytest +import yaml +from automated_security_helper.config.config import ( + ASHConfig, + BuildConfig, + SASTScannerConfig, + SASTScannerListConfig, + SBOMScannerConfig, + SBOMScannerListConfig, + ScannerPluginConfig, +) +from automated_security_helper.config.scanner_types import ( + BanditScannerConfig, + CdkNagPacks, + CdkNagScannerConfigOptions, + CfnNagScannerConfig, + CheckovScannerConfig, + GitSecretsScannerConfig, + JupyterNotebookScannerConfig, + NpmAuditScannerConfig, + BaseScannerOptions, + SemgrepScannerConfig, + CdkNagScannerConfig, + GrypeScannerConfig, + SyftScannerConfig, + CustomScannerConfig, +) + +from automated_security_helper.models.core import Location, Scanner +from automated_security_helper.models.security_vulnerability import ( + SecurityVulnerability, +) + + +TEST_DIR = Path(__file__).parent.joinpath("pytest-temp") +TEST_SOURCE_DIR = TEST_DIR.joinpath("source") +TEST_OUTPUT_DIR = TEST_DIR.joinpath("output") + + +def is_debugging(): + return "debugpy" in sys.modules + + +# enable_stop_on_exceptions if the debugger is running during a test +if is_debugging(): + + @pytest.hookimpl(tryfirst=True) + def pytest_exception_interact(call): + raise call.excinfo.value + + @pytest.hookimpl(tryfirst=True) + def pytest_internalerror(excinfo): + raise excinfo.value + + +@pytest.fixture +def test_source_dir() -> Path: + """Create a temporary source directory.""" + if not TEST_SOURCE_DIR.exists(): + TEST_SOURCE_DIR.mkdir(parents=True) + return TEST_SOURCE_DIR + + +@pytest.fixture +def test_output_dir() -> Path: + """Create a temporary output directory.""" + if not TEST_OUTPUT_DIR.exists(): + TEST_OUTPUT_DIR.mkdir(parents=True) + return TEST_OUTPUT_DIR + + +@pytest.fixture +def sample_config(): + return { + "scanners": {"bandit": {"type": "static", "config_file": "bandit.yaml"}}, + "parsers": {"bandit": {"format": "json"}}, + } + + +@pytest.fixture +def config_file(test_source_dir): + # Create a temporary config file + with open(test_source_dir.joinpath("config.yaml"), "w") as f: + yaml.dump( + { + "scanners": { + "bandit": {"type": "static", "config_file": "bandit.yaml"} + }, + "parsers": {"bandit": {"format": "json"}}, + }, + f, + ) + return f.name + + +@pytest.fixture +def base_location(): + """Create a base location instance for testing.""" + return Location(file_path="/path/to/file", start_line=10, end_line=5) + + +@pytest.fixture +def base_scanner(): + """Create a base scanner instance for testing.""" + return Scanner(name="base_scanner", version="1.0.0", type="SAST") + + +@pytest.fixture +def container_scanner(): + """Create a container scanner instance.""" + return Scanner( + name="container_scanner", + version="1.0.0", + type="CONTAINER", + ) + + +@pytest.fixture +def dependency_scanner(): + """Create a dependency scanner instance.""" + return Scanner( + name="dependency_scanner", + version="1.0.0", + type="DEPENDENCY", + ) + + +@pytest.fixture +def iac_scanner(): + """Create an IAC scanner instance.""" + return Scanner(name="iac_scanner", version="1.0.0", type="IAC") + + +# Legacy fixtures for backward compatibility +@pytest.fixture +def sample_scanner(): + """Create a sample scanner instance for testing.""" + return base_scanner() + + +@pytest.fixture +def sample_location(): + """Create a sample location instance for testing.""" + return base_location() + + +@pytest.fixture +def sample_vulnerability(sample_scanner, sample_location): + """Create a sample vulnerability instance for testing.""" + return SecurityVulnerability( + scanner=sample_scanner, + location=sample_location, + title="Test Vulnerability", + severity="HIGH", + description="A test vulnerability", + recommendation="Fix the vulnerability", + ) + + +@pytest.fixture +def ash_config() -> ASHConfig: + """Create a test ASHConfig object based on default ash.yaml settings.""" + conf = ASHConfig( + project_name="automated-security-helper", + build=BuildConfig( + mode="ASH_MODE_OFFLINE", + tool_install_scripts={ + "trivy": [ + "wget https://github.com/aquasecurity/trivy/releases/download/v0.61.0/trivy_0.61.0_Linux-64bit.deb", + "dpkg -i trivy_0.61.0_Linux-64bit.deb", + ] + }, + custom_scanners=[ + ScannerPluginConfig( + name="trivy-sast", + command="trivy", + args=["fs", "--format", "sarif"], + output_format="sarif", + output_stream="stdio", + get_tool_version_command=[ + "trivy", + "--version", + ], + format_arg="--format", + format_arg_value="sarif", + format_arg_position="before_args", + scan_path_arg_position="after_args", + invocation_mode="directory", + type="SAST", + ), + ScannerPluginConfig( + name="trivy-sbom", + command="trivy", + args=["fs", "--format", "cyclonedx"], + output_format="cyclonedx", + output_stream="stdio", + ), + ], + ), + fail_on_findings=True, + ignore_paths=["tests/**"], + output_dir="ash_output", + sast=SASTScannerConfig( + output_formats=["json", "csv", "junitxml", "html"], + scanners=SASTScannerListConfig( + bandit=BanditScannerConfig(), + cdknag=CdkNagScannerConfig( + enabled=True, + options=CdkNagScannerConfigOptions( + nag_packs=CdkNagPacks( + AwsSolutionsChecks=True, + HIPAASecurityChecks=True, + NIST80053R4Checks=True, + NIST80053R5Checks=True, + PCIDSS321Checks=True, + ), + ), + ), + cfnnag=CfnNagScannerConfig(), + checkov=CheckovScannerConfig(), + gitsecrets=GitSecretsScannerConfig( + options=BaseScannerOptions(enabled=True), + ), + jupyter=JupyterNotebookScannerConfig(), + grype=GrypeScannerConfig(), + npmaudit=NpmAuditScannerConfig(), + semgrep=SemgrepScannerConfig(), + trivysasy=CustomScannerConfig( + name="trivy-sast", + type="SAST", + custom=BaseScannerOptions(enabled=True), + ), + ), + ), + sbom=SBOMScannerConfig( + output_formats=["cyclonedx", "html"], + scanners=SBOMScannerListConfig( + syft=SyftScannerConfig(), + trivysbom=CustomScannerConfig( + name="trivy-sbom", + type="SBOM", + custom=BaseScannerOptions(enabled=True), + ), + ), + ), + ) + return conf diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..04f8b7b --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1,2 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/utils/cfn-to-cdk/cfn_to_cdk/__init__.py b/tests/models/__init__.py similarity index 100% rename from utils/cfn-to-cdk/cfn_to_cdk/__init__.py rename to tests/models/__init__.py diff --git a/tests/models/test_aggregation.py b/tests/models/test_aggregation.py new file mode 100644 index 0000000..d84f650 --- /dev/null +++ b/tests/models/test_aggregation.py @@ -0,0 +1,127 @@ +"""Unit tests for aggregation functionality.""" + +import pytest +from datetime import datetime, timedelta +from automated_security_helper.models.core import Location, Scanner, BaseFinding +from automated_security_helper.models.aggregation import ( + FindingAggregator, + TrendAnalyzer, +) + + +@pytest.fixture +def sample_finding(): + """Create a sample finding for testing.""" + scanner = Scanner(name="test_scanner", version="1.0.0", type="SAST") + location = Location(file_path="/path/to/file", start_line=10, end_line=20) + return BaseFinding( + id="TEST-001", + title="Test Finding", + description="This is a test finding", + severity="HIGH", + scanner=scanner, + location=location, + ) + + +def test_finding_aggregator_add(sample_finding): + """Test adding findings to aggregator.""" + aggregator = FindingAggregator() + aggregator.add_finding(sample_finding) + assert len(aggregator.findings) == 1 + + +def test_finding_aggregator_deduplicate(sample_finding): + """Test deduplication of findings.""" + aggregator = FindingAggregator() + # Create duplicate finding with same attributes + finding2 = BaseFinding( + id=f"TEST-{round(datetime.now().timestamp())}", + title=sample_finding.title, + description=sample_finding.description, + severity=sample_finding.severity, + scanner=sample_finding.scanner, + location=sample_finding.location, + ) + aggregator.add_finding(sample_finding) + aggregator.add_finding(finding2) + deduplicated = aggregator.deduplicate() + assert len(deduplicated) == 1 + + +def test_finding_aggregator_group_by_type(sample_finding): + """Test grouping findings by type.""" + aggregator = FindingAggregator() + # Create second finding with different rule + finding2 = BaseFinding( + id=f"TEST-{round(datetime.now().timestamp())}", + title=sample_finding.title, + description=sample_finding.description, + severity=sample_finding.severity, + scanner=Scanner(name="test_scanner", version="1.0.0", type="SAST"), + location=sample_finding.location, + ) + aggregator.add_finding(sample_finding) + aggregator.add_finding(finding2) + grouped = aggregator.group_by_type() + assert len(grouped) == 2 + + +def test_finding_aggregator_group_by_severity(sample_finding): + """Test grouping findings by severity.""" + aggregator = FindingAggregator() + # Create second finding with different severity + finding2 = BaseFinding( + id=f"TEST-{round(datetime.now().timestamp())}", + title=sample_finding.title, + description=sample_finding.description, + severity="MEDIUM", + scanner=sample_finding.scanner, + location=sample_finding.location, + ) + aggregator.add_finding(sample_finding) + aggregator.add_finding(finding2) + grouped = aggregator.group_by_severity() + assert len(grouped) == 2 + assert len(grouped["HIGH"]) == 1 + assert len(grouped["MEDIUM"]) == 1 + + +def test_trend_analyzer(sample_finding): + """Test trend analysis functionality.""" + analyzer = TrendAnalyzer() + first_scan_time = datetime.now() - timedelta(days=1) + second_scan_time = datetime.now() + + # Add findings from two different scans + analyzer.add_scan_findings(first_scan_time, [sample_finding]) + + # Create a new finding for second scan + finding2 = BaseFinding( + id=f"TEST-{round(datetime.now().timestamp())}", + title="New Finding", + description="A new test finding", + severity="MEDIUM", + scanner=Scanner(name="test_scanner", version="1.0.0", type="SAST"), + location=Location(file_path="/path/to/other/file", start_line=15, end_line=25), + ) + analyzer.add_scan_findings(second_scan_time, [finding2]) + + # Test finding counts over time + counts = analyzer.get_finding_counts_over_time() + assert len(counts) == 2 + assert counts[first_scan_time] == 1 + assert counts[second_scan_time] == 1 + + # Test severity trends + trends = analyzer.get_severity_trends() + assert "HIGH" in trends + assert "MEDIUM" in trends + # Removed duplicate implementation + + new_findings = analyzer.get_new_findings(first_scan_time, second_scan_time) + assert len(new_findings) == 1 # Should be 1 since we added a different finding + + # Test resolved findings detection + resolved = analyzer.get_resolved_findings(first_scan_time, second_scan_time) + assert len(resolved) == 1 # First finding is resolved in second scan diff --git a/tests/models/test_asharp_model.py b/tests/models/test_asharp_model.py new file mode 100644 index 0000000..53bc740 --- /dev/null +++ b/tests/models/test_asharp_model.py @@ -0,0 +1,119 @@ +"""Unit tests for ASHARP model.""" + +import pytest +from datetime import datetime, timezone +from automated_security_helper.models.core import Location, Scanner, BaseFinding +from automated_security_helper.models.asharp_model import ASHARPModel +from automated_security_helper.models.data_interchange import ExportFormat + + +@pytest.fixture +def sample_scanner_dict(): + """Create a sample scanner dictionary.""" + return { + "name": "test_scanner", + "version": "1.0.0", + "type": "SAST", + "description": "Security scanner", + } + + +@pytest.fixture +def sample_finding(): + """Create a sample finding for testing.""" + scanner = Scanner(name="test_scanner", version="1.0.0") + location = Location(file_path="/path/to/file", start_line=10, end_line=20) + return BaseFinding( + id="RULE-001", + title="Test Finding", + description="This is a test finding", + severity="HIGH", + scanner=scanner, + location=location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + ) + + +def test_asharp_model_creation(): + """Test creation of ASHARPModel.""" + model = ASHARPModel( + name="Test Report", version="1.0.0", description="Test description", findings=[] + ) + assert model.name == "Test Report" + assert model.version == "1.0.0" + assert model.description == "Test description" + assert len(model.findings) == 0 + + +def test_asharp_model_findings_management(sample_finding): + """Test findings management functionality.""" + model = ASHARPModel( + name="Test Report", + version="1.0.0", + description="Test description", + findings=[sample_finding], + ) + + # Test deduplication + model.findings.append(sample_finding) # Add duplicate + deduplicated = model.deduplicate_findings() + assert len(deduplicated) == 1 + + # Test grouping + by_type = model.group_findings_by_type() + assert len(by_type) == 1 + + by_severity = model.group_findings_by_severity() + assert len(by_severity) == 1 + assert "HIGH" in by_severity + + +def test_asharp_model_scanner_conversion(sample_scanner_dict): + """Test scanner conversion functionality.""" + model = ASHARPModel(name="Test Report", version="1.0.0", description="Test") + scanner = model._convert_to_scanner(sample_scanner_dict) + assert scanner.name == sample_scanner_dict["name"] + assert scanner.version == sample_scanner_dict["version"] + + +def test_asharp_model_scanners_property(sample_finding): + """Test scanners property.""" + model = ASHARPModel( + name="Test Report", + version="1.0.0", + description="Test description", + findings=[sample_finding], + scanners_used=[sample_finding.scanner], + ) + scanners = model.scanners + assert len(scanners) == 1 + assert scanners[0].name == sample_finding.scanner.name + + +def test_asharp_model_export(): + """Test model export functionality.""" + model = ASHARPModel(name="Test Report", version="1.0.0", description="Test") + + # Test JSON export + json_export = model.export(format=ExportFormat.JSON) + assert isinstance(json_export, str) + assert "Test Report" in json_export + + # Test dict export + dict_export = model.export(format=ExportFormat.DICT) + assert isinstance(dict_export, dict) + assert dict_export["name"] == "Test Report" + + +def test_asharp_model_from_json(): + """Test model creation from JSON.""" + json_data = { + "name": "Test Report", + "version": "1.0.0", + "description": "Test description", + "findings": [], + } + model = ASHARPModel.from_json(json_data) + assert model.name == "Test Report" + assert model.version == "1.0.0" + assert model.description == "Test description" diff --git a/tests/models/test_container_scan.py b/tests/models/test_container_scan.py new file mode 100644 index 0000000..7b307b2 --- /dev/null +++ b/tests/models/test_container_scan.py @@ -0,0 +1,120 @@ +"""Unit tests for container scan functionality.""" + +import pytest +from datetime import datetime, timezone +from automated_security_helper.models.core import Location +from automated_security_helper.models.container_scan import ( + ContainerVulnerability, + ContainerScanReport, +) + + +@pytest.fixture +def sample_location(base_location): + """Create a sample location for testing.""" + return Location(file_path="Dockerfile", start_line=10, end_line=11) + + +@pytest.fixture +def sample_vulnerability(base_scanner, sample_location): + """Create a sample container vulnerability for testing.""" + return ContainerVulnerability( + id=f"CVE-{round(datetime.now().timestamp())}", + title="Test Vulnerability", + description="A test container vulnerability", + severity="HIGH", + scanner=base_scanner, + location=sample_location, + package_name="test-package", + package_version="1.0.0", + installed_version="1.0.0", + fix_version="1.1.0", + ) + + +def test_container_vulnerability_creation(base_scanner, sample_location): + """Test creation of ContainerVulnerability objects.""" + vuln = ContainerVulnerability( + id=f"CVE-{round(datetime.now().timestamp())}", + title="Test Vulnerability", + description="A test container vulnerability", + severity="HIGH", + scanner=base_scanner, + location=sample_location, + package_name="test-package", + package_version="1.0.0", + installed_version="1.0.0", + fix_version="1.1.0", + ) + assert vuln.title == "Test Vulnerability" + assert vuln.description == "A test container vulnerability" + assert vuln.severity == "HIGH" + assert vuln.package_name == "test-package" + assert vuln.package_version == "1.0.0" + assert vuln.fix_version == "1.1.0" + + +def test_container_vulnerability_inheritance(sample_vulnerability): + """Test that ContainerVulnerability inherits correctly from BaseFinding.""" + assert hasattr(sample_vulnerability, "title") + assert hasattr(sample_vulnerability, "description") + assert hasattr(sample_vulnerability, "severity") + assert hasattr(sample_vulnerability, "scanner") + assert hasattr(sample_vulnerability, "location") + assert hasattr(sample_vulnerability, "timestamp") + + +def test_container_scan_report_creation(sample_vulnerability): + """Test creation of ContainerScanReport objects.""" + report = ContainerScanReport( + image_name="test-image", + image_tag="latest", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + scanner_name="test", + findings=[sample_vulnerability], + ) + assert report.image_name == "test-image" + assert report.image_tag == "latest" + assert len(report.findings) == 1 + assert report.findings[0] == sample_vulnerability + + +def test_container_scan_report_empty(): + """Test creation of empty ContainerScanReport.""" + report = ContainerScanReport( + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + scanner_name="test", + image_name="test-image", + image_tag="latest", + findings=[], + ) + assert report.image_name == "test-image" + assert report.image_tag == "latest" + assert len(report.findings) == 0 + + +def test_container_scan_report_multiple_vulnerabilities(sample_vulnerability): + """Test ContainerScanReport with multiple vulnerabilities.""" + vuln2 = ContainerVulnerability( + id=f"CVE-{round(datetime.now().timestamp())}", + title="Another Vulnerability", + description="Another test vulnerability", + severity="MEDIUM", + scanner=sample_vulnerability.scanner, + location=sample_vulnerability.location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + package_name="another-package", + package_version="2.0.0", + installed_version="2.0.0", + fix_version="2.1.0", + ) + report = ContainerScanReport( + image_name="test-image", + image_tag="latest", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + scanner_name="test", + findings=[sample_vulnerability, vuln2], + ) + assert len(report.findings) == 2 + assert any(v.severity == "HIGH" for v in report.findings) + assert any(v.severity == "MEDIUM" for v in report.findings) diff --git a/tests/models/test_core.py b/tests/models/test_core.py new file mode 100644 index 0000000..c09f5ad --- /dev/null +++ b/tests/models/test_core.py @@ -0,0 +1,52 @@ +"""Unit tests for core models.""" + +import pytest +from automated_security_helper.models.core import Location, Scanner, BaseFinding + + +def test_location_creation(): + """Test creation of Location objects.""" + loc = Location(file_path="/path/to/file", start_line=10, end_line=20) + assert loc.file_path == "/path/to/file" + assert loc.start_line == 10 + assert loc.end_line == 20 + + +def test_scanner_creation(): + """Test creation of Scanner objects.""" + scanner = Scanner(name="test_scanner", version="1.0.0", type="SAST") + assert scanner.name == "test_scanner" + assert scanner.version == "1.0.0" + + +def test_base_finding_creation(): + """Test creation of BaseFinding objects.""" + scanner = Scanner(name="test_scanner", version="1.0.0", type="SAST") + location = Location(file_path="/path/to/file", start_line=10, end_line=20) + finding = BaseFinding( + id="TEST-001", + title="Test Finding", + description="This is a test finding", + severity="HIGH", + scanner=scanner, + location=location, + ) + assert finding.title == "Test Finding" + assert finding.description == "This is a test finding" + assert finding.severity == "HIGH" + assert finding.location == location + + +def test_base_finding_invalid_severity(): + """Test that invalid severity values raise ValidationError.""" + scanner = Scanner(name="test_scanner", version="1.0.0", type="SAST") + location = Location(file_path="/path/to/file", start_line=10, end_line=20) + with pytest.raises(ValueError): + BaseFinding( + id="TEST-002", + title="Test Finding", + description="This is a test finding", + severity="INVALID", # Invalid severity value + scanner=scanner, + location=location, + ) diff --git a/tests/models/test_data_interchange.py b/tests/models/test_data_interchange.py new file mode 100644 index 0000000..a4da5df --- /dev/null +++ b/tests/models/test_data_interchange.py @@ -0,0 +1,170 @@ +"""Unit tests for data interchange functionality.""" + +import pytest +import json +from datetime import datetime, timezone +from automated_security_helper.models.core import Location, Scanner, BaseFinding +from automated_security_helper.models.data_interchange import ( + ExportFormat, + DataInterchange, + ReportMetadata, + SecurityReport, +) + + +@pytest.fixture +def sample_metadata(): + """Create a sample report metadata for testing.""" + return ReportMetadata( + report_id="report-1", + tool_name="ash", + source="test-source", + scan_type="security", + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + ) + + +@pytest.fixture +def sample_finding(): + """Create a sample finding for testing.""" + scanner = Scanner(name="test_scanner", version="1.0.0") + location = Location(file_path="/path/to/file", start_line=10, end_line=20) + return BaseFinding( + id="finding-1", + title="Test Finding", + description="This is a test finding", + severity="HIGH", + scanner=scanner, + location=location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + ) + + +def test_export_format_values(): + """Test ExportFormat enum values.""" + assert ExportFormat.JSON == "json" + assert ExportFormat.DICT == "dict" + assert ExportFormat.SARIF == "sarif" + + +def test_data_interchange_creation(): + """Test creation of DataInterchange objects.""" + data = DataInterchange( + name="Test Data", version="1.0.0", description="Test description" + ) + assert data.name == "Test Data" + assert data.version == "1.0.0" + assert data.description == "Test description" + + +def test_report_metadata_creation(sample_metadata): + """Test creation of ReportMetadata objects.""" + assert sample_metadata.source == "test-source" + assert sample_metadata.scan_type == "security" + assert isinstance(sample_metadata.timestamp, str) + + +def test_security_report_creation(sample_finding, sample_metadata): + """Test creation of SecurityReport objects.""" + report = SecurityReport( + name="Test Report", + version="1.0.0", + description="Test security report", + metadata=sample_metadata, + findings=[sample_finding], + ) + assert report.name == "Test Report" + assert report.metadata == sample_metadata + assert len(report.findings) == 1 + assert report.findings[0] == sample_finding + + +def test_security_report_export_json(sample_finding, sample_metadata): + """Test JSON export functionality.""" + report = SecurityReport( + name="Test Report", + version="1.0.0", + description="Test security report", + metadata=sample_metadata, + findings=[sample_finding], + ) + json_output = report.export(format=ExportFormat.JSON) + assert isinstance(json_output, str) + # Verify JSON is valid + parsed = json.loads(json_output) + assert parsed["name"] == "Test Report" + assert len(parsed["findings"]) == 1 + assert "timestamp" in parsed["findings"][0] # Verify timestamp is in output + assert "finding_id" not in parsed["findings"][0] # Verify id is not in output + + +def test_security_report_export_dict(sample_finding, sample_metadata): + """Test dictionary export functionality.""" + report = SecurityReport( + name="Test Report", + version="1.0.0", + description="Test security report", + metadata=sample_metadata, + findings=[sample_finding], + ) + dict_output = report.export(format=ExportFormat.DICT) + assert isinstance(dict_output, dict) + assert dict_output["name"] == "Test Report" + assert len(dict_output["findings"]) == 1 + + +def test_security_report_from_json(): + """Test creation of SecurityReport from JSON.""" + json_data = { + "name": "Test Report", + "version": "1.0.0", + "description": "Test security report", + "metadata": { + "report_id": "report-1", + "tool_name": "ash", + "source": "test-source", + "scan_type": "security", + "timestamp": datetime.now().isoformat(timespec="seconds"), + }, + "findings": [], + } + report = SecurityReport.from_json(json_data) + assert report.name == "Test Report" + assert report.version == "1.0.0" + assert report.metadata.source == "test-source" + + +def test_security_report_track_history(sample_finding): + """Test history tracking between reports.""" + # Create two reports with different findings + report1 = SecurityReport( + name="Test Report", + version="1.0.0", + description="Test report 1", + metadata=ReportMetadata( + report_id="report-1", + tool_name="ash", + source="test-source", + scan_type="security", + timestamp=datetime.now().isoformat(timespec="seconds"), + ), + findings=[sample_finding], + ) + + report2 = SecurityReport( + name="Test Report", + version="1.0.0", + description="Test report 2", + metadata=ReportMetadata( + report_id="report-2", + tool_name="ash", + source="test-source", + scan_type="security", + timestamp=datetime.now().isoformat(timespec="seconds"), + ), + findings=[], # No findings in second report + ) + + history = report2.track_history(report1) + assert history["resolved_findings"] == 1 # One finding was resolved + assert history["new_findings"] == 0 # No new findings diff --git a/tests/models/test_dependency_scan.py b/tests/models/test_dependency_scan.py new file mode 100644 index 0000000..86c0a5e --- /dev/null +++ b/tests/models/test_dependency_scan.py @@ -0,0 +1,149 @@ +"""Unit tests for dependency scanning functionality.""" + +import pytest +from datetime import datetime, timezone +from automated_security_helper.models.core import Location +from automated_security_helper.models.dependency_scan import ( + DependencyVulnerability, + DependencyScanReport, +) + + +@pytest.fixture +def sample_location(): + """Create a sample location for testing.""" + return Location(file_path="requirements.txt", start_line=10, end_line=11) + + +@pytest.fixture +def sample_vulnerability(base_scanner, sample_location): + """Create a sample dependency vulnerability for testing.""" + return DependencyVulnerability( + id="test_id", + package_version="1.0.0", + ecosystem="python", + title="Test Dependency Vulnerability", + description="A test dependency vulnerability", + severity="HIGH", + scanner=base_scanner, + location=sample_location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + package_name="requests", + installed_version="2.25.0", + fixed_version="2.26.0", + dependency_type="direct", + ) + + +def test_dependency_vulnerability_creation(base_scanner, sample_location): + """Test creation of DependencyVulnerability objects.""" + vuln = DependencyVulnerability( + id="test_id", + package_version="1.0.0", + ecosystem="python", + title="Test Dependency Vulnerability", + description="A test dependency vulnerability", + severity="HIGH", + scanner=base_scanner, + location=sample_location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + package_name="requests", + installed_version="2.25.0", + fixed_version="2.26.0", + dependency_type="direct", + ) + assert vuln.title == "Test Dependency Vulnerability" + assert vuln.description == "A test dependency vulnerability" + assert vuln.severity == "HIGH" + assert vuln.package_name == "requests" + assert vuln.installed_version == "2.25.0" + assert vuln.fixed_version == "2.26.0" + assert vuln.dependency_type == "direct" + + +def test_dependency_vulnerability_inheritance(sample_vulnerability): + """Test that DependencyVulnerability inherits correctly from BaseFinding.""" + assert hasattr(sample_vulnerability, "title") + assert hasattr(sample_vulnerability, "description") + assert hasattr(sample_vulnerability, "severity") + assert hasattr(sample_vulnerability, "scanner") + assert hasattr(sample_vulnerability, "location") + assert hasattr(sample_vulnerability, "timestamp") + + +def test_dependency_vulnerability_invalid_dependency_type( + base_scanner, sample_location +): + """Test that invalid dependency type raises ValidationError.""" + with pytest.raises(ValueError): + DependencyVulnerability( + title="Test Vulnerability", + description="A test vulnerability", + severity="HIGH", + scanner=base_scanner, + location=sample_location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + package_name="requests", + installed_version="2.25.0", + fixed_version="2.26.0", + dependency_type="invalid", # Invalid dependency type + ) + + +def test_dependency_scan_report_creation(sample_vulnerability): + """Test creation of DependencyScanReport objects.""" + report = DependencyScanReport( + scanner_name="deps", + manifest_file="npmaudit.json", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + findings=[sample_vulnerability], + dependencies={"requests": "2.25.0", "urllib3": "1.26.5"}, + ) + assert len(report.findings) == 1 + assert report.findings[0] == sample_vulnerability + assert len(report.dependencies) == 2 + assert report.dependencies["requests"] == "2.25.0" + + +def test_dependency_scan_report_empty(): + """Test creation of empty DependencyScanReport.""" + report = DependencyScanReport( + scanner_name="deps", + manifest_file="npmaudit.json", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + findings=[], + dependencies={}, + ) + assert len(report.findings) == 0 + assert len(report.dependencies) == 0 + + +def test_dependency_scan_report_multiple_vulnerabilities(sample_vulnerability): + """Test DependencyScanReport with multiple vulnerabilities.""" + vuln2 = DependencyVulnerability( + id="test_id", + package_version="1.0.0", + ecosystem="python", + title="Another Vulnerability", + description="Another test vulnerability", + severity="MEDIUM", + scanner=sample_vulnerability.scanner, + location=sample_vulnerability.location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + package_name="urllib3", + installed_version="1.26.5", + fixed_version="1.26.6", + dependency_type="transitive", + ) + report = DependencyScanReport( + scanner_name="deps", + manifest_file="npmaudit.json", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + findings=[sample_vulnerability, vuln2], + dependencies={"requests": "2.25.0", "urllib3": "1.26.5"}, + ) + assert len(report.findings) == 2 + assert any(v.severity == "HIGH" for v in report.findings) + assert any(v.severity == "MEDIUM" for v in report.findings) + assert any(v.dependency_type == "direct" for v in report.findings) + assert any(v.dependency_type == "transitive" for v in report.findings) diff --git a/tests/models/test_dynamic_analysis.py b/tests/models/test_dynamic_analysis.py new file mode 100644 index 0000000..f429b79 --- /dev/null +++ b/tests/models/test_dynamic_analysis.py @@ -0,0 +1,187 @@ +"""Unit tests for dynamic analysis functionality.""" + +import pytest +from datetime import datetime, timezone +from automated_security_helper.models.core import Location, Scanner +from automated_security_helper.models.dynamic_analysis import ( + DynamicAnalysisFinding, + DynamicAnalysisReport, +) + + +@pytest.fixture +def sample_scanner(): + """Create a sample scanner for testing.""" + return Scanner(name="dynamic_scanner", version="1.0.0", type="DAST") + + +@pytest.fixture +def sample_location(): + """Create a sample location for testing.""" + return Location( + file_path="/api/users", + start_line=0, # Not always applicable for dynamic analysis + end_line=0, + ) + + +@pytest.fixture +def sample_finding(sample_scanner, sample_location): + """Create a sample dynamic analysis finding for testing.""" + return DynamicAnalysisFinding( + id="sql-1", + endpoint="postgres://postres.cloud", + title="SQL Injection", + description="Potential SQL injection vulnerability detected", + severity="HIGH", + scanner=sample_scanner, + location=sample_location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + request_method="POST", + request_url="/api/users", + request_headers={"Content-Type": "application/json"}, + request_body='{"id": "1 OR 1=1"}', + response_status=200, + response_headers={"Content-Type": "application/json"}, + response_body='{"data": [...]}', + proof_of_concept="curl -X POST -H 'Content-Type: application/json' -d '{\"id\": \"1 OR 1=1\"}' /api/users", + ) + + +def test_dynamic_analysis_finding_creation(sample_scanner, sample_location): + """Test creation of DynamicAnalysisFinding objects.""" + finding = DynamicAnalysisFinding( + id="sql-1", + endpoint="postgres://postres.cloud", + title="SQL Injection", + description="Potential SQL injection vulnerability detected", + severity="HIGH", + scanner=sample_scanner, + location=sample_location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + request_method="POST", + request_url="/api/users", + request_headers={"Content-Type": "application/json"}, + request_body='{"id": "1 OR 1=1"}', + response_status=200, + response_headers={"Content-Type": "application/json"}, + response_body='{"data": [...]}', + proof_of_concept="curl -X POST -H 'Content-Type: application/json' -d '{\"id\": \"1 OR 1=1\"}' /api/users", + ) + assert finding.title == "SQL Injection" + assert finding.severity == "HIGH" + assert finding.request_method == "POST" + assert finding.request_url == "/api/users" + assert finding.response_status == 200 + assert "Content-Type" in finding.request_headers + assert "Content-Type" in finding.response_headers + + +def test_dynamic_analysis_finding_inheritance(sample_finding): + """Test that DynamicAnalysisFinding inherits correctly from BaseFinding.""" + assert hasattr(sample_finding, "title") + assert hasattr(sample_finding, "description") + assert hasattr(sample_finding, "severity") + assert hasattr(sample_finding, "scanner") + assert hasattr(sample_finding, "location") + assert hasattr(sample_finding, "timestamp") + + +def test_dynamic_analysis_finding_invalid_request_method( + sample_scanner, sample_location +): + """Test that invalid request method raises ValidationError.""" + with pytest.raises(ValueError): + DynamicAnalysisFinding( + title="SQL Injection", + description="Test vulnerability", + severity="HIGH", + scanner=sample_scanner, + location=sample_location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + request_method="INVALID", # Invalid HTTP method + request_url="/api/users", + request_headers={}, + request_body="", + response_status=200, + response_headers={}, + response_body="", + proof_of_concept="", + ) + + +def test_dynamic_analysis_report_creation(sample_finding): + """Test creation of DynamicAnalysisReport objects.""" + report = DynamicAnalysisReport( + scanner_name="DynaScan", + target_url="https://example.com", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + findings=[sample_finding], + scan_coverage={ + "endpoints_tested": 50, + "auth_tested": True, + "input_vectors_tested": ["path", "query", "body"], + }, + ) + assert report.target_url == "https://example.com" + assert len(report.findings) == 1 + assert report.findings[0] == sample_finding + assert report.scan_coverage.endpoints_tested == 50 + assert report.scan_coverage.auth_tested is True + + +def test_dynamic_analysis_report_empty(): + """Test creation of empty DynamicAnalysisReport.""" + report = DynamicAnalysisReport( + scanner_name="DynaScan", + target_url="https://example.com", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + findings=[], + scan_coverage={ + "endpoints_tested": 0, + "auth_tested": False, + "input_vectors_tested": [], + }, + ) + assert report.target_url == "https://example.com" + assert len(report.findings) == 0 + assert report.scan_coverage.endpoints_tested == 0 + + +def test_dynamic_analysis_report_multiple_findings(sample_finding): + """Test DynamicAnalysisReport with multiple findings.""" + finding2 = DynamicAnalysisFinding( + id="xss-1", + endpoint="https://myapp-is-secure.com", + title="XSS Vulnerability", + description="Cross-site scripting vulnerability detected", + severity="MEDIUM", + scanner=sample_finding.scanner, + location=sample_finding.location, + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + request_method="GET", + request_url="/api/comments", + request_headers={}, + request_body="", + response_status=200, + response_headers={"Content-Type": "text/html"}, + response_body="", + proof_of_concept="curl '/api/comments?input='", + ) + + report = DynamicAnalysisReport( + scanner_name="DynaScan", + target_url="https://example.com", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + findings=[sample_finding, finding2], + scan_coverage={ + "endpoints_tested": 100, + "auth_tested": True, + "input_vectors_tested": ["path", "query", "body", "header"], + }, + ) + assert len(report.findings) == 2 + assert any(f.severity == "HIGH" for f in report.findings) + assert any(f.severity == "MEDIUM" for f in report.findings) + assert any(f.request_method == "POST" for f in report.findings) + assert any(f.request_method == "GET" for f in report.findings) diff --git a/tests/models/test_iac_scan.py b/tests/models/test_iac_scan.py new file mode 100644 index 0000000..d7dfa39 --- /dev/null +++ b/tests/models/test_iac_scan.py @@ -0,0 +1,197 @@ +"""Unit tests for Infrastructure as Code scanning functionality.""" + +import pytest +from datetime import datetime, timezone +from automated_security_helper.models.core import Location, Scanner +from automated_security_helper.models.data_interchange import ReportMetadata +from automated_security_helper.models.iac_scan import IaCVulnerability, IaCScanReport + + +@pytest.fixture +def sample_vulnerability(): + """Create a sample IaC vulnerability for testing.""" + return IaCVulnerability( + id="vuln-1", + file_path="test.template.json", + line_number=20, + rule_id="TEST-01", + title="Insecure Security Group", + description="Security group allows unrestricted access", + severity="HIGH", + location=Location(file_path="/path/to/file", start_line=10, end_line=5), + timestamp=datetime.now().isoformat(timespec="seconds"), + resource_type="AWS::EC2::SecurityGroup", + resource_name="WebServerSecurityGroup", + violation_details={ + "rule": "no-public-ingress-sgr", + "resource_id": "sg-12345", + "impact": "Allows unrestricted inbound access", + "remediation": "Restrict inbound traffic to specific IP ranges", + }, + ) + + +def test_iac_vulnerability_creation(base_scanner, base_location): + """Test creation of IaCVulnerability objects.""" + vuln = IaCVulnerability( + id="vuln-1", + file_path="test.template.json", + line_number=20, + rule_id="RULE-1", + title="Insecure Security Group", + description="Security group allows unrestricted access", + severity="HIGH", + scanner=base_scanner, + location=base_location, + timestamp=datetime.now().isoformat(timespec="seconds"), + resource_type="AWS::EC2::SecurityGroup", + resource_name="WebServerSecurityGroup", + violation_details={ + "rule": "no-public-ingress-sgr", + "resource_id": "sg-12345", + "impact": "Allows unrestricted inbound access", + "remediation": "Restrict inbound traffic to specific IP ranges", + }, + ) + assert vuln.title == "Insecure Security Group" + assert vuln.severity == "HIGH" + assert vuln.resource_type == "AWS::EC2::SecurityGroup" + assert vuln.resource_name == "WebServerSecurityGroup" + assert "rule" in vuln.violation_details + assert "remediation" in vuln.violation_details + + +def test_iac_vulnerability_inheritance(sample_vulnerability): + """Test that IaCVulnerability inherits correctly from BaseFinding.""" + assert hasattr(sample_vulnerability, "title") + assert hasattr(sample_vulnerability, "description") + assert hasattr(sample_vulnerability, "severity") + assert hasattr(sample_vulnerability, "location") + assert hasattr(sample_vulnerability, "timestamp") + + +def test_iac_scan_report_creation(sample_vulnerability): + """Test creation of IaCScanReport objects.""" + report = IaCScanReport( + name="CloudFormation IaC Scan Report", + metadata=ReportMetadata(report_id="XXXXX", tool_name="cdk-nag"), + iac_framework="CloudFormation", + timestamp=datetime.now().isoformat(timespec="seconds"), + findings=[sample_vulnerability], + scanners_used=[ + Scanner(name="base_scanner", version="1.0.0", type="SAST"), + ], + resources_checked={ + "AWS::EC2::SecurityGroup": 2, + "AWS::S3::Bucket": 3, + "AWS::IAM::Role": 1, + }, + ) + assert report.iac_framework == "CloudFormation" + assert len(report.findings) == 1 + assert report.findings[0] == sample_vulnerability + assert report.resources_checked["AWS::EC2::SecurityGroup"] == 2 + + +def test_iac_scan_report_empty(): + """Test creation of empty IaCScanReport.""" + report = IaCScanReport( + name="CloudFormation IaC Scan Report", + iac_framework="CloudFormation", + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + findings=[], + resources_checked={}, + metadata=ReportMetadata( + report_id="XXXXX", + tool_name="cfn-nag", + ), + ) + assert report.iac_framework == "CloudFormation" + assert len(report.findings) == 0 + + +def test_iac_scan_report_multiple_findings(base_scanner, sample_vulnerability): + """Test IaCScanReport with multiple findings.""" + vuln2 = IaCVulnerability( + id="vuln-2", + file_path="test.template.json", + line_number=25, + rule_id="XXXXXX", + title="Unencrypted S3 Bucket", + description="S3 bucket does not have encryption enabled", + severity="MEDIUM", + scanner=base_scanner, + location=Location(file_path="templates/main.yaml", start_line=20, end_line=25), + timestamp=datetime.now().isoformat(timespec="seconds"), + resource_type="AWS::S3::Bucket", + resource_name="DataBucket", + violation_details={ + "rule": "encrypt-s3-bucket", + "resource_id": "my-bucket", + "impact": "Data stored in plaintext", + "remediation": "Enable S3 bucket encryption", + }, + ) + + report = IaCScanReport( + name="CloudFormation IaC Scan Report", + metadata=ReportMetadata(report_id="XXXXX", tool_name="cdk-nag"), + template_path="templates/main.yaml", + template_type="CloudFormation", + timestamp=datetime.now().isoformat(timespec="seconds"), + findings=[sample_vulnerability, vuln2], + resources_checked={ + "AWS::EC2::SecurityGroup": 2, + "AWS::S3::Bucket": 3, + "AWS::IAM::Role": 1, + }, + ) + assert len(report.findings) == 2 + assert any(f.severity == "HIGH" for f in report.findings) + assert any(f.severity == "MEDIUM" for f in report.findings) + assert any(f.resource_type == "AWS::EC2::SecurityGroup" for f in report.findings) + assert any(f.resource_type == "AWS::S3::Bucket" for f in report.findings) + + +def test_iac_scan_report_invalid_template_type(): + """Test that invalid template type raises ValidationError.""" + with pytest.raises(ValueError): + IaCScanReport( + template_path="templates/main.yaml", + template_type="InvalidType", # Invalid template type + timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + findings=[], + resources_checked={}, + ) + + +def test_iac_vulnerability_with_policy_violation(base_scanner, base_location): + """Test IaCVulnerability with policy violation details.""" + vuln = IaCVulnerability( + id="vuln-1", + file_path="test.template.json", + line_number=20, + rule_id="XXXXXX", + title="Non-Compliant IAM Policy", + description="IAM policy grants excessive permissions", + severity="HIGH", + scanner=base_scanner, + location=base_location, + timestamp=datetime.now().isoformat(timespec="seconds"), + resource_type="AWS::IAM::Policy", + resource_name="AdminPolicy", + violation_details={ + "rule": "restrict-iam-policy", + "resource_id": "policy-12345", + "impact": "Overly permissive IAM policy", + "remediation": "Limit IAM permissions to required actions only", + "policy_violation": "Actions include wildcard permissions", + }, + ) + assert vuln.title == "Non-Compliant IAM Policy" + assert vuln.resource_type == "AWS::IAM::Policy" + assert "policy_violation" in vuln.violation_details + assert ( + "Actions include wildcard permissions" + in vuln.violation_details["policy_violation"] + ) diff --git a/tests/models/test_sbom.py b/tests/models/test_sbom.py new file mode 100644 index 0000000..32233dc --- /dev/null +++ b/tests/models/test_sbom.py @@ -0,0 +1,240 @@ +"""Unit tests for Software Bill of Materials (SBOM) functionality.""" + +import pytest +from datetime import datetime, timezone +from automated_security_helper.models.sbom import ( + SBOMComponent, + SBOMMetadata, + SBOMPackage, + SBOMReport, +) + + +@pytest.fixture +def sample_package(): + """Create a sample package for testing.""" + dep1 = SBOMPackage( + name="urllib3", + version="1.26.6", + type="pypi", + dependencies=[], + license="MIT", + publisher="urllib3", + ) + dep2 = SBOMPackage( + name="certifi", + version="2021.5.30", + type="pypi", + dependencies=[], + license="MPL-2.0", + publisher="certifi", + ) + return SBOMPackage( + name="requests", + version="2.26.0", + type="pypi", + dependencies=[dep1, dep2], + license="Apache-2.0", + publisher="Kenneth Reitz", + metadata={ + "description": "Python HTTP for Humans", + "homepage": "https://requests.readthedocs.io", + }, + ) + + +def test_sbom_package_creation(sample_package): + """Test creation of SBOMPackage objects.""" + assert sample_package.name == "requests" + assert sample_package.version == "2.26.0" + assert sample_package.type == "pypi" + assert len(sample_package.dependencies) == 2 + assert sample_package.license == "Apache-2.0" + assert sample_package.publisher == "Kenneth Reitz" + + +def test_sbom_package_invalid_type(): + """Test that invalid package type raises ValidationError.""" + with pytest.raises(ValueError): + SBOMPackage( + name="requests", + version="2.26.0", + type="invalid", # Invalid package type + dependencies=[], + metadata={}, + ) + + +def test_sbom_report_creation(sample_package): + """Test creation of SBOMReport objects.""" + report = SBOMReport( + name="test-sbom", + project_name="test-project", + version=datetime.now(timezone.utc).strftime("%Y.%m.%d"), + generated_at=datetime.now(timezone.utc).isoformat(timespec="seconds"), + packages=[sample_package], + metadata=[ + SBOMMetadata( + component=SBOMComponent( + name="requests", + version="2.26.0", + license="Apache-2.0", + type="pypi", + publisher="Kenneth Reitz", + ) + ) + ], + ) + assert report.project_name == "test-project" + assert len(report.packages) == 1 + assert report.packages[0] == sample_package + + +def test_sbom_report_empty(): + """Test creation of empty SBOMReport.""" + report = SBOMReport( + name="test-sbom", + project_name="test-project", + version=datetime.now(timezone.utc).strftime("%Y.%m.%d"), + generated_at=datetime.now(timezone.utc).isoformat(timespec="seconds"), + packages=[], + metadata=[ + SBOMMetadata( + component=SBOMComponent( + name="requests", + version="2.26.0", + license="Apache-2.0", + type="pypi", + publisher="Kenneth Reitz", + ) + ) + ], + ) + assert report.project_name == "test-project" + assert len(report.packages) == 0 + + +def test_sbom_report_multiple_packages(sample_package): + """Test SBOMReport with multiple packages.""" + package2 = SBOMPackage( + name="flask", + version="2.0.1", + type="pypi", + dependencies=[ + {"name": "werkzeug", "version": "2.0.1", "type": "pypi"}, + {"name": "jinja2", "version": "3.0.1", "type": "pypi"}, + ], + metadata={ + "author": "Armin Ronacher", + "license": "BSD-3-Clause", + "description": "Python micro framework", + }, + ) + + report = SBOMReport( + name="test-sbom", + project_name="test-project", + version=datetime.now(timezone.utc).strftime("%Y.%m.%d"), + generated_at=datetime.now(timezone.utc).isoformat(timespec="seconds"), + packages=[sample_package, package2], + metadata=[ + SBOMMetadata( + component=SBOMComponent( + name="requests", + version="2.26.0", + license="Apache-2.0", + type="pypi", + publisher="Kenneth Reitz", + ) + ) + ], + ) + assert len(report.packages) == 2 + assert any(p.name == "requests" for p in report.packages) + assert any(p.name == "flask" for p in report.packages) + + +def test_sbom_package_license_check(sample_package): + """Test package license validation.""" + # Test valid licenses + valid_licenses = ["MIT", "Apache 2.0", "GPL-3.0", "BSD-3-Clause"] + for license in valid_licenses: + package = SBOMPackage( + name="test", + version="1.0.0", + type="pypi", + dependencies=[], + metadata={"license": license}, + ) + assert package.metadata["license"] == license + + +def test_sbom_package_version_validation(): + """Test package version format validation.""" + valid_versions = ["1.0.0", "2.3.4-alpha", "0.1.0-rc1", "1.0.0.dev0"] + for version in valid_versions: + package = SBOMPackage( + name="test", version=version, type="pypi", dependencies=[], metadata={} + ) + assert package.version == version + + +def test_sbom_report_dependency_tree(sample_package): + """Test dependency tree generation in SBOM report.""" + report = SBOMReport( + name="test-sbom", + project_name="test-project", + generated_at=datetime.now(timezone.utc).isoformat(timespec="seconds"), + version=datetime.now(timezone.utc).strftime("%Y.%m.%d"), + packages=[sample_package], + metadata=[ + SBOMMetadata( + component=SBOMComponent( + name="requests", + version="2.26.0", + license="Apache-2.0", + type="pypi", + publisher="Kenneth Reitz", + ) + ) + ], + ) + + dep_tree = report.get_dependency_tree() + assert "requests" in dep_tree + assert "urllib3" in str(dep_tree) # Should be in the nested structure + assert "certifi" in str(dep_tree) # Should be in the nested structure + + +def test_sbom_report_export(sample_package): + """Test SBOM report export functionality.""" + report = SBOMReport( + project_name="test-project", + name="test-sbom", + version=datetime.now(timezone.utc).strftime("%Y.%m.%d"), + generated_at=datetime.now(timezone.utc).isoformat(timespec="seconds"), + packages=[sample_package], + metadata=[ + SBOMMetadata( + component=SBOMComponent( + name="requests", + version="2.26.0", + license="Apache-2.0", + type="pypi", + publisher="Kenneth Reitz", + ) + ) + ], + ) + + # Test CycloneDX format export + cyclonedx = report.export(format="cyclonedx") + assert isinstance(cyclonedx, dict) + assert "requests" in cyclonedx["packages"][0]["name"] + assert "2.26.0" in cyclonedx["packages"][0]["version"] + + # Test SPDX format export + spdx = report.export(format="spdx") + assert isinstance(spdx, dict) + assert "requests" in spdx["packages"][0]["name"] + assert "2.26.0" in spdx["packages"][0]["version"] diff --git a/tests/models/test_scan_results.py b/tests/models/test_scan_results.py new file mode 100644 index 0000000..93a6a1c --- /dev/null +++ b/tests/models/test_scan_results.py @@ -0,0 +1,35 @@ +"""Unit tests for scan results container.""" + +from automated_security_helper.models.scan_results import ScanResultsContainer + + +def test_scan_results_container_initialization(): + """Test ScanResultsContainer initialization.""" + container = ScanResultsContainer() + assert container.findings == [] + assert container.metadata == {} + assert container.raw_results is None + + +def test_scan_results_container_add_findings(): + """Test adding findings to container.""" + container = ScanResultsContainer() + findings = [{"id": 1, "severity": "HIGH"}, {"id": 2, "severity": "LOW"}] + container.add_findings(findings) + assert container.findings == findings + + +def test_scan_results_container_add_metadata(): + """Test adding metadata to container.""" + container = ScanResultsContainer() + container.add_metadata("version", "1.0.0") + container.add_metadata("scanner", "test_scanner") + assert container.metadata == {"version": "1.0.0", "scanner": "test_scanner"} + + +def test_scan_results_container_set_raw_results(): + """Test setting raw results.""" + container = ScanResultsContainer() + raw_results = {"findings": [], "metadata": {}} + container.set_raw_results(raw_results) + assert container.raw_results == raw_results diff --git a/tests/models/test_security_vulnerability.py b/tests/models/test_security_vulnerability.py new file mode 100644 index 0000000..721cebc --- /dev/null +++ b/tests/models/test_security_vulnerability.py @@ -0,0 +1,246 @@ +"""Unit tests for security vulnerability model.""" + +import pytest +from datetime import datetime +from automated_security_helper.models.core import BaseFinding, Location, Scanner +from automated_security_helper.models.security_vulnerability import ( + SecurityVulnerability, + SecurityVulnerabilityReport, +) +from automated_security_helper.models.data_interchange import ReportMetadata + + +@pytest.fixture +def sample_scanner(): + """Create a sample scanner for testing.""" + return Scanner(name="security_scanner_1", version="1.0.0", type="SAST") + + +@pytest.fixture +def sample_location(): + """Create a sample location for testing.""" + return Location(file_path="/app/vulnerable_code.py", start_line=42, end_line=45) + + +@pytest.fixture +def sample_vulnerability(sample_scanner, sample_location): + """Create a sample security vulnerability for testing.""" + return SecurityVulnerability( + id="SEC-001", + title="Hardcoded Secret", + description="Found hardcoded API key in source code", + severity="HIGH", + scanner=sample_scanner, + location=sample_location, + vulnerability_type="secret_exposure", + cwe_id="CWE-798", + cvss_score=7.5, + remediation_steps=["Remove hardcoded secrets", "Use environment variables"], + references=[ + "https://cwe.mitre.org/data/definitions/798.html", + "https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password", + ], + status="OPEN", + ) + + +def test_security_vulnerability_creation(sample_scanner, sample_location): + """Test creation of SecurityVulnerability objects.""" + vuln = SecurityVulnerability( + id=f"SEC-{round(datetime.now().timestamp())}", + title="Hardcoded Secret", + description="Found hardcoded API key in source code", + severity="HIGH", + scanner=sample_scanner, + location=sample_location, + timestamp=datetime.now().isoformat(timespec="seconds"), + vulnerability_type="secret_exposure", + cwe_id="CWE-798", + cvss_score=7.5, + remediation_steps=["Remove hardcoded secrets", "Use environment variables"], + references=[ + "https://cwe.mitre.org/data/definitions/798.html", + "https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password", + ], + ) + assert vuln.title == "Hardcoded Secret" + assert vuln.severity == "HIGH" + assert vuln.vulnerability_type == "secret_exposure" + assert vuln.cwe_id == "CWE-798" + assert vuln.cvss_score == 7.5 + assert len(vuln.remediation_steps) == 2 + assert len(vuln.references) == 2 + + +def test_security_vulnerability_inheritance(sample_vulnerability): + """Test that SecurityVulnerability inherits correctly from BaseFinding.""" + # Test inheritance + assert isinstance(sample_vulnerability, BaseFinding) + + # Test base attributes + assert sample_vulnerability.id == "SEC-001" + assert sample_vulnerability.title == "Hardcoded Secret" + assert sample_vulnerability.severity == "HIGH" + assert sample_vulnerability.status == "OPEN" + assert isinstance(sample_vulnerability.timestamp, str) + + # Test extended attributes + assert sample_vulnerability.vulnerability_type == "secret_exposure" + assert sample_vulnerability.cwe_id == "CWE-798" + assert sample_vulnerability.cvss_score == 7.5 + assert len(sample_vulnerability.remediation_steps) == 2 + assert len(sample_vulnerability.references) == 2 + assert hasattr(sample_vulnerability, "severity") + assert hasattr(sample_vulnerability, "scanner") + assert hasattr(sample_vulnerability, "location") + assert hasattr(sample_vulnerability, "timestamp") + + +def test_security_vulnerability_invalid_cvss_score(sample_scanner, sample_location): + """Test that invalid CVSS score raises ValidationError.""" + with pytest.raises(ValueError): + SecurityVulnerability( + id="TEST-001", + title="Test Vulnerability", + description="Test description", + severity="HIGH", + scanner=sample_scanner, + location=sample_location, + vulnerability_type="test_type", + cwe_id="CWE-000", + cvss_score=11.0, # Invalid CVSS score (should be 0-10) + remediation_steps=[], + references=[], + status="OPEN", + ) + + +def test_security_vulnerability_invalid_cwe_id(sample_scanner, sample_location): + """Test that invalid CWE ID format raises ValidationError.""" + with pytest.raises(ValueError): + SecurityVulnerability( + title="Test Vulnerability", + description="Test description", + severity="HIGH", + scanner=sample_scanner, + location=sample_location, + timestamp=datetime.now().isoformat(timespec="seconds"), + vulnerability_type="test_type", + cwe_id="invalid-format", # Invalid CWE ID format + cvss_score=5.0, + remediation_steps=[], + references=[], + ) + + +def test_security_vulnerability_report_creation(sample_vulnerability): + """Test creation of SecurityVulnerabilityReport objects.""" + report = SecurityVulnerabilityReport( + name="Test Security Report", + metadata=ReportMetadata( + report_id="TEST-001", + project_name="test-project", + tool_name="security-tool", + tool_version="1.0.0", + ), + findings=[sample_vulnerability], + ) + + assert report.name == "Test Security Report" + assert len(report.findings) == 1 + assert report.findings[0] == sample_vulnerability + assert "total_cvss" in report.risk_metrics + assert report.risk_metrics["avg_cvss"] == 7.5 + + +def test_security_vulnerability_report_empty(): + """Test creation of empty SecurityVulnerabilityReport.""" + report = SecurityVulnerabilityReport( + name="Empty Report", + metadata=ReportMetadata( + report_id="TEST-002", + project_name="test-project", + tool_name="security-tool", + tool_version="1.0.0", + ), + ) + assert report.metadata.project_name == "test-project" + assert len(report.findings) == 0 + assert report.risk_metrics["total_cvss"] == 0.0 + assert report.risk_metrics["avg_cvss"] == 0.0 + assert report.risk_metrics["max_cvss"] == 0.0 + + +def test_security_vulnerability_report_multiple_vulnerabilities( + sample_vulnerability, sample_scanner, sample_location +): + """Test SecurityVulnerabilityReport with multiple vulnerabilities.""" + vuln2 = SecurityVulnerability( + id="SQL-001", + title="SQL Injection", + description="SQL injection vulnerability in query", + severity="MEDIUM", + scanner=sample_scanner, + location=sample_location, + vulnerability_type="injection", + cwe_id="CWE-89", + cvss_score=6.5, + remediation_steps=["Use parameterized queries"], + references=["https://cwe.mitre.org/data/definitions/89.html"], + status="OPEN", + ) + + report = SecurityVulnerabilityReport( + name="Multiple Vulnerabilities Report", + metadata=ReportMetadata( + report_id="TEST-003", + project_name="test-project", + tool_name="security-tool", + tool_version="1.0.0", + ), + findings=[sample_vulnerability, vuln2], + ) + assert len(report.findings) == 2 + assert any(v.severity == "HIGH" for v in report.findings) + assert any(v.severity == "MEDIUM" for v in report.findings) + assert any(v.cwe_id == "CWE-798" for v in report.findings) + assert any(v.cwe_id == "CWE-89" for v in report.findings) + + +def test_security_vulnerability_risk_metrics_calculation( + sample_scanner, sample_location +): + """Test risk metrics calculation functionality.""" + vulnerabilities = [ + SecurityVulnerability( + id=f"TEST-{i}", + title=f"Test Vulnerability {i}", + description=f"Test description {i}", + severity="HIGH" if i < 2 else "MEDIUM" if i < 4 else "LOW", + scanner=Scanner(name="test", version="1.0"), + location=Location(file_path="test.py", start_line=i, end_line=i), + timestamp=datetime.now().isoformat(timespec="seconds"), + vulnerability_type="test", + cwe_id=f"CWE-{i}", + cvss_score=8.0 if i < 2 else 5.0 if i < 4 else 3.0, + remediation_steps=[], + references=[], + ) + for i in range(6) + ] + report = SecurityVulnerabilityReport( + name="Risk Metrics Report", + metadata=ReportMetadata( + report_id="RISK-001", + project_name="test-project", + tool_name="security-tool", + tool_version="1.0.0", + ), + findings=vulnerabilities, + ) + + report.calculate_risk_metrics() + assert report.metadata.summary_stats["high"] == 2 + assert report.metadata.summary_stats["medium"] == 2 + assert report.metadata.summary_stats["low"] == 2 + assert 5.0 <= report.risk_metrics["avg_cvss"] <= 5.5 # Average of all CVSS scores diff --git a/tests/models/test_static_analysis.py b/tests/models/test_static_analysis.py new file mode 100644 index 0000000..3c5fee1 --- /dev/null +++ b/tests/models/test_static_analysis.py @@ -0,0 +1,290 @@ +"""Unit tests for static analysis functionality.""" + +import pytest +from datetime import datetime, timezone +from automated_security_helper.config.config import ScannerBaseConfig +from automated_security_helper.models.core import Location, Scanner +from automated_security_helper.models.data_interchange import ScanStatistics +from automated_security_helper.models.static_analysis import ( + StaticAnalysisFinding, + StaticAnalysisReport, +) + + +@pytest.fixture +def sample_scanner(): + """Create a sample scanner for testing.""" + return Scanner(name="static_analyzer", version="1.0.0", type="SAST") + + +@pytest.fixture +def sample_location(): + """Create a sample location for testing.""" + return Location(file_path="/app/main.py", start_line=45, end_line=48) + + +@pytest.fixture +def sample_finding(sample_scanner, sample_location): + """Create a sample static analysis finding for testing.""" + return StaticAnalysisFinding( + id="SAST/PICKLE/001", + title="Insecure Deserialization", + description="Unsafe pickle.loads() usage detected", + severity="HIGH", + source_file="myfile.py", + scanner=sample_scanner, + location=sample_location, + finding_type="security", + code_snippet="data = pickle.loads(user_input)", + line_number=46, + column_start=5, + column_end=35, + affected_parameters=["user_input"], + data_flow=[ + { + "source": {"file": "routes.py", "line": 23, "value": "request.data"}, + "sink": {"file": "main.py", "line": 46, "function": "process_data"}, + } + ], + fix_recommendation="Use safe deserialization methods like json.loads()", + ) + + +def test_static_analysis_finding_creation(sample_scanner, sample_location): + """Test creation of StaticAnalysisFinding objects.""" + finding = StaticAnalysisFinding( + id=f"SAST-{round(datetime.now().timestamp())}", + title="Insecure Deserialization", + description="Unsafe pickle.loads() usage detected", + severity="HIGH", + scanner=sample_scanner, + source_file="myfile.py", + location=sample_location, + timestamp=datetime.now().isoformat(timespec="seconds"), + finding_type="security", + code_snippet="data = pickle.loads(user_input)", + line_number=46, + column_start=5, + column_end=35, + affected_parameters=["user_input"], + data_flow=[ + { + "source": {"file": "routes.py", "line": 23, "value": "request.data"}, + "sink": {"file": "main.py", "line": 46, "function": "process_data"}, + } + ], + fix_recommendation="Use safe deserialization methods like json.loads()", + ) + assert finding.title == "Insecure Deserialization" + assert finding.severity == "HIGH" + assert finding.finding_type == "security" + assert finding.code_snippet == "data = pickle.loads(user_input)" + assert finding.line_number == 46 + assert finding.column_start == 5 + assert finding.column_end == 35 + assert "user_input" in finding.affected_parameters + assert len(finding.data_flow) == 1 + assert ( + finding.fix_recommendation + == "Use safe deserialization methods like json.loads()" + ) + + +def test_static_analysis_finding_inheritance(sample_finding): + """Test that StaticAnalysisFinding inherits correctly from BaseFinding.""" + assert hasattr(sample_finding, "title") + assert hasattr(sample_finding, "description") + assert hasattr(sample_finding, "severity") + assert hasattr(sample_finding, "scanner") + assert hasattr(sample_finding, "location") + assert hasattr(sample_finding, "timestamp") + + +def test_static_analysis_finding_invalid_finding_type(sample_scanner, sample_location): + """Test that invalid finding type raises ValidationError.""" + with pytest.raises(ValueError): + StaticAnalysisFinding( + title="Test Finding", + description="Test description", + severity="HIGH", + scanner=sample_scanner, + source_file="myfile.py", + location=sample_location, + timestamp=datetime.now().isoformat(timespec="seconds"), + finding_type="invalid", # Invalid finding type + code_snippet="test code", + line_number=1, + column_start=1, + column_end=10, + affected_parameters=[], + data_flow=[], + fix_recommendation="", + ) + + +def test_static_analysis_report_creation(sample_finding): + """Test creation of StaticAnalysisReport objects.""" + report = StaticAnalysisReport( + name="test-project", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + scanner_name="test", + findings=[sample_finding], + scan_config={ + "rules": ["security", "performance", "style"], + "ignored_paths": ["tests/", "docs/"], + "max_line_length": 100, + }, + statistics={ + "files_scanned": 50, + "lines_of_code": 5000, + "findings_by_type": {"security": 1, "performance": 0, "style": 0}, + }, + ) + assert report.name == "test-project" + assert len(report.findings) == 1 + assert report.findings[0] == sample_finding + assert "security" in report.scan_config["rules"] + assert report.statistics.files_scanned == 50 + assert report.statistics.findings_by_type["security"] == 1 + + +def test_static_analysis_report_empty(): + """Test creation of empty StaticAnalysisReport.""" + report = StaticAnalysisReport( + name="test-project", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + scanner_name="test", + findings=[], + scan_config={ + "rules": ["security"], + "ignored_paths": [], + "max_line_length": 100, + }, + statistics=ScanStatistics( + files_scanned=0, + lines_of_code=0, + findings_by_type={"security": 0}, + ), + ) + assert report.name == "test-project" + assert len(report.findings) == 0 + assert report.statistics.findings_by_type["security"] == 0 + + +def test_static_analysis_report_multiple_findings(sample_finding): + """Test StaticAnalysisReport with multiple findings.""" + finding2 = StaticAnalysisFinding( + id=f"SAST-{round(datetime.now().timestamp())}", + title="SQL Injection Risk", + description="Possible SQL injection in query construction", + severity="MEDIUM", + scanner=sample_finding.scanner, + source_file="myfile.py", + location=sample_finding.location, + timestamp=datetime.now().isoformat(timespec="seconds"), + finding_type="security", + code_snippet='query = f"SELECT * FROM users WHERE id = {user_id}"', + line_number=50, + column_start=1, + column_end=50, + affected_parameters=["user_id"], + data_flow=[ + { + "source": { + "file": "routes.py", + "line": 30, + "value": "request.args.get('id')", + }, + "sink": {"file": "main.py", "line": 50, "function": "get_user"}, + } + ], + fix_recommendation="Use parameterized queries with cursor.execute()", + ) + + report = StaticAnalysisReport( + name="test-project", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + scanner_name="test", + findings=[sample_finding, finding2], + scan_config={ + "rules": ["security", "performance", "style"], + "ignored_paths": ["tests/", "docs/"], + "max_line_length": 100, + }, + statistics=ScanStatistics( + files_scanned=50, + lines_of_code=5000, + findings_by_type={"security": 2, "performance": 0, "style": 0}, + ), + ) + assert len(report.findings) == 2 + assert any(f.severity == "HIGH" for f in report.findings) + assert any(f.severity == "MEDIUM" for f in report.findings) + assert report.statistics.findings_by_type["security"] == 2 + + +def test_static_analysis_report_by_file(): + """Test grouping findings by file functionality.""" + findings = [ + StaticAnalysisFinding( + id=f"FINDING-{i}", + title=f"Finding {i}", + description=f"Description {i}", + severity="HIGH", + scanner=Scanner(name="test", version="1.0", type="SAST"), + source_file=f"file{i}.py", + location=Location(file_path=f"file{i}.py", start_line=i, end_line=i), + timestamp=datetime.now().isoformat(timespec="seconds"), + finding_type="security", + code_snippet=f"code_{i}", + line_number=i, + column_start=1, + column_end=10, + affected_parameters=[], + data_flow=[], + fix_recommendation="", + ) + for i in range(1, 4) + ] + + # Add duplicate file finding + findings.append( + StaticAnalysisFinding( + id="dupe", + title="Duplicate File Finding", + description="Another finding in file1.py", + severity="MEDIUM", + scanner=Scanner(name="test", version="1.0", type="SAST"), + source_file="file1.py", + location=Location(file_path="file1.py", start_line=10, end_line=10), + timestamp=datetime.now().isoformat(timespec="seconds"), + finding_type="security", + code_snippet="duplicate_code", + line_number=10, + column_start=1, + column_end=10, + affected_parameters=[], + data_flow=[], + fix_recommendation="", + ) + ) + + report = StaticAnalysisReport( + name="test-project", + scan_timestamp=datetime.now(timezone.utc).isoformat(timespec="seconds"), + scanner_name="test", + findings=findings, + scan_config=ScannerBaseConfig(rules=["security"]), + statistics=ScanStatistics( + files_scanned=3, + lines_of_code=300, + findings_by_type={"security": 4}, + ), + ) + + by_file = report.group_findings_by_file() + assert len(by_file) == 3 # Should have 3 unique files + assert len(by_file["file1.py"]) == 2 # Should have 2 findings + assert all( + len(findings) == 1 for file, findings in by_file.items() if file != "file1.py" + ) diff --git a/tests/models/test_validation.py b/tests/models/test_validation.py new file mode 100644 index 0000000..3f311ea --- /dev/null +++ b/tests/models/test_validation.py @@ -0,0 +1,101 @@ +"""Unit tests for validation module.""" + +from automated_security_helper.models.validation import ConfigurationValidator +from automated_security_helper.config.config import ParserConfig, ScannerPluginConfig + + +def test_validate_config(): + """Test basic config validation functionality.""" + validator = ConfigurationValidator() + + # Test valid dict config + valid_dict = {"name": "test", "type": "static"} + is_valid, error = validator.validate_config(valid_dict, ScannerPluginConfig) + assert is_valid is True + assert error is None + + # Test valid model config + valid_model = ScannerPluginConfig(name="test", type="static") + is_valid, error = validator.validate_config(valid_model, ScannerPluginConfig) + assert is_valid is True + assert error is None + + # Test invalid config + invalid_config = {} + is_valid, error = validator.validate_config(invalid_config, ScannerPluginConfig) + assert is_valid is False + assert "Empty configuration" in str(error) + + # Test invalid type + is_valid, error = validator.validate_config(None, ScannerPluginConfig) # type: ignore + assert not is_valid + assert error is not None + + +def test_validate_scanner_config(): + """Test validation of scanner configurations.""" + validator = ConfigurationValidator() + + # Test valid scanner config + valid_scanner_config = {"name": "scanner1", "type": "static"} + result = validator.validate_scanner_config(valid_scanner_config) + assert isinstance(result, tuple) + assert result[0] is True + assert result[1] is None + + # Test invalid scanner config (None) + is_valid, error = validator.validate_scanner_config(None) # type: ignore + assert is_valid is False + assert error is not None + + # Test invalid scanner config (missing required fields) + invalid_config = {} + is_valid, error = validator.validate_scanner_config(invalid_config) + assert is_valid is False + assert error is not None + assert "Empty configuration" in str(error) + + +def test_validate_parser_config(): + """Test validation of parser configurations.""" + validator = ConfigurationValidator() + + # Test valid parser config + valid_parser_config = ParserConfig( + name="json_parser", + type="SAST", + output_format="json", + ) + is_valid, error = validator.validate_parser_config(valid_parser_config.model_dump()) + assert is_valid is True + assert error is None + + # Test invalid parser config + invalid_parser_config = {"name": "json_parser", "type": "json"} + is_valid, error = validator.validate_parser_config(invalid_parser_config) + assert is_valid is False + assert error is not None + + # Test None config + is_valid, error = validator.validate_parser_config(None) # type: ignore + assert is_valid is False + assert error is not None + + +def test_validate_configs_integration(): + validator = ConfigurationValidator() + # Test valid configs + valid_configs = [ + {"name": "scanner1", "type": "STATIC"}, + {"name": "scanner2", "type": "IAC"}, + ] + results = validator.validate_configs(valid_configs, ScannerPluginConfig) + assert all(result[0] for result in results) + assert all(result[1] is None for result in results) + + # Test invalid configs + invalid_configs = [{}, {"invalid": "config"}] + results = validator.validate_configs(invalid_configs, ScannerPluginConfig) + print(results) + assert all(not result[0] for result in results) + assert all(result[1] is not None for result in results) diff --git a/tests/scanners/test_bandit_scanner.py b/tests/scanners/test_bandit_scanner.py new file mode 100644 index 0000000..74997a3 --- /dev/null +++ b/tests/scanners/test_bandit_scanner.py @@ -0,0 +1,91 @@ +"""Test module for BanditScanner implementation.""" + +import pytest +from automated_security_helper.scanners.bandit_scanner import BanditScanner +from automated_security_helper.exceptions import ScannerError +from automated_security_helper.config.config import ScannerPluginConfig + + +def test_bandit_scanner_init(test_source_dir, test_output_dir): + """Test BanditScanner initialization.""" + scanner = BanditScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure() + assert scanner._config is None + scanner.configure(scanner.default_config) + assert scanner._config is not None + assert scanner._output_format == "json" + + +def test_bandit_scanner_configure(test_source_dir, test_output_dir): + """Test scanner configuration.""" + scanner = BanditScanner(source_dir=test_source_dir, output_dir=test_output_dir) + config = ScannerPluginConfig(name="bandit", output_format="text") + scanner.configure(config) + assert scanner._config == config + # We have to force standardization of output format at the scanner level so we + # know what format to parse from and what data is available for a particular scanner + assert scanner._output_format == "json" + + +def test_bandit_scanner_validate(test_source_dir, test_output_dir): + """Test validation logic.""" + scanner = BanditScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner._config = None + assert scanner.validate() is False + scanner._set_config(scanner.default_config) + assert scanner.validate() is True + + +def test_bandit_scanner_scan_json(mocker, test_source_dir, test_output_dir): + """Test scanning with JSON output.""" + + scanner = BanditScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure(scanner.default_config) + + # Mock subprocess execution + mock_run = mocker.patch.object(scanner, "_run_subprocess") + mock_parse_outputs = mocker.patch.object(scanner, "_parse_outputs") + mock_output = mocker.patch.object(scanner, "_output") + mock_output.return_value = ['{"results": []}'] + + result = scanner.scan("/test/path") + + # Verify command construction + mock_run.assert_called_once() + mock_parse_outputs.assert_called_once() + assert len(result.findings) == 0 + + +def test_bandit_scanner_scan_with_config(mocker, test_source_dir, test_output_dir): + """Test scanning with additional config options.""" + scanner = BanditScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure(scanner.default_config) + + # Mock subprocess execution + mock_run = mocker.patch.object(scanner, "_run_subprocess") + mock_parse_outputs = mocker.patch.object(scanner, "_parse_outputs") + mock_output = mocker.patch.object(scanner, "_output") + mock_output.return_value = ['{"results": []}'] + + scanner.scan("/test/path") + + # Verify command includes config options + mock_run.assert_called_once() + mock_parse_outputs.assert_called_once() + + +def test_bandit_scanner_scan_error(mocker, test_source_dir, test_output_dir): + """Test error handling during scan.""" + scanner = BanditScanner(source_dir=test_source_dir, output_dir=test_output_dir) + + # Mock subprocess failure + mock_run = mocker.patch.object(scanner, "_run_subprocess") + mock_run.side_effect = Exception("Command failed") + mock_errors = mocker.patch.object(scanner, "_errors") + mock_errors.return_value = ["Error: Invalid path"] + + with pytest.raises(ScannerError) as exc: + scanner.scan("/test/path") + + assert " " in str(exc.value) + assert "Bandit scan failed: Command failed" in str(exc.value) diff --git a/tests/scanners/test_cdk_nag_scanner.py b/tests/scanners/test_cdk_nag_scanner.py new file mode 100644 index 0000000..b0a5c1a --- /dev/null +++ b/tests/scanners/test_cdk_nag_scanner.py @@ -0,0 +1,121 @@ +"""Tests for the CDK Nag scanner.""" + +import glob +import os +import pytest +from automated_security_helper.scanners.cdk_nag_scanner import CDKNagScanner + +# def test_scanner_validate_script_missing(tmp_path): +# """Test validation fails when docker script is missing.""" +# scanner = CDKNagScanner() +# with pytest.raises(ScannerError, match=".*requires cdk-docker-execute.sh script"): +# scanner.validate() + +# def test_scanner_scan_parses_findings(tmp_path, mocker): +# """Test scanning parses findings from CSV output.""" +# scanner = CDKNagScanner() + +# # Mock subprocess execution +# mock_run = mocker.patch.object(scanner, "_run_cdk_synthesis") +# mock_findings = mocker.patch.object(scanner, "_parse_findings") +# mock_findings.return_value = [] + +# # Configure scanner +# scanner.configure(ScannerConfig(name="cdk-nag")) + +# # Run scan +# result = scanner.scan("/test/path") + +# # Verify mocks were called correctly +# mock_run.assert_called_once() +# mock_findings.assert_called_once() +# assert isinstance(result.findings, list) +# assert len(result.findings) == 0 + +# def test_scanner_parse_csv_findings(tmp_path): +# """Test parsing of CDK Nag CSV output.""" +# scanner = CDKNagScanner() +# scanner.configure(ScannerConfig(name="cdk-nag")) + +# # Create test working directory and CSV file +# scanner.work_dir = tmp_path / "test_cdk_nag_results" +# scanner.work_dir.mkdir() + +# # Create mock CSV output +# csv_content = "\n".join([ +# "Rule ID,Resource ID,Compliance,Exception Reason,Rule Level,Rule Info", +# "AwsSolutions-S1,test/bucket1,Non-Compliant,N/A,Error,Server access logs disabled", +# "AwsSolutions-S2,test/bucket1,Compliant,N/A,Error,Public access blocked" +# ]) +# csv_file = scanner.work_dir / "AwsSolutions-test-NagReport.csv" +# csv_file.write_text(csv_content) + +# # Parse findings and verify +# findings = scanner._parse_findings() +# assert len(findings) == 2 +# assert findings[0].rule_id == "AwsSolutions-S1" +# assert findings[0].resource_id == "test/bucket1" + +# def test_scanner_scan_error(tmp_path, mocker): +# """Test error handling during scan.""" +# scanner = CDKNagScanner() +# scanner.configure(ScannerConfig(name="cdk-nag")) + +# # Mock subprocess failure +# mock_run = mocker.patch.object(scanner, "_run_cdk_synthesis") +# mock_run.side_effect = Exception("CDK synthesis failed") + +# with pytest.raises(ScannerError, match="CDK synthesis failed"): +# scanner.scan("/test/path") + + +@pytest.fixture +def scanner(test_source_dir, test_output_dir): + """Create a CDK Nag scanner instance for testing.""" + return CDKNagScanner( + source_dir=test_source_dir, + output_dir=test_output_dir, + ) + + +def get_template_files(): + """Get all template JSON files from test data directory.""" + test_data_dir = os.path.join( + "tests", "test_data", "scanners", "cdk", "test.yaml_cdk_nag_results" + ) + return glob.glob(os.path.join(test_data_dir, "*.template.json")) + + +def get_csv_files(): + """Get all CSV result files from test data directory.""" + test_data_dir = os.path.join( + "tests", "test_data", "scanners", "cdk", "test.yaml_cdk_nag_results" + ) + return glob.glob(os.path.join(test_data_dir, "*NagReport.csv")) + + +@pytest.mark.parametrize("template_file", get_template_files()) +def test_scanner_scan_parses_findings( + mocker, template_file, test_source_dir, test_output_dir +): + """Test that scanner can parse findings from CDK nag output.""" + scanner = CDKNagScanner(source_dir=test_source_dir, output_dir=test_output_dir) + + report = scanner.scan(template_file) + assert report is not None + + +@pytest.mark.parametrize("csv_file", get_csv_files()) +def test_scanner_parse_csv_findings(csv_file, test_source_dir, test_output_dir): + """Test that scanner can parse findings from CSV file.""" + scanner = CDKNagScanner(source_dir=test_source_dir, output_dir=test_output_dir) + findings = scanner._parse_findings(csv_file) + + assert findings is not None + assert len(findings) > 0 + for finding in findings: + assert finding.id is not None + assert finding.severity is not None + assert finding.title is not None + assert finding.description is not None + assert finding.location is not None diff --git a/tests/scanners/test_jupyter_scanner.py b/tests/scanners/test_jupyter_scanner.py new file mode 100644 index 0000000..a8385e2 --- /dev/null +++ b/tests/scanners/test_jupyter_scanner.py @@ -0,0 +1,27 @@ +"""Unit tests for jupyter scanner module.""" + +from importlib.metadata import version +from pathlib import Path + + +from automated_security_helper.scanners.jupyter_scanner import JupyterScanner + + +def test_jupyter_scanner_initialization(test_source_dir: Path, test_output_dir: Path): + """Test basic initialization of JupyterScanner.""" + scanner = JupyterScanner(source_dir=test_source_dir, output_dir=test_output_dir) + assert scanner._default_config.type == "SAST" + assert scanner.tool_version == version("automated_security_helper") + + +def test_jupyter_scanner_scan(test_source_dir: Path, test_output_dir: Path): + """Test JupyterScanner scan method.""" + scanner = JupyterScanner(source_dir=test_source_dir, output_dir=test_output_dir) + result = scanner.scan(test_source_dir, test_output_dir) + + # Verify structure of returned results + assert isinstance(result, dict) + assert "findings" in result + assert "metadata" in result + assert isinstance(result["findings"], list) + assert isinstance(result["metadata"], dict) diff --git a/tests/scanners/test_scanner_factory.py b/tests/scanners/test_scanner_factory.py new file mode 100644 index 0000000..9087b5e --- /dev/null +++ b/tests/scanners/test_scanner_factory.py @@ -0,0 +1,170 @@ +"""Unit tests for scanner factory module.""" + +import pytest + +from automated_security_helper.config.config import ScannerPluginConfig +from automated_security_helper.scanners.bandit_scanner import BanditScanner +from automated_security_helper.scanners.cdk_nag_scanner import CDKNagScanner +from automated_security_helper.scanner_factory import ScannerFactory + + +def test_scanner_factory_initialization(): + """Test that factory properly initializes with default scanners.""" + factory = ScannerFactory() + + # Check internal state + assert hasattr(factory, "_scanners") + assert isinstance(factory._scanners, dict) + + # Check default scanners are registered + scanners = factory.available_scanners() + assert "bandit" in scanners + assert "cdknag" in scanners + assert scanners["bandit"] == BanditScanner + assert scanners["cdknag"] == CDKNagScanner + + +def test_scanner_factory_registration(): + """Test scanner registration functionality.""" + factory = ScannerFactory() + + # Register a new scanner + factory.register_scanner("test", BanditScanner) + assert "test" in factory._scanners + assert factory._scanners["test"] == BanditScanner + + # Verify it's available in scanners list + scanners = factory.available_scanners() + assert "test" in scanners + + +def test_scanner_factory_multiple_registrations(): + """Test handling of multiple scanner registrations.""" + factory = ScannerFactory() + + # First registration should succeed + factory.register_scanner("test", BanditScanner) + assert factory.get_scanner_class("test") == BanditScanner + + # Second registration should fail + with pytest.raises(ValueError, match="Scanner 'test' is already registered"): + factory.register_scanner("test", CDKNagScanner) + + +def test_create_bandit_scanner(test_source_dir, test_output_dir): + """Test creation of Bandit scanner instance.""" + factory = ScannerFactory() + + # Test with ScannerConfig + config = ScannerPluginConfig( + name="bandit", + type="SAST", + source_dir=test_source_dir, + output_dir=test_output_dir, + ) + scanner = factory.create_scanner( + config.name, config, test_source_dir, test_output_dir + ) + assert isinstance(scanner, BanditScanner) + assert scanner.name == "bandit" + assert scanner.type == "SAST" + + # Test with dict config + dict_config = { + "name": "bandit", + "type": "SAST", + "source_dir": str(test_source_dir), + "output_dir": str(test_output_dir), + } + scanner = factory.create_scanner( + dict_config["name"], dict_config, test_source_dir, test_output_dir + ) + assert isinstance(scanner, BanditScanner) + assert scanner.name == "bandit" + assert scanner.type == "SAST" + + +def test_create_cdk_nag_scanner(test_source_dir, test_output_dir): + """Test creation of CDK Nag scanner instance.""" + factory = ScannerFactory() + + # Test with ScannerConfig + config = ScannerPluginConfig( + name="cdknag", + type="IAC", + source_dir=test_source_dir, + output_dir=test_output_dir, + ) + scanner = factory.create_scanner( + config.name, config, test_source_dir, test_output_dir + ) + assert isinstance(scanner, CDKNagScanner) + assert scanner.name == "cdknag" + assert scanner.type == "IAC" + + # Test with dict config + dict_config = { + "name": "cdknag", + "type": "IAC", + "source_dir": str(test_source_dir), + "output_dir": str(test_output_dir), + } + scanner = factory.create_scanner( + dict_config["name"], dict_config, test_source_dir, test_output_dir + ) + assert isinstance(scanner, CDKNagScanner) + assert scanner.name == "cdknag" + assert scanner.type == "IAC" + + +def test_create_invalid_scanner(test_source_dir, test_output_dir): + """Test creating scanner with invalid configuration.""" + factory = ScannerFactory() + + # Test non-existent scanner + config = ScannerPluginConfig(name="invalid", type="UNKNOWN") + with pytest.raises(ValueError, match="Unable to determine scanner class"): + factory.create_scanner(config.name, config, test_source_dir, test_output_dir) + + +def test_scanner_factory_config_validation(test_source_dir, test_output_dir): + """Test scanner configuration validation.""" + factory = ScannerFactory() + + # Test with missing name in dict config + with pytest.raises(ValueError, match="Unable to determine scanner class"): + factory.create_scanner("test", {}, test_source_dir, test_output_dir) + + # Test with None config + with pytest.raises(ValueError, match="Unable to determine scanner class"): + factory.create_scanner("test", None, test_source_dir, test_output_dir) + + +def test_scanner_factory_type_lookup(): + """Test lookup of scanner classes by name.""" + factory = ScannerFactory() + + # Test valid lookups + assert factory.get_scanner_class("bandit") == BanditScanner + assert factory.get_scanner_class("cdknag") == CDKNagScanner + + # Test invalid lookup + with pytest.raises(ValueError, match="Unable to determine scanner class"): + factory.get_scanner_class("invalid") + + +def test_scanner_factory_default_scanners(test_source_dir, test_output_dir): + """Test that default scanners are properly registered and can be created.""" + factory = ScannerFactory() + + # Check available scanners + scanners = factory.available_scanners() + assert len(scanners) >= 2 # Should have at least 2 by default (Bandit and CDKNag) + assert all(scanner.__name__.endswith("Scanner") for scanner in scanners.values()) + + # Test creating all default scanners + for name, scanner_class in scanners.items(): + config = ScannerPluginConfig(name=name, type="SAST") + scanner = factory.create_scanner(name, config, test_source_dir, test_output_dir) + assert isinstance(scanner, scanner_class) + assert scanner.name == name diff --git a/tests/scanners/test_scanner_plugin.py b/tests/scanners/test_scanner_plugin.py new file mode 100644 index 0000000..5a94a6b --- /dev/null +++ b/tests/scanners/test_scanner_plugin.py @@ -0,0 +1,238 @@ +"""Unit tests for abstract scanner module.""" + +from pathlib import Path +from typing import Any, Dict, Optional + +import pytest + +from automated_security_helper.config.config import ScannerPluginConfig + +import logging + +from automated_security_helper.models.scanner_plugin import ( + ScannerPlugin, +) +from automated_security_helper.exceptions import ScannerError +from automated_security_helper.utils.log import get_logger +from tests.conftest import TEST_SOURCE_DIR, TEST_OUTPUT_DIR + + +class ConcreteScanner(ScannerPlugin): + """Concrete implementation of Scanner for testing.""" + + def __init__(self, source_dir: Path = None, output_dir: Path = None): + logger = get_logger("test_logger", level=logging.DEBUG) + source_dir = source_dir if source_dir else TEST_SOURCE_DIR + output_dir = output_dir if output_dir else TEST_OUTPUT_DIR + super().__init__(source_dir, output_dir, logger) + + @property + def default_config(self): + """Get default scanner configuration.""" + return ScannerPluginConfig( + name="test_scanner", + type="CUSTOM", + command="test_command", + source_dir=self.source_dir, + output_dir=self.output_dir, + ) + + def validate(self): + return True + + def scan(self, target: str, options: Optional[Dict[str, Any]] = None) -> None: + """Execute the actual scan operation.""" + self._pre_scan(target, options) + # Simulate a scan result + self._run_subprocess(["python", "--version"]) + + +def test_scanner_initialization(test_source_dir, test_output_dir): + """Test scanner initialization with config.""" + config = ScannerPluginConfig( + name="test_scanner", + command="pwd", + source_dir=test_source_dir, + output_dir=test_output_dir, + ) + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure(config) + assert scanner.name == "test_scanner" + assert scanner.config == config + assert scanner.options == {} + + +def test_scanner_initialization_none(test_source_dir, test_output_dir): + """Test scanner initialization with no config.""" + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + assert scanner.name == "test_scanner" + assert scanner.type == "" + assert scanner.config is not None + assert scanner.options == {} + + +def test_scanner_initialization_with_options(test_source_dir, test_output_dir): + """Test scanner initialization with custom options.""" + config = ScannerPluginConfig( + name="test_scanner", + type="SAST", + options={"severity": "high", "threshold": 5}, + source_dir=test_source_dir, + output_dir=test_output_dir, + ) + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure(config) + assert scanner.options == {"severity": "high", "threshold": 5} + + +def test_scanner_execution(test_source_dir, test_output_dir): + """Test basic scanner execution.""" + config = ScannerPluginConfig( + name="test_scanner", + type="SAST", + ) + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure(config) + + # Execute scan + scanner.scan("test/target") + + # Verify output was generated + assert ( + len([item for item in scanner.output if item is not None and item != ""]) == 1 + ) + assert "Python 3" in scanner.output[0] + + +def test_scanner_execution_with_options(test_source_dir, test_output_dir): + """Test scanner execution with options.""" + config = ScannerPluginConfig( + name="test_scanner", + type="SAST", + options={"severity": "high"}, + source_dir=test_source_dir, + output_dir=test_output_dir, + ) + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure(config) + + # Execute scan with additional options + scanner.scan("test/target", {"threshold": 5}) + + # Verify options were merged correctly + assert scanner.options["severity"] == "high" + assert scanner.options["threshold"] == 5 + + +def test_scanner_error_handling(test_source_dir, test_output_dir): + """Test scanner error handling.""" + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure( + ScannerPluginConfig( + name="test_scanner", + type="SAST", + source_dir=test_source_dir, + output_dir=test_output_dir, + ) + ) + + # Test with empty target + with pytest.raises(ScannerError, match="No target specified"): + scanner.scan(None) + + +def test_scanner_result_processing(test_source_dir, test_output_dir): + """Test scanner result handling.""" + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure(scanner.default_config) + scanner.scan("test/target") + assert ( + len([item for item in scanner.output if item is not None and item != ""]) == 1 + ) + assert not scanner.errors + + +def test_scanner_metadata_handling(test_source_dir, test_output_dir): + """Test scanner metadata access.""" + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure( + ScannerPluginConfig(name="test_scanner", type="SAST", options={"key": "value"}) + ) + assert scanner.name == "test_scanner" + assert scanner.type == "SAST" + assert scanner.options == { + "severity": "high", + "threshold": 5, + "key": "value", + } + + +def test_scanner_command_execution(test_source_dir, test_output_dir): + """Test scanner subprocess execution.""" + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure( + ScannerPluginConfig( + name="test_scanner", + type="SAST", + ) + ) + scanner._run_subprocess(["echo", "test"]) + assert ( + len([item for item in scanner.output if item is not None and item != ""]) == 1 + ) + assert scanner.output[0] == "test\n" + + +def test_scanner_command_execution_error(test_source_dir, test_output_dir): + """Test scanner subprocess error handling.""" + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + scanner.configure( + ScannerPluginConfig( + name="test_scanner", + type="SAST", + source_dir=test_source_dir, + output_dir=test_output_dir, + ) + ) + with pytest.raises(ScannerError): + scanner._run_subprocess(["nonexistent_command"]) + + +def test_scanner_with_custom_config(test_source_dir, test_output_dir): + """Test scanner with custom configuration.""" + scanner = ConcreteScanner(source_dir=test_source_dir, output_dir=test_output_dir) + config = ScannerPluginConfig( + name="custom_scanner", + type="CUSTOM", + options={"level": "high", "include": ["*.py"], "exclude": ["test/*"]}, + source_dir=test_source_dir, + output_dir=test_output_dir, + ) + scanner.configure(config) + assert scanner.name == "custom_scanner" + assert scanner.type == "CUSTOM" + assert scanner.options == { + "severity": "high", + "threshold": 5, + "key": "value", + "level": "high", + "include": ["*.py"], + "exclude": ["test/*"], + } + + +def test_scanner_validate_config(): + """Test scanner configuration validation.""" + config = {"name": "invalid", "command": "pwd"} + scanner = ConcreteScanner() + scanner.configure(config) + assert scanner.name == "invalid" + assert scanner.config.command == "pwd" + assert scanner.options == { + "severity": "high", + "threshold": 5, + "key": "value", + "level": "high", + "include": ["*.py"], + "exclude": ["test/*"], + } diff --git a/tests/schemas/test_generate_schemas.py b/tests/schemas/test_generate_schemas.py new file mode 100644 index 0000000..b6c9245 --- /dev/null +++ b/tests/schemas/test_generate_schemas.py @@ -0,0 +1,20 @@ +"""Unit tests for schema generation module.""" + +from automated_security_helper.schemas.generate_schemas import generate_schemas + + +def test_generate_json_schema(): + # Test generating schema for a single model + schema = generate_schemas("dict") + assert isinstance(schema, dict) + assert "ASHConfig" in schema + assert "ASHARPModel" in schema + + # Validate schema structure + assert "type" in schema["ASHConfig"] + assert "properties" in schema["ASHConfig"] + assert isinstance(schema["ASHConfig"]["properties"], dict) + + assert "type" in schema["ASHARPModel"] + assert "properties" in schema["ASHARPModel"] + assert isinstance(schema["ASHARPModel"]["properties"], dict) diff --git a/tests/test_data/__init__.py b/tests/test_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_data/scanners/bandit/src.ash_output.work.json b/tests/test_data/scanners/bandit/src.ash_output.work.json new file mode 100644 index 0000000..690e98e --- /dev/null +++ b/tests/test_data/scanners/bandit/src.ash_output.work.json @@ -0,0 +1,180 @@ +{ + "errors": [], + "generated_at": "2025-04-01T23:03:58Z", + "metrics": { + "/src/ash_output/work/bla/dummy1.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 0, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/ash_output/work/bla2/dummy2.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 0, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/ash_output/work/ipynb.ipynb-converted.py": { + "CONFIDENCE.HIGH": 6, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 2, + "SEVERITY.MEDIUM": 4, + "SEVERITY.UNDEFINED": 0, + "loc": 125, + "nosec": 0, + "skipped_tests": 0 + }, + "_totals": { + "CONFIDENCE.HIGH": 6, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 2, + "SEVERITY.MEDIUM": 4, + "SEVERITY.UNDEFINED": 0, + "loc": 125, + "nosec": 0, + "skipped_tests": 0 + } + }, + "results": [ + { + "code": "197 \n198 import dill\n199 import StringIO\n", + "col_offset": 0, + "end_col_offset": 11, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "LOW", + "issue_text": "Consider possible security implications associated with dill module.", + "line_number": 198, + "line_range": [ + 198 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_imports.html#b403-import-pickle", + "test_id": "B403", + "test_name": "blacklist" + }, + { + "code": "202 pick = dill.dumps({'a': 'b', 'c': 'd'})\n203 print(dill.loads(pick))\n204 \n", + "col_offset": 6, + "end_col_offset": 22, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 203, + "line_range": [ + 203 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + }, + { + "code": "207 file_obj.seek(0)\n208 print(dill.load(file_obj))\n209 \n", + "col_offset": 6, + "end_col_offset": 25, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 208, + "line_range": [ + 208 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + }, + { + "code": "221 \n222 import dill\n223 import StringIO\n", + "col_offset": 0, + "end_col_offset": 11, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "LOW", + "issue_text": "Consider possible security implications associated with dill module.", + "line_number": 222, + "line_range": [ + 222 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_imports.html#b403-import-pickle", + "test_id": "B403", + "test_name": "blacklist" + }, + { + "code": "226 pick = dill.dumps({'a': 'b', 'c': 'd'})\n227 print(dill.loads(pick))\n228 \n", + "col_offset": 6, + "end_col_offset": 22, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 227, + "line_range": [ + 227 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + }, + { + "code": "231 file_obj.seek(0)\n232 print(dill.load(file_obj))\n233 \n", + "col_offset": 6, + "end_col_offset": 25, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 232, + "line_range": [ + 232 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + } + ] +} diff --git a/tests/test_data/scanners/bandit/src.json b/tests/test_data/scanners/bandit/src.json new file mode 100644 index 0000000..e512a3b --- /dev/null +++ b/tests/test_data/scanners/bandit/src.json @@ -0,0 +1,443 @@ +{ + "errors": [], + "generated_at": "2025-04-01T23:03:58Z", + "metrics": { + "/src/ash_output/work/bla/dummy1.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 0, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/ash_output/work/bla2/dummy2.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 0, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/ash_output/work/ipynb.ipynb-converted.py": { + "CONFIDENCE.HIGH": 6, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 2, + "SEVERITY.MEDIUM": 4, + "SEVERITY.UNDEFINED": 0, + "loc": 125, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/cdk/app copy.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 10, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/cdk/app.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 10, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/cdk/app/__init__.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 0, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/cdk/app/app_stack.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 13, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/cdk/replace.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 28, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/cdk/tests/__init__.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 0, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/cdk/tests/unit/__init__.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 0, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/cdk/tests/unit/test_app_stack.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 7, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/cfn_and_python/py-test-dll.py": { + "CONFIDENCE.HIGH": 3, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 1, + "SEVERITY.MEDIUM": 2, + "SEVERITY.UNDEFINED": 0, + "loc": 10, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/dummy2.py": { + "CONFIDENCE.HIGH": 0, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 0, + "SEVERITY.MEDIUM": 0, + "SEVERITY.UNDEFINED": 0, + "loc": 0, + "nosec": 0, + "skipped_tests": 0 + }, + "/src/py-test-dll.py": { + "CONFIDENCE.HIGH": 3, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 1, + "SEVERITY.MEDIUM": 2, + "SEVERITY.UNDEFINED": 0, + "loc": 10, + "nosec": 0, + "skipped_tests": 0 + }, + "_totals": { + "CONFIDENCE.HIGH": 12, + "CONFIDENCE.LOW": 0, + "CONFIDENCE.MEDIUM": 0, + "CONFIDENCE.UNDEFINED": 0, + "SEVERITY.HIGH": 0, + "SEVERITY.LOW": 4, + "SEVERITY.MEDIUM": 8, + "SEVERITY.UNDEFINED": 0, + "loc": 213, + "nosec": 0, + "skipped_tests": 0 + } + }, + "results": [ + { + "code": "197 \n198 import dill\n199 import StringIO\n", + "col_offset": 0, + "end_col_offset": 11, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "LOW", + "issue_text": "Consider possible security implications associated with dill module.", + "line_number": 198, + "line_range": [ + 198 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_imports.html#b403-import-pickle", + "test_id": "B403", + "test_name": "blacklist" + }, + { + "code": "202 pick = dill.dumps({'a': 'b', 'c': 'd'})\n203 print(dill.loads(pick))\n204 \n", + "col_offset": 6, + "end_col_offset": 22, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 203, + "line_range": [ + 203 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + }, + { + "code": "207 file_obj.seek(0)\n208 print(dill.load(file_obj))\n209 \n", + "col_offset": 6, + "end_col_offset": 25, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 208, + "line_range": [ + 208 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + }, + { + "code": "221 \n222 import dill\n223 import StringIO\n", + "col_offset": 0, + "end_col_offset": 11, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "LOW", + "issue_text": "Consider possible security implications associated with dill module.", + "line_number": 222, + "line_range": [ + 222 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_imports.html#b403-import-pickle", + "test_id": "B403", + "test_name": "blacklist" + }, + { + "code": "226 pick = dill.dumps({'a': 'b', 'c': 'd'})\n227 print(dill.loads(pick))\n228 \n", + "col_offset": 6, + "end_col_offset": 22, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 227, + "line_range": [ + 227 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + }, + { + "code": "231 file_obj.seek(0)\n232 print(dill.load(file_obj))\n233 \n", + "col_offset": 6, + "end_col_offset": 25, + "filename": "/src/ash_output/work/ipynb.ipynb-converted.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 232, + "line_range": [ + 232 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + }, + { + "code": "1 import dill\n2 import StringIO\n3 \n", + "col_offset": 0, + "end_col_offset": 11, + "filename": "/src/cfn_and_python/py-test-dll.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "LOW", + "issue_text": "Consider possible security implications associated with dill module.", + "line_number": 1, + "line_range": [ + 1 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_imports.html#b403-import-pickle", + "test_id": "B403", + "test_name": "blacklist" + }, + { + "code": "5 pick = dill.dumps({'a': 'b', 'c': 'd'})\n6 print(dill.loads(pick))\n7 \n", + "col_offset": 6, + "end_col_offset": 22, + "filename": "/src/cfn_and_python/py-test-dll.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 6, + "line_range": [ + 6 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + }, + { + "code": "10 file_obj.seek(0)\n11 print(dill.load(file_obj))\n12 \n", + "col_offset": 6, + "end_col_offset": 25, + "filename": "/src/cfn_and_python/py-test-dll.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 11, + "line_range": [ + 11 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + }, + { + "code": "1 import dill\n2 import StringIO\n3 \n", + "col_offset": 0, + "end_col_offset": 11, + "filename": "/src/py-test-dll.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "LOW", + "issue_text": "Consider possible security implications associated with dill module.", + "line_number": 1, + "line_range": [ + 1 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_imports.html#b403-import-pickle", + "test_id": "B403", + "test_name": "blacklist" + }, + { + "code": "5 pick = dill.dumps({'a': 'b', 'c': 'd'})\n6 print(dill.loads(pick))\n7 \n", + "col_offset": 6, + "end_col_offset": 22, + "filename": "/src/py-test-dll.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 6, + "line_range": [ + 6 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + }, + { + "code": "10 file_obj.seek(0)\n11 print(dill.load(file_obj))\n12 \n", + "col_offset": 6, + "end_col_offset": 25, + "filename": "/src/py-test-dll.py", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 502, + "link": "https://cwe.mitre.org/data/definitions/502.html" + }, + "issue_severity": "MEDIUM", + "issue_text": "Pickle and modules that wrap it can be unsafe when used to deserialize untrusted data, possible security issue.", + "line_number": 11, + "line_range": [ + 11 + ], + "more_info": "https://bandit.readthedocs.io/en/1.8.3/blacklists/blacklist_calls.html#b301-pickle", + "test_id": "B301", + "test_name": "blacklist" + } + ] +} diff --git a/tests/test_data/scanners/cdk/cfn-to-cdk.template.json_cdk_nag_results/AwsSolutions-cfn-and-python-ash-cf2cdk-output-test-yaml-cdk-nag-results-cfn-to-cdk-template-json-NagReport.csv b/tests/test_data/scanners/cdk/cfn-to-cdk.template.json_cdk_nag_results/AwsSolutions-cfn-and-python-ash-cf2cdk-output-test-yaml-cdk-nag-results-cfn-to-cdk-template-json-NagReport.csv new file mode 100644 index 0000000..576c4a9 --- /dev/null +++ b/tests/test_data/scanners/cdk/cfn-to-cdk.template.json_cdk_nag_results/AwsSolutions-cfn-and-python-ash-cf2cdk-output-test-yaml-cdk-nag-results-cfn-to-cdk-template-json-NagReport.csv @@ -0,0 +1,5 @@ +Rule ID,Resource ID,Compliance,Exception Reason,Rule Level,Rule Info +"AwsSolutions-S1","cfn-and-python-ash-cf2cdk-output-test-yaml-cdk-nag-results-cfn-to-cdk-template-json/--src--cfn_and_python--ash_cf2cdk_output--test.yaml_cdk_nag_results--cfn-to-cdk.template.json/S3Bucket2","Non-Compliant","N/A","Error","The S3 Bucket has server access logs disabled." +"AwsSolutions-S2","cfn-and-python-ash-cf2cdk-output-test-yaml-cdk-nag-results-cfn-to-cdk-template-json/--src--cfn_and_python--ash_cf2cdk_output--test.yaml_cdk_nag_results--cfn-to-cdk.template.json/S3Bucket2","Compliant","N/A","Error","The S3 Bucket does not have public access restricted and blocked." +"AwsSolutions-S5","cfn-and-python-ash-cf2cdk-output-test-yaml-cdk-nag-results-cfn-to-cdk-template-json/--src--cfn_and_python--ash_cf2cdk_output--test.yaml_cdk_nag_results--cfn-to-cdk.template.json/S3Bucket2","Compliant","N/A","Error","The S3 static website bucket either has an open world bucket policy or does not use a CloudFront Origin Access Identity (OAI) in the bucket policy for limited getObject and/or putObject permissions." +"AwsSolutions-S10","cfn-and-python-ash-cf2cdk-output-test-yaml-cdk-nag-results-cfn-to-cdk-template-json/--src--cfn_and_python--ash_cf2cdk_output--test.yaml_cdk_nag_results--cfn-to-cdk.template.json/S3Bucket2","Non-Compliant","N/A","Error","The S3 Bucket or bucket policy does not require requests to use SSL." diff --git a/tests/test_data/scanners/cdk/secure-s3-template/secure-s3-template.yaml b/tests/test_data/scanners/cdk/secure-s3-template/secure-s3-template.yaml new file mode 100644 index 0000000..f0dec49 --- /dev/null +++ b/tests/test_data/scanners/cdk/secure-s3-template/secure-s3-template.yaml @@ -0,0 +1,83 @@ +AWSTemplateFormatVersion: '2010-09-09' +Resources: + S3Bucket2: + Type: AWS::S3::Bucket + Properties: + LoggingConfiguration: + DestinationBucketName: !Ref LoggingBucket + LogFilePrefix: 'S3Bucket2-logs/' + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + + S3Bucket2BucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref S3Bucket2 + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: AllowSSLRequestsOnly + Effect: Deny + Principal: '*' + Action: s3:* + Resource: + - !Sub '${S3Bucket2.Arn}/*' + - !GetAtt S3Bucket2.Arn + Condition: + Bool: + aws:SecureTransport: false + + S3Bucket3: + Type: AWS::S3::Bucket + Properties: + LoggingConfiguration: + DestinationBucketName: !Ref LoggingBucket + LogFilePrefix: 'S3Bucket3-logs/' + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + + S3Bucket3BucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref S3Bucket3 + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: AllowSSLRequestsOnly + Effect: Deny + Principal: '*' + Action: s3:* + Resource: + - !Sub '${S3Bucket3.Arn}/*' + - !GetAtt S3Bucket3.Arn + Condition: + Bool: + aws:SecureTransport: false + + LoggingBucket: + Type: AWS::S3::Bucket + Properties: + AccessControl: LogDeliveryWrite + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true \ No newline at end of file diff --git a/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/AwsSolutions-cfn-and-python-test-yaml-NagReport.csv b/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/AwsSolutions-cfn-and-python-test-yaml-NagReport.csv new file mode 100644 index 0000000..c073e99 --- /dev/null +++ b/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/AwsSolutions-cfn-and-python-test-yaml-NagReport.csv @@ -0,0 +1,5 @@ +Rule ID,Resource ID,Compliance,Exception Reason,Rule Level,Rule Info +"AwsSolutions-S1","cfn-and-python-test-yaml/--src--cfn_and_python--test.yaml/S3Bucket2","Non-Compliant","N/A","Error","The S3 Bucket has server access logs disabled." +"AwsSolutions-S2","cfn-and-python-test-yaml/--src--cfn_and_python--test.yaml/S3Bucket2","Compliant","N/A","Error","The S3 Bucket does not have public access restricted and blocked." +"AwsSolutions-S5","cfn-and-python-test-yaml/--src--cfn_and_python--test.yaml/S3Bucket2","Compliant","N/A","Error","The S3 static website bucket either has an open world bucket policy or does not use a CloudFront Origin Access Identity (OAI) in the bucket policy for limited getObject and/or putObject permissions." +"AwsSolutions-S10","cfn-and-python-test-yaml/--src--cfn_and_python--test.yaml/S3Bucket2","Non-Compliant","N/A","Error","The S3 Bucket or bucket policy does not require requests to use SSL." diff --git a/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/AwsSolutions-test-yaml-NagReport.csv b/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/AwsSolutions-test-yaml-NagReport.csv new file mode 100644 index 0000000..63cd981 --- /dev/null +++ b/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/AwsSolutions-test-yaml-NagReport.csv @@ -0,0 +1,5 @@ +Rule ID,Resource ID,Compliance,Exception Reason,Rule Level,Rule Info +"AwsSolutions-S1","test-yaml/--src--test.yaml/S3Bucket3","Non-Compliant","N/A","Error","The S3 Bucket has server access logs disabled." +"AwsSolutions-S2","test-yaml/--src--test.yaml/S3Bucket3","Compliant","N/A","Error","The S3 Bucket does not have public access restricted and blocked." +"AwsSolutions-S5","test-yaml/--src--test.yaml/S3Bucket3","Compliant","N/A","Error","The S3 static website bucket either has an open world bucket policy or does not use a CloudFront Origin Access Identity (OAI) in the bucket policy for limited getObject and/or putObject permissions." +"AwsSolutions-S10","test-yaml/--src--test.yaml/S3Bucket3","Non-Compliant","N/A","Error","The S3 Bucket or bucket policy does not require requests to use SSL." diff --git a/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/cfn-and-python-test-yaml.template.json b/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/cfn-and-python-test-yaml.template.json new file mode 100644 index 0000000..05cd6d2 --- /dev/null +++ b/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/cfn-and-python-test-yaml.template.json @@ -0,0 +1,318 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Conditions": { + "CDKMetadataAvailable": { + "Fn::Or": [ + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "af-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-3" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-4" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-northwest-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-south-2" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "il-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "sa-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-1" + ] + } + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-2" + ] + } + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", + "Type": "AWS::SSM::Parameter::Value" + } + }, + "Resources": { + "CDKMetadata": { + "Condition": "CDKMetadataAvailable", + "Metadata": { + "aws:cdk:path": "cfn-and-python-test-yaml/CDKMetadata/Default" + }, + "Properties": { + "Analytics": "v2:deflate64:H4sIAAAAAAAA/yXIMQ6DMAxA0bOwJybAUFjLxNoeAKVOkEyCI+GkDBV3RxXTf/otNP0DTGUP0eiCjvSB3ztbDApjKm5J+2YzJZ6JMRbnYVx4uqnsIbN0//MsGHw+FSfnYZX62xpoBjDVKkR6L5xp8/C6ewHxvoHpdAAAAA==" + }, + "Type": "AWS::CDK::Metadata" + }, + "S3Bucket2": { + "Type": "AWS::S3::Bucket" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} diff --git a/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/test-yaml.template.json b/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/test-yaml.template.json new file mode 100644 index 0000000..95bcddd --- /dev/null +++ b/tests/test_data/scanners/cdk/test.yaml_cdk_nag_results/test-yaml.template.json @@ -0,0 +1,318 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Conditions": { + "CDKMetadataAvailable": { + "Fn::Or": [ + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "af-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-northeast-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-south-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-3" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ap-southeast-4" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "ca-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "cn-northwest-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-central-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-north-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-south-2" + ] + } + ] + }, + { + "Fn::Or": [ + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "eu-west-3" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "il-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-central-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "me-south-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "sa-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-1" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-2" + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-1" + ] + } + ] + }, + { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-west-2" + ] + } + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", + "Type": "AWS::SSM::Parameter::Value" + } + }, + "Resources": { + "CDKMetadata": { + "Condition": "CDKMetadataAvailable", + "Metadata": { + "aws:cdk:path": "test-yaml/CDKMetadata/Default" + }, + "Properties": { + "Analytics": "v2:deflate64:H4sIAAAAAAAA/yXIMQ6DMAxA0bOwJybAUFjLxNoeAKVOkEyCI+GkDBV3RxXTf/otNP0DTGUP0eiCjvSB3ztbDApjKm5J+2YzJZ6JMRbnYVx4uqnsIbN0//MsGHw+FSfnYZX62xpoBjDVKkR6L5xp8/C6ewHxvoHpdAAAAA==" + }, + "Type": "AWS::CDK::Metadata" + }, + "S3Bucket3": { + "Type": "AWS::S3::Bucket" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} diff --git a/tests/test_data/scanners/cfn_nag/src.cfn_and_python.test.yaml.json b/tests/test_data/scanners/cfn_nag/src.cfn_and_python.test.yaml.json new file mode 100644 index 0000000..6b31279 --- /dev/null +++ b/tests/test_data/scanners/cfn_nag/src.cfn_and_python.test.yaml.json @@ -0,0 +1,55 @@ +[ + { + "file_results": { + "failure_count": 0, + "violations": [ + { + "element_types": [ + "resource" + ], + "id": "W51", + "line_numbers": [ + 5 + ], + "logical_resource_ids": [ + "S3Bucket2" + ], + "message": "S3 bucket should likely have a bucket policy", + "name": "MissingBucketPolicyRule", + "type": "WARN" + }, + { + "element_types": [ + "resource" + ], + "id": "W35", + "line_numbers": [ + 5 + ], + "logical_resource_ids": [ + "S3Bucket2" + ], + "message": "S3 Bucket should have access logging configured", + "name": "S3BucketAccessLoggingRule", + "type": "WARN" + }, + { + "element_types": [ + "resource" + ], + "id": "W41", + "line_numbers": [ + 5 + ], + "logical_resource_ids": [ + "S3Bucket2" + ], + "message": "S3 Bucket should have encryption option set", + "name": "S3BucketEncryptionSetRule", + "type": "WARN" + } + ] + }, + "filename": "/src/cfn_and_python/test.yaml" + } +] diff --git a/tests/test_data/scanners/cfn_nag/src.test.yaml.json b/tests/test_data/scanners/cfn_nag/src.test.yaml.json new file mode 100644 index 0000000..03750c7 --- /dev/null +++ b/tests/test_data/scanners/cfn_nag/src.test.yaml.json @@ -0,0 +1,55 @@ +[ + { + "file_results": { + "failure_count": 0, + "violations": [ + { + "element_types": [ + "resource" + ], + "id": "W51", + "line_numbers": [ + 5 + ], + "logical_resource_ids": [ + "S3Bucket3" + ], + "message": "S3 bucket should likely have a bucket policy", + "name": "MissingBucketPolicyRule", + "type": "WARN" + }, + { + "element_types": [ + "resource" + ], + "id": "W35", + "line_numbers": [ + 5 + ], + "logical_resource_ids": [ + "S3Bucket3" + ], + "message": "S3 Bucket should have access logging configured", + "name": "S3BucketAccessLoggingRule", + "type": "WARN" + }, + { + "element_types": [ + "resource" + ], + "id": "W41", + "line_numbers": [ + 5 + ], + "logical_resource_ids": [ + "S3Bucket3" + ], + "message": "S3 Bucket should have encryption option set", + "name": "S3BucketEncryptionSetRule", + "type": "WARN" + } + ] + }, + "filename": "/src/test.yaml" + } +] diff --git a/tests/test_data/scanners/checkov/src.cfn_and_python.test.yaml.json b/tests/test_data/scanners/checkov/src.cfn_and_python.test.yaml.json new file mode 100644 index 0000000..87a238c --- /dev/null +++ b/tests/test_data/scanners/checkov/src.cfn_and_python.test.yaml.json @@ -0,0 +1,443 @@ +{ + "check_type": "cloudformation", + "results": { + "failed_checks": [ + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_21", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3IgnorePublicACLs", + "check_id": "CKV_AWS_55", + "check_len": null, + "check_name": "Ensure S3 bucket has ignore public ACLs enabled", + "check_result": { + "evaluated_keys": [ + "Properties/PublicAccessBlockConfiguration/IgnorePublicAcls" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket2:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/cfn_and_python/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/cfn_and_python/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/bc-aws-s3-21", + "repo_file_path": "/cfn_and_python/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket2", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_16", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3Versioning", + "check_id": "CKV_AWS_21", + "check_len": null, + "check_name": "Ensure the S3 bucket has versioning enabled", + "check_result": { + "evaluated_keys": [ + "Properties/VersioningConfiguration/Status" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket2:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/cfn_and_python/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/cfn_and_python/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-16-enable-versioning", + "repo_file_path": "/cfn_and_python/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket2", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_19", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3BlockPublicACLs", + "check_id": "CKV_AWS_53", + "check_len": null, + "check_name": "Ensure S3 bucket has block public ACLs enabled", + "check_result": { + "evaluated_keys": [ + "Properties/PublicAccessBlockConfiguration/BlockPublicAcls" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket2:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/cfn_and_python/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/cfn_and_python/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/bc-aws-s3-19", + "repo_file_path": "/cfn_and_python/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket2", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_20", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3BlockPublicPolicy", + "check_id": "CKV_AWS_54", + "check_len": null, + "check_name": "Ensure S3 bucket has block public policy enabled", + "check_result": { + "evaluated_keys": [ + "Properties/PublicAccessBlockConfiguration/BlockPublicPolicy" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket2:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/cfn_and_python/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/cfn_and_python/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/bc-aws-s3-20", + "repo_file_path": "/cfn_and_python/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket2", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_13", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3AccessLogs", + "check_id": "CKV_AWS_18", + "check_len": null, + "check_name": "Ensure the S3 bucket has access logging enabled", + "check_result": { + "evaluated_keys": [ + "Properties/LoggingConfiguration" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket2:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/cfn_and_python/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/cfn_and_python/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-13-enable-logging", + "repo_file_path": "/cfn_and_python/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket2", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_22", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3RestrictPublicBuckets", + "check_id": "CKV_AWS_56", + "check_len": null, + "check_name": "Ensure S3 bucket has RestrictPublicBuckets enabled", + "check_result": { + "evaluated_keys": [ + "Properties/PublicAccessBlockConfiguration/RestrictPublicBuckets" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket2:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/cfn_and_python/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/cfn_and_python/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/bc-aws-s3-22", + "repo_file_path": "/cfn_and_python/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket2", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + } + ], + "parsing_errors": [], + "passed_checks": [ + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_14", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3Encryption", + "check_id": "CKV_AWS_19", + "check_len": null, + "check_name": "Ensure the S3 bucket has server-side-encryption enabled", + "check_result": { + "evaluated_keys": [ + "Properties/BucketEncryption/ServerSideEncryptionConfiguration/[0]/ServerSideEncryptionByDefault/SSEAlgorithm" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + " S3Bucket2:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/cfn_and_python/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/cfn_and_python/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-14-data-encrypted-at-rest", + "repo_file_path": "/cfn_and_python/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket2", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_1", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3PublicACLRead", + "check_id": "CKV_AWS_20", + "check_len": null, + "check_name": "Ensure the S3 bucket does not allow READ permissions to everyone", + "check_result": { + "evaluated_keys": [ + "Properties/AccessControl" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + " S3Bucket2:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/cfn_and_python/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/cfn_and_python/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-1-acl-read-permissions-everyone", + "repo_file_path": "/cfn_and_python/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket2", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_2", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3PublicACLWrite", + "check_id": "CKV_AWS_57", + "check_len": null, + "check_name": "Ensure the S3 bucket does not allow WRITE permissions to everyone", + "check_result": { + "evaluated_keys": [ + "Properties/AccessControl" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + " S3Bucket2:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/cfn_and_python/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/cfn_and_python/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-2-acl-write-permissions-everyone", + "repo_file_path": "/cfn_and_python/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket2", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + } + ], + "skipped_checks": [] + }, + "summary": { + "checkov_version": "3.2.396", + "failed": 6, + "parsing_errors": 0, + "passed": 3, + "resource_count": 1, + "skipped": 0 + }, + "url": "Add an api key '--bc-api-key ' to see more detailed insights via https://bridgecrew.cloud" +} diff --git a/tests/test_data/scanners/checkov/src.js.docker.Dockerfile.json b/tests/test_data/scanners/checkov/src.js.docker.Dockerfile.json new file mode 100644 index 0000000..2fe46de --- /dev/null +++ b/tests/test_data/scanners/checkov/src.js.docker.Dockerfile.json @@ -0,0 +1,2984 @@ +{ + "check_type": "dockerfile", + "results": { + "failed_checks": [ + { + "bc_category": null, + "bc_check_id": "BC_DKR_5", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.dockerfile.checks.AddExists", + "check_id": "CKV_DOCKER_4", + "check_len": null, + "check_name": "Ensure that COPY is used instead of ADD in Dockerfiles", + "check_result": { + "result": "FAILED", + "results_configuration": [ + { + "content": "ADD confd /etc/confd/\n", + "endline": 6, + "instruction": "ADD", + "startline": 6, + "value": "confd /etc/confd/" + }, + { + "content": "ADD bin/boot.sh /boot.sh\n", + "endline": 9, + "instruction": "ADD", + "startline": 9, + "value": "bin/boot.sh /boot.sh" + } + ] + }, + "code_block": [ + [ + 7, + "ADD confd /etc/confd/\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 7, + 7 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-that-copy-is-used-instead-of-add-in-dockerfiles", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.ADD", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_DKR_5", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.dockerfile.checks.AddExists", + "check_id": "CKV_DOCKER_4", + "check_len": null, + "check_name": "Ensure that COPY is used instead of ADD in Dockerfiles", + "check_result": { + "result": "FAILED", + "results_configuration": [ + { + "content": "ADD confd /etc/confd/\n", + "endline": 6, + "instruction": "ADD", + "startline": 6, + "value": "confd /etc/confd/" + }, + { + "content": "ADD bin/boot.sh /boot.sh\n", + "endline": 9, + "instruction": "ADD", + "startline": 9, + "value": "bin/boot.sh /boot.sh" + } + ] + }, + "code_block": [ + [ + 10, + "ADD bin/boot.sh /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 10, + 10 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-that-copy-is-used-instead-of-add-in-dockerfiles", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.ADD", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_DKR_2", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.dockerfile.checks.HealthcheckExists", + "check_id": "CKV_DOCKER_2", + "check_len": null, + "check_name": "Ensure that HEALTHCHECK instructions have been added to container images", + "check_result": { + "result": "FAILED", + "results_configuration": null + }, + "code_block": [ + [ + 1, + "FROM nginx:stable-alpine\n" + ], + [ + 2, + "\n" + ], + [ + 3, + "# confd\n" + ], + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ], + [ + 7, + "ADD confd /etc/confd/\n" + ], + [ + 8, + "\n" + ], + [ + 9, + "# boot script\n" + ], + [ + 10, + "ADD bin/boot.sh /boot.sh\n" + ], + [ + 11, + "RUN chmod +x /boot.sh\n" + ], + [ + 12, + "\n" + ], + [ + 13, + "# nginx\n" + ], + [ + 14, + "COPY nginx/default.conf /etc/nginx/conf.d/default.conf\n" + ], + [ + 15, + "COPY www /usr/share/nginx/html\n" + ], + [ + 16, + "\n" + ], + [ + 17, + "CMD [\"/boot.sh\"]" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 1, + 17 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-that-healthcheck-instructions-have-been-added-to-container-images", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_DKR_3", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.dockerfile.checks.UserExists", + "check_id": "CKV_DOCKER_3", + "check_len": null, + "check_name": "Ensure that a user for the container has been created", + "check_result": { + "result": "FAILED", + "results_configuration": null + }, + "code_block": [ + [ + 1, + "FROM nginx:stable-alpine\n" + ], + [ + 2, + "\n" + ], + [ + 3, + "# confd\n" + ], + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ], + [ + 7, + "ADD confd /etc/confd/\n" + ], + [ + 8, + "\n" + ], + [ + 9, + "# boot script\n" + ], + [ + 10, + "ADD bin/boot.sh /boot.sh\n" + ], + [ + 11, + "RUN chmod +x /boot.sh\n" + ], + [ + 12, + "\n" + ], + [ + 13, + "# nginx\n" + ], + [ + 14, + "COPY nginx/default.conf /etc/nginx/conf.d/default.conf\n" + ], + [ + 15, + "COPY www /usr/share/nginx/html\n" + ], + [ + 16, + "\n" + ], + [ + 17, + "CMD [\"/boot.sh\"]" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 1, + 17 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-that-a-user-for-the-container-has-been-created", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + } + ], + "parsing_errors": [], + "passed_checks": [ + { + "bc_category": null, + "bc_check_id": "BC_DKR_7", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.dockerfile.checks.ReferenceLatestTag", + "check_id": "CKV_DOCKER_7", + "check_len": null, + "check_name": "Ensure the base image uses a non latest version tag", + "check_result": { + "result": "PASSED", + "results_configuration": [ + { + "content": "FROM nginx:stable-alpine\n", + "endline": 0, + "instruction": "FROM", + "startline": 0, + "value": "nginx:stable-alpine" + } + ] + }, + "code_block": [ + [ + 1, + "FROM nginx:stable-alpine\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 1, + 1 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-the-base-image-uses-a-non-latest-version-tag", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.FROM", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_DKR_GENERAL_9", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.dockerfile.checks.AliasIsUnique", + "check_id": "CKV_DOCKER_11", + "check_len": null, + "check_name": "Ensure From Alias are unique for multistage builds.", + "check_result": { + "result": "PASSED", + "results_configuration": null + }, + "code_block": [ + [ + 1, + "FROM nginx:stable-alpine\n" + ], + [ + 2, + "\n" + ], + [ + 3, + "# confd\n" + ], + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ], + [ + 7, + "ADD confd /etc/confd/\n" + ], + [ + 8, + "\n" + ], + [ + 9, + "# boot script\n" + ], + [ + 10, + "ADD bin/boot.sh /boot.sh\n" + ], + [ + 11, + "RUN chmod +x /boot.sh\n" + ], + [ + 12, + "\n" + ], + [ + 13, + "# nginx\n" + ], + [ + 14, + "COPY nginx/default.conf /etc/nginx/conf.d/default.conf\n" + ], + [ + 15, + "COPY www /usr/share/nginx/html\n" + ], + [ + 16, + "\n" + ], + [ + 17, + "CMD [\"/boot.sh\"]" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 1, + 17 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-from-alias-is-unique-for-multistage-builds", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_DKR_NETWORKING_1", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.dockerfile.checks.RunUsingAPT", + "check_id": "CKV_DOCKER_9", + "check_len": null, + "check_name": "Ensure that APT isn't used", + "check_result": { + "result": "PASSED", + "results_configuration": null + }, + "code_block": [ + [ + 1, + "FROM nginx:stable-alpine\n" + ], + [ + 2, + "\n" + ], + [ + 3, + "# confd\n" + ], + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ], + [ + 7, + "ADD confd /etc/confd/\n" + ], + [ + 8, + "\n" + ], + [ + 9, + "# boot script\n" + ], + [ + 10, + "ADD bin/boot.sh /boot.sh\n" + ], + [ + 11, + "RUN chmod +x /boot.sh\n" + ], + [ + 12, + "\n" + ], + [ + 13, + "# nginx\n" + ], + [ + 14, + "COPY nginx/default.conf /etc/nginx/conf.d/default.conf\n" + ], + [ + 15, + "COPY www /usr/share/nginx/html\n" + ], + [ + 16, + "\n" + ], + [ + 17, + "CMD [\"/boot.sh\"]" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 1, + 17 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-apt-is-not-used", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_DKR_4", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.dockerfile.checks.UpdateNotAlone", + "check_id": "CKV_DOCKER_5", + "check_len": null, + "check_name": "Ensure update instructions are not use alone in the Dockerfile", + "check_result": { + "result": "PASSED", + "results_configuration": null + }, + "code_block": [ + [ + 1, + "FROM nginx:stable-alpine\n" + ], + [ + 2, + "\n" + ], + [ + 3, + "# confd\n" + ], + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ], + [ + 7, + "ADD confd /etc/confd/\n" + ], + [ + 8, + "\n" + ], + [ + 9, + "# boot script\n" + ], + [ + 10, + "ADD bin/boot.sh /boot.sh\n" + ], + [ + 11, + "RUN chmod +x /boot.sh\n" + ], + [ + 12, + "\n" + ], + [ + 13, + "# nginx\n" + ], + [ + 14, + "COPY nginx/default.conf /etc/nginx/conf.d/default.conf\n" + ], + [ + 15, + "COPY www /usr/share/nginx/html\n" + ], + [ + 16, + "\n" + ], + [ + 17, + "CMD [\"/boot.sh\"]" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 1, + 17 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-update-instructions-are-not-used-alone-in-the-dockerfile", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_16", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with pip via the 'PIP_TRUSTED_HOST' environment variable", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-pip-trusted-host", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_16", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with pip via the 'PIP_TRUSTED_HOST' environment variable", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-pip-trusted-host", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_5", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with the PYTHONHTTPSVERIFY environmnet variable", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-PYTHONHTTPSVERIFY-secure", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_5", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with the PYTHONHTTPSVERIFY environmnet variable", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-PYTHONHTTPSVERIFY-secure", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_17", + "check_len": null, + "check_name": "Ensure that 'chpasswd' is not used to set or remove passwords", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/bc-docker-2-17", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_17", + "check_len": null, + "check_name": "Ensure that 'chpasswd' is not used to set or remove passwords", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/bc-docker-2-17", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_6", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with the NODE_TLS_REJECT_UNAUTHORIZED environmnet variable", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-node-tls-secure", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_6", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with the NODE_TLS_REJECT_UNAUTHORIZED environmnet variable", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-node-tls-secure", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_14", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled for git by setting the environment variable 'GIT_SSL_NO_VERIFY' to any value", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-git-ssl", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_14", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled for git by setting the environment variable 'GIT_SSL_NO_VERIFY' to any value", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-git-ssl", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_1", + "check_len": null, + "check_name": "Ensure that sudo isn't used", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-dont-use-sudo", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_1", + "check_len": null, + "check_name": "Ensure that sudo isn't used", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-dont-use-sudo", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_4", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with the pip '--trusted-host' option", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-pip-secure", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_4", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with the pip '--trusted-host' option", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-pip-secure", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_2", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with curl", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-curl-secure", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_2", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with curl", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-curl-secure", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_3", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with wget", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-wget-secure", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_3", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled with wget", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-wget-secure", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_12", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled for npm via the 'NPM_CONFIG_STRICT_SSL' environmnet variable", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-npm-strict-ssl", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_12", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled for npm via the 'NPM_CONFIG_STRICT_SSL' environmnet variable", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-npm-strict-ssl", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_15", + "check_len": null, + "check_name": "Ensure that the yum and dnf package managers are not configured to disable SSL certificate validation via the 'sslverify' configuration option", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-yum-ssl", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_15", + "check_len": null, + "check_name": "Ensure that the yum and dnf package managers are not configured to disable SSL certificate validation via the 'sslverify' configuration option", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-yum-ssl", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_7", + "check_len": null, + "check_name": "Ensure that packages with untrusted or missing signatures are not used by apk via the '--allow-untrusted' option", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-apk-trusted", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_7", + "check_len": null, + "check_name": "Ensure that packages with untrusted or missing signatures are not used by apk via the '--allow-untrusted' option", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-apk-trusted", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_10", + "check_len": null, + "check_name": "Ensure that packages with untrusted or missing signatures are not used by rpm via the '--nodigest', '--nosignature', '--noverify', or '--nofiledigest' options", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-rpm-signed", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_10", + "check_len": null, + "check_name": "Ensure that packages with untrusted or missing signatures are not used by rpm via the '--nodigest', '--nosignature', '--noverify', or '--nofiledigest' options", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-rpm-signed", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_8", + "check_len": null, + "check_name": "Ensure that packages with untrusted or missing signatures are not used by apt-get via the '--allow-unauthenticated' option", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-apt-authenticated", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_8", + "check_len": null, + "check_name": "Ensure that packages with untrusted or missing signatures are not used by apt-get via the '--allow-unauthenticated' option", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-apt-authenticated", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_11", + "check_len": null, + "check_name": "Ensure that the '--force-yes' option is not used, as it disables signature validation and allows packages to be downgraded which can leave the system in a broken or inconsistent state", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-apt-force", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_11", + "check_len": null, + "check_name": "Ensure that the '--force-yes' option is not used, as it disables signature validation and allows packages to be downgraded which can leave the system in a broken or inconsistent state", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-apt-force", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_9", + "check_len": null, + "check_name": "Ensure that packages with untrusted or missing GPG signatures are not used by dnf, tdnf, or yum via the '--nogpgcheck' option", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-yum-signed", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_9", + "check_len": null, + "check_name": "Ensure that packages with untrusted or missing GPG signatures are not used by dnf, tdnf, or yum via the '--nogpgcheck' option", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-yum-signed", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_13", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled for npm or yarn by setting the option strict-ssl to false", + "check_result": { + "entity": { + "__endline__": 5, + "__startline__": 3, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 5, + "__startline__": 3, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "content": "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n chmod +x /usr/local/bin/confd\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "280b16b96bbba3974ac3602739d2d6def0433d5e76df099e7c6880dfe978eced", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && chmod +x /usr/local/bin/confd" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + "RUN wget https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 && \\\n" + ], + [ + 5, + " mv confd-0.16.0-linux-amd64 /usr/local/bin/confd && \\\n" + ], + [ + 6, + " chmod +x /usr/local/bin/confd\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 4, + 6 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-npm-strict-ssl2", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": null, + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.common.graph.checks_infra.base_check", + "check_id": "CKV2_DOCKER_13", + "check_len": null, + "check_name": "Ensure that certificate validation isn't disabled for npm or yarn by setting the option strict-ssl to false", + "check_result": { + "entity": { + "__endline__": 10, + "__startline__": 10, + "block_name_": "RUN", + "block_type_": "resource", + "config_": { + "__endline__": 10, + "__startline__": 10, + "content": "RUN chmod +x /boot.sh\n", + "value": "chmod +x /boot.sh" + }, + "content": "RUN chmod +x /boot.sh\n", + "file_path_": "/src/js/docker/Dockerfile", + "hash": "9052dbf88f6ad184e0cdf7bfb0cb134588fff60670cfd78b1b7fb8bca42a4668", + "id_": "RUN", + "label_": "resource: RUN", + "resource_type": "RUN", + "source_": "Dockerfile", + "value": "chmod +x /boot.sh" + }, + "evaluated_keys": [ + "value" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 11, + "RUN chmod +x /boot.sh\n" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": null, + "file_abs_path": "/src/js/docker/Dockerfile", + "file_line_range": [ + 11, + 11 + ], + "file_path": "/src/js/docker/Dockerfile", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/docker-policies/docker-policy-index/ensure-docker-npm-strict-ssl2", + "repo_file_path": "/js/docker/Dockerfile", + "resource": "/src/js/docker/Dockerfile.RUN", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + } + ], + "skipped_checks": [] + }, + "summary": { + "checkov_version": "3.2.396", + "failed": 4, + "parsing_errors": 0, + "passed": 38, + "resource_count": 1, + "skipped": 0 + }, + "url": "Add an api key '--bc-api-key ' to see more detailed insights via https://bridgecrew.cloud" +} diff --git a/tests/test_data/scanners/checkov/src.test.yaml.json b/tests/test_data/scanners/checkov/src.test.yaml.json new file mode 100644 index 0000000..981264e --- /dev/null +++ b/tests/test_data/scanners/checkov/src.test.yaml.json @@ -0,0 +1,443 @@ +{ + "check_type": "cloudformation", + "results": { + "failed_checks": [ + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_21", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3IgnorePublicACLs", + "check_id": "CKV_AWS_55", + "check_len": null, + "check_name": "Ensure S3 bucket has ignore public ACLs enabled", + "check_result": { + "evaluated_keys": [ + "Properties/PublicAccessBlockConfiguration/IgnorePublicAcls" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket3:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/bc-aws-s3-21", + "repo_file_path": "/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket3", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_16", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3Versioning", + "check_id": "CKV_AWS_21", + "check_len": null, + "check_name": "Ensure the S3 bucket has versioning enabled", + "check_result": { + "evaluated_keys": [ + "Properties/VersioningConfiguration/Status" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket3:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-16-enable-versioning", + "repo_file_path": "/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket3", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_19", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3BlockPublicACLs", + "check_id": "CKV_AWS_53", + "check_len": null, + "check_name": "Ensure S3 bucket has block public ACLs enabled", + "check_result": { + "evaluated_keys": [ + "Properties/PublicAccessBlockConfiguration/BlockPublicAcls" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket3:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/bc-aws-s3-19", + "repo_file_path": "/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket3", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_20", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3BlockPublicPolicy", + "check_id": "CKV_AWS_54", + "check_len": null, + "check_name": "Ensure S3 bucket has block public policy enabled", + "check_result": { + "evaluated_keys": [ + "Properties/PublicAccessBlockConfiguration/BlockPublicPolicy" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket3:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/bc-aws-s3-20", + "repo_file_path": "/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket3", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_13", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3AccessLogs", + "check_id": "CKV_AWS_18", + "check_len": null, + "check_name": "Ensure the S3 bucket has access logging enabled", + "check_result": { + "evaluated_keys": [ + "Properties/LoggingConfiguration" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket3:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-13-enable-logging", + "repo_file_path": "/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket3", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_22", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3RestrictPublicBuckets", + "check_id": "CKV_AWS_56", + "check_len": null, + "check_name": "Ensure S3 bucket has RestrictPublicBuckets enabled", + "check_result": { + "evaluated_keys": [ + "Properties/PublicAccessBlockConfiguration/RestrictPublicBuckets" + ], + "result": "FAILED" + }, + "code_block": [ + [ + 4, + " S3Bucket3:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/bc-aws-s3-22", + "repo_file_path": "/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket3", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + } + ], + "parsing_errors": [], + "passed_checks": [ + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_14", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3Encryption", + "check_id": "CKV_AWS_19", + "check_len": null, + "check_name": "Ensure the S3 bucket has server-side-encryption enabled", + "check_result": { + "evaluated_keys": [ + "Properties/BucketEncryption/ServerSideEncryptionConfiguration/[0]/ServerSideEncryptionByDefault/SSEAlgorithm" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + " S3Bucket3:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-14-data-encrypted-at-rest", + "repo_file_path": "/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket3", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_1", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3PublicACLRead", + "check_id": "CKV_AWS_20", + "check_len": null, + "check_name": "Ensure the S3 bucket does not allow READ permissions to everyone", + "check_result": { + "evaluated_keys": [ + "Properties/AccessControl" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + " S3Bucket3:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-1-acl-read-permissions-everyone", + "repo_file_path": "/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket3", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + }, + { + "bc_category": null, + "bc_check_id": "BC_AWS_S3_2", + "benchmarks": null, + "caller_file_line_range": null, + "caller_file_path": null, + "check_class": "checkov.cloudformation.checks.resource.aws.S3PublicACLWrite", + "check_id": "CKV_AWS_57", + "check_len": null, + "check_name": "Ensure the S3 bucket does not allow WRITE permissions to everyone", + "check_result": { + "evaluated_keys": [ + "Properties/AccessControl" + ], + "result": "PASSED" + }, + "code_block": [ + [ + 4, + " S3Bucket3:\n" + ], + [ + 5, + " Type: AWS::S3::Bucket" + ] + ], + "connected_node": null, + "definition_context_file_path": null, + "description": null, + "details": [], + "entity_tags": null, + "evaluations": {}, + "file_abs_path": "/src/test.yaml", + "file_line_range": [ + 4, + 5 + ], + "file_path": "/test.yaml", + "fixed_definition": null, + "guideline": "https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/s3-policies/s3-2-acl-write-permissions-everyone", + "repo_file_path": "/test.yaml", + "resource": "AWS::S3::Bucket.S3Bucket3", + "resource_address": null, + "severity": null, + "short_description": null, + "vulnerability_details": null + } + ], + "skipped_checks": [] + }, + "summary": { + "checkov_version": "3.2.396", + "failed": 6, + "parsing_errors": 0, + "passed": 3, + "resource_count": 1, + "skipped": 0 + }, + "url": "Add an api key '--bc-api-key ' to see more detailed insights via https://bridgecrew.cloud" +} diff --git a/tests/test_data/scanners/git/tree.txt b/tests/test_data/scanners/git/tree.txt new file mode 100644 index 0000000..d85afea --- /dev/null +++ b/tests/test_data/scanners/git/tree.txt @@ -0,0 +1,237 @@ +/src +├── [6.0K] .DS_Store +├── [ 22] .gitignore +├── [ 11K] ash_output +│   ├── [ 483] ash-ignore-report.txt +│   ├── [6.0K] ash-scan-set-files-list.txt +│   ├── [ 990] scanners +│   │   ├── [ 64] cdk +│   │   ├── [ 96] git +│   │   │   └── [ 0] tree.txt +│   │   ├── [ 64] grype +│   │   ├── [ 64] npmaudit +│   │   └── [ 510] results +│   │   ├── [ 29] cdk_report_result.txt +│   │   ├── [ 32] git_report_result.txt +│   │   ├── [ 44] grype_report_result.txt +│   │   ├── [ 0] js_report_result.txt +│   │   ├── [ 73] py_report_result.txt +│   │   └── [ 76] yaml_report_result.txt +│   └── [3.4K] work +│   ├── [ 244] bla +│   │   ├── [ 0] dummy1.py +│   │   └── [ 116] dummy_creds +│   ├── [ 244] bla2 +│   │   ├── [ 0] dummy2.py +│   │   └── [ 116] dummy_creds2 +│   └── [2.7K] zip_w_space +│   └── [2.6K] dir with space +│   └── [2.5K] test.yaml +├── [ 417] bla.zip +├── [ 419] bla2.zip +├── [ 41K] cdk +│   ├── [6.0K] .DS_Store +│   ├── [1.6K] README.md +│   ├── [8.4K] app +│   │   ├── [6.0K] .DS_Store +│   │   ├── [ 0] __init__.py +│   │   ├── [1.8K] __pycache__ +│   │   │   ├── [ 130] __init__.cpython-38.pyc +│   │   │   ├── [ 115] __init__.cpython-39.pyc +│   │   │   ├── [ 665] app_stack.cpython-38.pyc +│   │   │   └── [ 745] app_stack.cpython-39.pyc +│   │   └── [ 438] app_stack.py +│   ├── [1.0K] app copy.py +│   ├── [1.1K] app.py +│   ├── [ 780] cdk.json +│   ├── [ 19K] cdk.out +│   │   ├── [ 652] AppStack.assets.json +│   │   ├── [5.8K] AppStack.template.json +│   │   ├── [ 545] AwsSolutions-AppStack-NagReport.csv +│   │   ├── [ 20] cdk.out +│   │   ├── [9.2K] manifest.json +│   │   └── [2.2K] tree.json +│   ├── [1.3K] replace.py +│   ├── [ 14] requirements-dev.txt +│   ├── [ 61] requirements.txt +│   ├── [ 437] source.bat +│   └── [ 709] tests +│   ├── [ 0] __init__.py +│   └── [ 581] unit +│   ├── [ 0] __init__.py +│   └── [ 453] test_app_stack.py +├── [ 18K] cfn_and_python +│   ├── [6.0K] .DS_Store +│   ├── [ 11K] ash_cf2cdk_output +│   │   ├── [6.0K] .DS_Store +│   │   └── [5.2K] test.yaml_cdk_nag_results +│   │   ├── [ 803] AwsSolutions-cfn-to-cdk-NagReport.csv +│   │   └── [4.3K] cfn-to-cdk.template.json +│   ├── [ 263] py-test-dll.py +│   └── [ 87] test.yaml +├── [ 0] dummy2.py +├── [ 116] dummy_creds2 +├── [ 12K] ipynb.ipynb +├── [593K] js +│   ├── [8.0K] .DS_Store +│   ├── [ 69] .gitignore +│   ├── [ 393] .travis.yml +│   ├── [ 766] Gemfile +│   ├── [1.8K] Gemfile.lock +│   ├── [1.1K] LICENSE +│   ├── [2.4K] README.md +│   ├── [1.5K] _config.yml +│   ├── [2.1K] docker +│   │   ├── [ 428] Dockerfile +│   │   ├── [ 635] bin +│   │   │   └── [ 539] boot.sh +│   │   ├── [ 310] confd +│   │   │   └── [ 214] conf.d +│   │   │   └── [ 118] config.js.toml +│   │   └── [ 602] nginx +│   │   └── [ 506] default.conf +│   ├── [ 83K] package-lock.json +│   ├── [1.9K] package.json +│   ├── [2.0K] scripts +│   │   ├── [ 499] build-materialize.js +│   │   ├── [ 145] clean.js +│   │   ├── [ 635] copy-libs.js +│   │   ├── [ 147] docker-build.sh +│   │   ├── [ 195] docker-prepare.sh +│   │   └── [ 159] docker-run.sh +│   └── [487K] src +│   ├── [6.0K] .DS_Store +│   ├── [3.3K] _data +│   │   ├── [ 524] categories.yaml +│   │   └── [2.6K] examples.yaml +│   ├── [6.1K] _includes +│   │   ├── [ 122] footer.html +│   │   ├── [ 805] header.html +│   │   ├── [ 65] html-footer.html +│   │   ├── [1.4K] html-head.html +│   │   ├── [2.8K] meta-data.html +│   │   ├── [ 0] scripts.html +│   │   ├── [ 399] side-nav-group.html +│   │   └── [ 286] side-nav.html +│   ├── [1.9K] _layouts +│   │   ├── [ 572] default.html +│   │   └── [1.2K] example.html +│   ├── [316K] assets +│   │   ├── [6.0K] .DS_Store +│   │   ├── [124K] css +│   │   │   ├── [1.1K] examples-common.css +│   │   │   ├── [118K] materialize.min.css +│   │   │   └── [4.9K] style.css +│   │   ├── [ 27K] favicon +│   │   │   ├── [1.5K] favicon-16x16.png +│   │   │   ├── [2.2K] favicon-32x32.png +│   │   │   ├── [7.9K] favicon-96x96.png +│   │   │   └── [ 15K] favicon.ico +│   │   ├── [134K] fonts +│   │   │   └── [134K] CenturyGothic.ttf +│   │   ├── [ 16K] img +│   │   │   ├── [1.7K] GitHub-Mark-32px.png +│   │   │   ├── [1.5K] GitHub-Mark-Light-32px.png +│   │   │   └── [ 13K] logo.png +│   │   └── [8.4K] js +│   │   ├── [1.3K] example-helpers.js +│   │   ├── [ 655] exampleId.js +│   │   ├── [ 663] examples.js +│   │   ├── [5.5K] js_report_result.txt +│   │   └── [ 82] package-lock.json +│   ├── [ 450] config.example.js +│   ├── [152K] examples +│   │   ├── [ 10K] .DS_Store +│   │   ├── [ 15K] ace +│   │   │   ├── [ 679] README.md +│   │   │   ├── [ 373] ace.css +│   │   │   ├── [8.5K] ace.js +│   │   │   ├── [3.5K] default_editor_contents.js +│   │   │   └── [1.3K] index.html +│   │   ├── [8.3K] buddy-list +│   │   │   ├── [ 904] README.md +│   │   │   ├── [1.2K] buddy-list.css +│   │   │   ├── [2.7K] buddy-list.js +│   │   │   ├── [2.9K] index.html +│   │   │   └── [ 401] users.js +│   │   ├── [ 11K] chartjs +│   │   │   ├── [1.6K] README.md +│   │   │   ├── [1.1K] chart.css +│   │   │   ├── [7.1K] chart.js +│   │   │   └── [1.0K] index.html +│   │   ├── [7.8K] chat-room +│   │   │   ├── [ 560] README.md +│   │   │   ├── [2.8K] chat-components.js +│   │   │   ├── [ 551] chat-room.css +│   │   │   ├── [2.6K] chat-room.js +│   │   │   └── [1.1K] index.html +│   │   ├── [ 11K] codemirror +│   │   │   ├── [ 701] README.md +│   │   │   ├── [4.4K] codemirror-adapter.js +│   │   │   ├── [ 186] codemirror.css +│   │   │   ├── [ 793] codemirror.js +│   │   │   ├── [3.5K] default_editor_contents.js +│   │   │   └── [1.5K] index.html +│   │   ├── [ 11K] collaborative-textarea +│   │   │   ├── [ 647] README.md +│   │   │   ├── [5.7K] data.js +│   │   │   ├── [ 301] example.css +│   │   │   ├── [3.0K] example.js +│   │   │   └── [1.3K] index.html +│   │   ├── [1.8K] content-editable +│   │   │   ├── [ 150] content-editable.css +│   │   │   ├── [ 575] content-editable.js +│   │   │   └── [ 978] index.html +│   │   ├── [3.6K] easymde +│   │   │   ├── [ 616] README.md +│   │   │   ├── [ 672] default_editor_contents.js +│   │   │   ├── [ 41] easymde.css +│   │   │   ├── [ 737] easymde.js +│   │   │   └── [1.4K] index.html +│   │   ├── [4.1K] froala +│   │   │   ├── [ 248] README.md +│   │   │   ├── [ 42] froala.css +│   │   │   ├── [1.9K] froala.js +│   │   │   └── [1.7K] index.html +│   │   ├── [5.0K] input-elements +│   │   │   ├── [ 346] README.md +│   │   │   ├── [ 200] example.css +│   │   │   ├── [2.0K] example.js +│   │   │   └── [2.2K] index.html +│   │   ├── [ 16K] jointjs +│   │   │   ├── [ 10K] default-graph-data.js +│   │   │   ├── [1.5K] index.html +│   │   │   ├── [1.5K] jointjs.css +│   │   │   └── [2.8K] jointjs.js +│   │   ├── [ 13K] monaco +│   │   │   ├── [ 680] README.md +│   │   │   ├── [3.5K] default_editor_contents.js +│   │   │   ├── [1.8K] index.html +│   │   │   ├── [5.0K] monaco-adapter.js +│   │   │   ├── [ 288] monaco.css +│   │   │   └── [1.1K] monaco.js +│   │   ├── [ 18K] mxgraph +│   │   │   ├── [ 15K] default-graph.js +│   │   │   ├── [1.3K] index.html +│   │   │   ├── [ 97] mxgraph.css +│   │   │   └── [1.2K] mxgraph.js +│   │   ├── [ 12K] pointer +│   │   │   ├── [ 525] README.md +│   │   │   ├── [1.6K] assets +│   │   │   │   └── [1.5K] cursor.png +│   │   │   ├── [1.8K] index.html +│   │   │   ├── [1.4K] pointer.css +│   │   │   └── [6.9K] pointer.js +│   │   └── [3.7K] tui-editor +│   │   ├── [ 248] README.md +│   │   ├── [ 672] default_editor_contents.js +│   │   ├── [1.7K] index.html +│   │   ├── [ 37] tui-editor.css +│   │   └── [ 877] tui-editor.js +│   ├── [1.5K] index.html +│   └── [ 0] optional.js +├── [ 263] py-test-dll.py +├── [ 87] test.yaml +└── [1.2K] zip_w_space.zip + + 684K used in 55 directories, 179 files diff --git a/tests/test_data/scanners/grype/grype.src.ash_output.work.json b/tests/test_data/scanners/grype/grype.src.ash_output.work.json new file mode 100644 index 0000000..d2d7f3a --- /dev/null +++ b/tests/test_data/scanners/grype/grype.src.ash_output.work.json @@ -0,0 +1,233 @@ +{ + "descriptor": { + "configuration": { + "add-cpes-if-none": false, + "by-cve": false, + "check-for-app-update": true, + "db": { + "auto-update": true, + "ca-cert": "", + "cache-dir": "/deps/.grype", + "max-allowed-built-age": 432000000000000, + "max-update-check-frequency": 7200000000000, + "require-update-check": false, + "update-available-timeout": 30000000000, + "update-download-timeout": 300000000000, + "update-url": "https://grype.anchore.io/databases", + "validate-age": true, + "validate-by-hash-on-start": true + }, + "default-image-pull-source": "", + "dev": { + "db": { + "debug": false + } + }, + "distro": "", + "exclude": [ + "/src/ash_output/work/**/*-converted.py", + "/src/ash_output/work/**/*_report_result.txt" + ], + "exp": {}, + "externalSources": { + "enable": false, + "maven": { + "baseUrl": "https://search.maven.org/solrsearch/select", + "rateLimit": 300000000, + "searchUpstreamBySha1": true + } + }, + "fail-on-severity": "medium", + "file": "", + "ignore": [ + { + "fix-state": "", + "match-type": "exact-indirect-match", + "namespace": "", + "package": { + "language": "", + "location": "", + "name": "kernel-headers", + "type": "rpm", + "upstream-name": "kernel", + "version": "" + }, + "reason": "", + "vex-justification": "", + "vex-status": "", + "vulnerability": "" + }, + { + "fix-state": "", + "match-type": "exact-indirect-match", + "namespace": "", + "package": { + "language": "", + "location": "", + "name": "linux(-.*)?-headers-.*", + "type": "deb", + "upstream-name": "linux.*", + "version": "" + }, + "reason": "", + "vex-justification": "", + "vex-status": "", + "vulnerability": "" + }, + { + "fix-state": "", + "match-type": "exact-indirect-match", + "namespace": "", + "package": { + "language": "", + "location": "", + "name": "linux-libc-dev", + "type": "deb", + "upstream-name": "linux", + "version": "" + }, + "reason": "", + "vex-justification": "", + "vex-status": "", + "vulnerability": "" + } + ], + "ignore-wontfix": "", + "match": { + "dotnet": { + "using-cpes": false + }, + "golang": { + "allow-main-module-pseudo-version-comparison": false, + "always-use-cpe-for-stdlib": true, + "using-cpes": false + }, + "java": { + "using-cpes": false + }, + "javascript": { + "using-cpes": false + }, + "jvm": { + "using-cpes": true + }, + "python": { + "using-cpes": false + }, + "ruby": { + "using-cpes": false + }, + "rust": { + "using-cpes": false + }, + "stock": { + "using-cpes": true + } + }, + "match-upstream-kernel-headers": false, + "name": "", + "only-fixed": false, + "only-notfixed": false, + "output": [ + "json" + ], + "output-template-file": "", + "platform": "", + "pretty": false, + "registry": { + "auth": null, + "ca-cert": "", + "insecure-skip-tls-verify": false, + "insecure-use-http": false + }, + "search": { + "indexed-archives": true, + "scope": "squashed", + "unindexed-archives": false + }, + "show-suppressed": false, + "vex-add": [], + "vex-documents": [] + }, + "db": { + "providers": { + "alpine": { + "captured": "2025-04-01T01:32:50Z", + "input": "xxh64:c45556b3d251c9a8" + }, + "amazon": { + "captured": "2025-04-01T01:31:39Z", + "input": "xxh64:3984da2a8ee942c3" + }, + "chainguard": { + "captured": "2025-04-01T01:32:32Z", + "input": "xxh64:ae5e19e263827387" + }, + "debian": { + "captured": "2025-04-01T01:32:38Z", + "input": "xxh64:9e64566bea45d2b6" + }, + "epss": { + "captured": "2025-04-01T01:32:38Z", + "input": "xxh64:af18a12b328ed880" + }, + "github": { + "captured": "2025-04-01T01:33:09Z", + "input": "xxh64:3402f0e5c4acef5e" + }, + "kev": { + "captured": "2025-04-01T01:32:57Z", + "input": "xxh64:e05c02e66fb8e1c5" + }, + "mariner": { + "captured": "2025-04-01T01:32:55Z", + "input": "xxh64:91c9dadb5d25d3c7" + }, + "nvd": { + "captured": "2025-04-01T01:32:57Z", + "input": "xxh64:392dc047861ee67d" + }, + "oracle": { + "captured": "2025-04-01T01:32:49Z", + "input": "xxh64:5f3f527abbfa3ad7" + }, + "rhel": { + "captured": "2025-04-01T01:33:25Z", + "input": "xxh64:72ab31ada33a781b" + }, + "sles": { + "captured": "2025-04-01T01:32:53Z", + "input": "xxh64:5bd23eb7b341c8a6" + }, + "ubuntu": { + "captured": "2025-04-01T01:33:33Z", + "input": "xxh64:4dd8fdcfc6db1541" + }, + "wolfi": { + "captured": "2025-04-01T01:32:49Z", + "input": "xxh64:36fce80efdb5eff4" + } + }, + "status": { + "built": "2025-04-01T04:08:17Z", + "from": "https://grype.anchore.io/databases/v6/vulnerability-db_v6.0.2_2025-04-01T01:31:39Z_1743480497.tar.zst?checksum=sha256%3A3c167f99651f8c69ab785aff771c769d5143f6537014fef32a53c1527192bda3", + "path": "/deps/.grype/6/vulnerability.db", + "schemaVersion": "v6.0.2", + "valid": true + } + }, + "name": "grype", + "timestamp": "2025-04-01T23:04:25.094798815Z", + "version": "0.91.0" + }, + "distro": { + "idLike": null, + "name": "", + "version": "" + }, + "matches": [], + "source": { + "target": "/src/ash_output/work", + "type": "directory" + } +} diff --git a/tests/test_data/scanners/grype/grype.src.json b/tests/test_data/scanners/grype/grype.src.json new file mode 100644 index 0000000..c2b4446 --- /dev/null +++ b/tests/test_data/scanners/grype/grype.src.json @@ -0,0 +1,1225 @@ +{ + "descriptor": { + "configuration": { + "add-cpes-if-none": false, + "by-cve": false, + "check-for-app-update": true, + "db": { + "auto-update": true, + "ca-cert": "", + "cache-dir": "/deps/.grype", + "max-allowed-built-age": 432000000000000, + "max-update-check-frequency": 7200000000000, + "require-update-check": false, + "update-available-timeout": 30000000000, + "update-download-timeout": 300000000000, + "update-url": "https://grype.anchore.io/databases", + "validate-age": true, + "validate-by-hash-on-start": true + }, + "default-image-pull-source": "", + "dev": { + "db": { + "debug": false + } + }, + "distro": "", + "exclude": [ + "/src/**/*-converted.py", + "/src/**/*_report_result.txt" + ], + "exp": {}, + "externalSources": { + "enable": false, + "maven": { + "baseUrl": "https://search.maven.org/solrsearch/select", + "rateLimit": 300000000, + "searchUpstreamBySha1": true + } + }, + "fail-on-severity": "medium", + "file": "", + "ignore": [ + { + "fix-state": "", + "match-type": "exact-indirect-match", + "namespace": "", + "package": { + "language": "", + "location": "", + "name": "kernel-headers", + "type": "rpm", + "upstream-name": "kernel", + "version": "" + }, + "reason": "", + "vex-justification": "", + "vex-status": "", + "vulnerability": "" + }, + { + "fix-state": "", + "match-type": "exact-indirect-match", + "namespace": "", + "package": { + "language": "", + "location": "", + "name": "linux(-.*)?-headers-.*", + "type": "deb", + "upstream-name": "linux.*", + "version": "" + }, + "reason": "", + "vex-justification": "", + "vex-status": "", + "vulnerability": "" + }, + { + "fix-state": "", + "match-type": "exact-indirect-match", + "namespace": "", + "package": { + "language": "", + "location": "", + "name": "linux-libc-dev", + "type": "deb", + "upstream-name": "linux", + "version": "" + }, + "reason": "", + "vex-justification": "", + "vex-status": "", + "vulnerability": "" + } + ], + "ignore-wontfix": "", + "match": { + "dotnet": { + "using-cpes": false + }, + "golang": { + "allow-main-module-pseudo-version-comparison": false, + "always-use-cpe-for-stdlib": true, + "using-cpes": false + }, + "java": { + "using-cpes": false + }, + "javascript": { + "using-cpes": false + }, + "jvm": { + "using-cpes": true + }, + "python": { + "using-cpes": false + }, + "ruby": { + "using-cpes": false + }, + "rust": { + "using-cpes": false + }, + "stock": { + "using-cpes": true + } + }, + "match-upstream-kernel-headers": false, + "name": "", + "only-fixed": false, + "only-notfixed": false, + "output": [ + "json" + ], + "output-template-file": "", + "platform": "", + "pretty": false, + "registry": { + "auth": null, + "ca-cert": "", + "insecure-skip-tls-verify": false, + "insecure-use-http": false + }, + "search": { + "indexed-archives": true, + "scope": "squashed", + "unindexed-archives": false + }, + "show-suppressed": false, + "vex-add": [], + "vex-documents": [] + }, + "db": { + "providers": { + "alpine": { + "captured": "2025-04-01T01:32:50Z", + "input": "xxh64:c45556b3d251c9a8" + }, + "amazon": { + "captured": "2025-04-01T01:31:39Z", + "input": "xxh64:3984da2a8ee942c3" + }, + "chainguard": { + "captured": "2025-04-01T01:32:32Z", + "input": "xxh64:ae5e19e263827387" + }, + "debian": { + "captured": "2025-04-01T01:32:38Z", + "input": "xxh64:9e64566bea45d2b6" + }, + "epss": { + "captured": "2025-04-01T01:32:38Z", + "input": "xxh64:af18a12b328ed880" + }, + "github": { + "captured": "2025-04-01T01:33:09Z", + "input": "xxh64:3402f0e5c4acef5e" + }, + "kev": { + "captured": "2025-04-01T01:32:57Z", + "input": "xxh64:e05c02e66fb8e1c5" + }, + "mariner": { + "captured": "2025-04-01T01:32:55Z", + "input": "xxh64:91c9dadb5d25d3c7" + }, + "nvd": { + "captured": "2025-04-01T01:32:57Z", + "input": "xxh64:392dc047861ee67d" + }, + "oracle": { + "captured": "2025-04-01T01:32:49Z", + "input": "xxh64:5f3f527abbfa3ad7" + }, + "rhel": { + "captured": "2025-04-01T01:33:25Z", + "input": "xxh64:72ab31ada33a781b" + }, + "sles": { + "captured": "2025-04-01T01:32:53Z", + "input": "xxh64:5bd23eb7b341c8a6" + }, + "ubuntu": { + "captured": "2025-04-01T01:33:33Z", + "input": "xxh64:4dd8fdcfc6db1541" + }, + "wolfi": { + "captured": "2025-04-01T01:32:49Z", + "input": "xxh64:36fce80efdb5eff4" + } + }, + "status": { + "built": "2025-04-01T04:08:17Z", + "from": "https://grype.anchore.io/databases/v6/vulnerability-db_v6.0.2_2025-04-01T01:31:39Z_1743480497.tar.zst?checksum=sha256%3A3c167f99651f8c69ab785aff771c769d5143f6537014fef32a53c1527192bda3", + "path": "/deps/.grype/6/vulnerability.db", + "schemaVersion": "v6.0.2", + "valid": true + } + }, + "name": "grype", + "timestamp": "2025-04-01T23:04:22.72584623Z", + "version": "0.91.0" + }, + "distro": { + "idLike": null, + "name": "", + "version": "" + }, + "matches": [ + { + "artifact": { + "cpes": [ + "cpe:2.3:a:addressable:addressable:2.7.0:*:*:*:*:*:*:*", + "cpe:2.3:a:ruby-lang:addressable:2.7.0:*:*:*:*:*:*:*", + "cpe:2.3:a:ruby_lang:addressable:2.7.0:*:*:*:*:*:*:*", + "cpe:2.3:a:ruby:addressable:2.7.0:*:*:*:*:*:*:*" + ], + "id": "1d184295bb8d623a", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "addressable", + "purl": "pkg:gem/addressable@2.7.0", + "type": "gem", + "upstreams": [], + "version": "2.7.0" + }, + "matchDetails": [ + { + "fix": { + "suggestedVersion": "2.8.0" + }, + "found": { + "versionConstraint": ">=2.3.0,<=2.7.0 (semver)", + "vulnerabilityID": "GHSA-jxhc-q857-3j6g" + }, + "matcher": "ruby-gem-matcher", + "searchedBy": { + "language": "ruby", + "namespace": "github:language:ruby", + "package": { + "name": "addressable", + "version": "2.7.0" + } + }, + "type": "exact-direct-match" + } + ], + "relatedVulnerabilities": [ + { + "cvss": [ + { + "metrics": { + "baseScore": 7.5, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + }, + "source": "nvd@nist.gov", + "type": "Primary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + }, + { + "metrics": { + "baseScore": 5, + "exploitabilityScore": 10, + "impactScore": 2.9 + }, + "source": "nvd@nist.gov", + "type": "Primary", + "vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "vendorMetadata": {}, + "version": "2.0" + }, + { + "metrics": { + "baseScore": 7.5, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + }, + "source": "security-advisories@github.com", + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2021-32740", + "description": "Addressable is an alternative implementation to the URI implementation that is part of Ruby's standard library. An uncontrolled resource consumption vulnerability exists after version 2.3.0 through version 2.7.0. Within the URI template implementation in Addressable, a maliciously crafted template may result in uncontrolled resource consumption, leading to denial of service when matched against a URI. In typical usage, templates would not normally be read from untrusted user input, but nonetheless, no previous security advisory for Addressable has cautioned against doing this. Users of the parsing capabilities in Addressable but not the URI template capabilities are unaffected. The vulnerability is patched in version 2.8.0. As a workaround, only create Template objects from trusted sources that have been validated not to produce catastrophic backtracking.", + "epss": [ + { + "cve": "CVE-2021-32740", + "date": "2025-03-31", + "epss": 0.00459, + "percentile": 0.61206 + } + ], + "id": "CVE-2021-32740", + "namespace": "nvd:cpe", + "severity": "High", + "urls": [ + "https://github.com/sporkmonger/addressable/commit/0d8a3127e35886ce9284810a7f2438bff6b43cbc", + "https://github.com/sporkmonger/addressable/security/advisories/GHSA-jxhc-q857-3j6g", + "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/SDFQM2NHNAZ3NNUQZEJTYECYZYXV4UDS/", + "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/WYPVOOQU7UB277UUERJMCNQLRCXRCIQ5/", + "https://github.com/sporkmonger/addressable/commit/0d8a3127e35886ce9284810a7f2438bff6b43cbc", + "https://github.com/sporkmonger/addressable/security/advisories/GHSA-jxhc-q857-3j6g", + "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/SDFQM2NHNAZ3NNUQZEJTYECYZYXV4UDS/", + "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/WYPVOOQU7UB277UUERJMCNQLRCXRCIQ5/" + ] + } + ], + "vulnerability": { + "advisories": [], + "cvss": [ + { + "metrics": { + "baseScore": 7.5, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + }, + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://github.com/advisories/GHSA-jxhc-q857-3j6g", + "description": "Regular Expression Denial of Service in Addressable templates", + "epss": [ + { + "cve": "CVE-2021-32740", + "date": "2025-03-31", + "epss": 0.00459, + "percentile": 0.61206 + } + ], + "fix": { + "state": "fixed", + "versions": [ + "2.8.0" + ] + }, + "id": "GHSA-jxhc-q857-3j6g", + "namespace": "github:language:ruby", + "severity": "High", + "urls": [] + } + }, + { + "artifact": { + "cpes": [ + "cpe:2.3:a:ruby-lang:rexml:3.2.5:*:*:*:*:ruby:*:*" + ], + "id": "a26fdaf742880a0e", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "rexml", + "purl": "pkg:gem/rexml@3.2.5", + "type": "gem", + "upstreams": [], + "version": "3.2.5" + }, + "matchDetails": [ + { + "fix": { + "suggestedVersion": "3.3.6" + }, + "found": { + "versionConstraint": "<3.3.6 (semver)", + "vulnerabilityID": "GHSA-vmwr-mc7x-5vc3" + }, + "matcher": "ruby-gem-matcher", + "searchedBy": { + "language": "ruby", + "namespace": "github:language:ruby", + "package": { + "name": "rexml", + "version": "3.2.5" + } + }, + "type": "exact-direct-match" + } + ], + "relatedVulnerabilities": [ + { + "cvss": [ + { + "metrics": { + "baseScore": 5.9, + "exploitabilityScore": 2.3, + "impactScore": 3.6 + }, + "source": "security-advisories@github.com", + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2024-43398", + "description": "REXML is an XML toolkit for Ruby. The REXML gem before 3.3.6 has a DoS vulnerability when it parses an XML that has many deep elements that have same local name attributes. If you need to parse untrusted XMLs with tree parser API like REXML::Document.new, you may be impacted to this vulnerability. If you use other parser APIs such as stream parser API and SAX2 parser API, this vulnerability is not affected. The REXML gem 3.3.6 or later include the patch to fix the vulnerability.", + "epss": [ + { + "cve": "CVE-2024-43398", + "date": "2025-03-31", + "epss": 0.0036, + "percentile": 0.55256 + } + ], + "id": "CVE-2024-43398", + "namespace": "nvd:cpe", + "severity": "Medium", + "urls": [ + "https://github.com/ruby/rexml/releases/tag/v3.3.6", + "https://github.com/ruby/rexml/security/advisories/GHSA-vmwr-mc7x-5vc3", + "https://security.netapp.com/advisory/ntap-20250103-0006/" + ] + } + ], + "vulnerability": { + "advisories": [], + "cvss": [ + { + "metrics": { + "baseScore": 5.9, + "exploitabilityScore": 2.3, + "impactScore": 3.6 + }, + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://github.com/advisories/GHSA-vmwr-mc7x-5vc3", + "description": "REXML denial of service vulnerability", + "epss": [ + { + "cve": "CVE-2024-43398", + "date": "2025-03-31", + "epss": 0.0036, + "percentile": 0.55256 + } + ], + "fix": { + "state": "fixed", + "versions": [ + "3.3.6" + ] + }, + "id": "GHSA-vmwr-mc7x-5vc3", + "namespace": "github:language:ruby", + "severity": "High", + "urls": [] + } + }, + { + "artifact": { + "cpes": [ + "cpe:2.3:a:ruby-lang:rexml:3.2.5:*:*:*:*:ruby:*:*" + ], + "id": "a26fdaf742880a0e", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "rexml", + "purl": "pkg:gem/rexml@3.2.5", + "type": "gem", + "upstreams": [], + "version": "3.2.5" + }, + "matchDetails": [ + { + "fix": { + "suggestedVersion": "3.3.9" + }, + "found": { + "versionConstraint": "<3.3.9 (semver)", + "vulnerabilityID": "GHSA-2rxp-v6pw-ch6m" + }, + "matcher": "ruby-gem-matcher", + "searchedBy": { + "language": "ruby", + "namespace": "github:language:ruby", + "package": { + "name": "rexml", + "version": "3.2.5" + } + }, + "type": "exact-direct-match" + } + ], + "relatedVulnerabilities": [ + { + "cvss": [ + { + "metrics": { + "baseScore": 7.5, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + }, + "source": "nvd@nist.gov", + "type": "Primary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2024-49761", + "description": "REXML is an XML toolkit for Ruby. The REXML gem before 3.3.9 has a ReDoS vulnerability when it parses an XML that has many digits between &# and x...; in a hex numeric character reference (&#x...;). This does not happen with Ruby 3.2 or later. Ruby 3.1 is the only affected maintained Ruby. The REXML gem 3.3.9 or later include the patch to fix the vulnerability.", + "epss": [ + { + "cve": "CVE-2024-49761", + "date": "2025-03-31", + "epss": 0.00162, + "percentile": 0.33771 + } + ], + "id": "CVE-2024-49761", + "namespace": "nvd:cpe", + "severity": "High", + "urls": [ + "https://github.com/ruby/rexml/commit/ce59f2eb1aeb371fe1643414f06618dbe031979f", + "https://github.com/ruby/rexml/security/advisories/GHSA-2rxp-v6pw-ch6m", + "https://www.ruby-lang.org/en/news/2024/10/28/redos-rexml-cve-2024-49761", + "https://security.netapp.com/advisory/ntap-20241227-0004/" + ] + } + ], + "vulnerability": { + "advisories": [], + "cvss": [ + { + "metrics": { + "baseScore": 7.5, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + }, + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://github.com/advisories/GHSA-2rxp-v6pw-ch6m", + "description": "REXML ReDoS vulnerability", + "epss": [ + { + "cve": "CVE-2024-49761", + "date": "2025-03-31", + "epss": 0.00162, + "percentile": 0.33771 + } + ], + "fix": { + "state": "fixed", + "versions": [ + "3.3.9" + ] + }, + "id": "GHSA-2rxp-v6pw-ch6m", + "namespace": "github:language:ruby", + "severity": "Medium", + "urls": [] + } + }, + { + "artifact": { + "cpes": [ + "cpe:2.3:a:ruby-lang:rexml:3.2.5:*:*:*:*:ruby:*:*" + ], + "id": "a26fdaf742880a0e", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "rexml", + "purl": "pkg:gem/rexml@3.2.5", + "type": "gem", + "upstreams": [], + "version": "3.2.5" + }, + "matchDetails": [ + { + "fix": { + "suggestedVersion": "3.3.2" + }, + "found": { + "versionConstraint": "<3.3.2 (semver)", + "vulnerabilityID": "GHSA-4xqq-m2hx-25v8" + }, + "matcher": "ruby-gem-matcher", + "searchedBy": { + "language": "ruby", + "namespace": "github:language:ruby", + "package": { + "name": "rexml", + "version": "3.2.5" + } + }, + "type": "exact-direct-match" + } + ], + "relatedVulnerabilities": [ + { + "cvss": [ + { + "metrics": { + "baseScore": 4.3, + "exploitabilityScore": 2.9, + "impactScore": 1.5 + }, + "source": "security-advisories@github.com", + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2024-39908", + "description": "REXML is an XML toolkit for Ruby. The REXML gem before 3.3.1 has some DoS vulnerabilities when it parses an XML that has many specific characters such as `<`, `0` and `%>`. If you need to parse untrusted XMLs, you many be impacted to these vulnerabilities. The REXML gem 3.3.2 or later include the patches to fix these vulnerabilities. Users are advised to upgrade. Users unable to upgrade should avoid parsing untrusted XML strings.", + "epss": [ + { + "cve": "CVE-2024-39908", + "date": "2025-03-31", + "epss": 0.09091, + "percentile": 0.91883 + } + ], + "id": "CVE-2024-39908", + "namespace": "nvd:cpe", + "severity": "Medium", + "urls": [ + "https://github.com/ruby/rexml/security/advisories/GHSA-4xqq-m2hx-25v8", + "https://www.ruby-lang.org/en/news/2024/07/16/dos-rexml-cve-2024-39908", + "https://github.com/ruby/rexml/security/advisories/GHSA-4xqq-m2hx-25v8", + "https://security.netapp.com/advisory/ntap-20250117-0008/", + "https://www.ruby-lang.org/en/news/2024/07/16/dos-rexml-cve-2024-39908" + ] + } + ], + "vulnerability": { + "advisories": [], + "cvss": [ + { + "metrics": { + "baseScore": 4.3, + "exploitabilityScore": 2.9, + "impactScore": 1.5 + }, + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://github.com/advisories/GHSA-4xqq-m2hx-25v8", + "description": "REXML denial of service vulnerability", + "epss": [ + { + "cve": "CVE-2024-39908", + "date": "2025-03-31", + "epss": 0.09091, + "percentile": 0.91883 + } + ], + "fix": { + "state": "fixed", + "versions": [ + "3.3.2" + ] + }, + "id": "GHSA-4xqq-m2hx-25v8", + "namespace": "github:language:ruby", + "severity": "Medium", + "urls": [] + } + }, + { + "artifact": { + "cpes": [ + "cpe:2.3:a:ruby-lang:rexml:3.2.5:*:*:*:*:ruby:*:*" + ], + "id": "a26fdaf742880a0e", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "rexml", + "purl": "pkg:gem/rexml@3.2.5", + "type": "gem", + "upstreams": [], + "version": "3.2.5" + }, + "matchDetails": [ + { + "fix": { + "suggestedVersion": "3.3.3" + }, + "found": { + "versionConstraint": "<3.3.3 (semver)", + "vulnerabilityID": "GHSA-5866-49gr-22v4" + }, + "matcher": "ruby-gem-matcher", + "searchedBy": { + "language": "ruby", + "namespace": "github:language:ruby", + "package": { + "name": "rexml", + "version": "3.2.5" + } + }, + "type": "exact-direct-match" + } + ], + "relatedVulnerabilities": [ + { + "cvss": [ + { + "metrics": { + "baseScore": 7.5, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + }, + "source": "nvd@nist.gov", + "type": "Primary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + }, + { + "metrics": { + "baseScore": 5.3, + "exploitabilityScore": 3.9, + "impactScore": 1.5 + }, + "source": "security-advisories@github.com", + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2024-41946", + "description": "REXML is an XML toolkit for Ruby. The REXML gem 3.3.2 has a DoS vulnerability when it parses an XML that has many entity expansions with SAX2 or pull parser API. The REXML gem 3.3.3 or later include the patch to fix the vulnerability.", + "epss": [ + { + "cve": "CVE-2024-41946", + "date": "2025-03-31", + "epss": 0.00833, + "percentile": 0.72445 + } + ], + "id": "CVE-2024-41946", + "namespace": "nvd:cpe", + "severity": "High", + "urls": [ + "https://github.com/ruby/rexml/commit/033d1909a8f259d5a7c53681bcaf14f13bcf0368", + "https://github.com/ruby/rexml/security/advisories/GHSA-5866-49gr-22v4", + "https://www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexml", + "https://www.ruby-lang.org/en/news/2024/08/01/dos-rexml-cve-2024-41946", + "https://security.netapp.com/advisory/ntap-20250117-0007/" + ] + } + ], + "vulnerability": { + "advisories": [], + "cvss": [ + { + "metrics": { + "baseScore": 7.5, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + }, + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://github.com/advisories/GHSA-5866-49gr-22v4", + "description": "REXML DoS vulnerability", + "epss": [ + { + "cve": "CVE-2024-41946", + "date": "2025-03-31", + "epss": 0.00833, + "percentile": 0.72445 + } + ], + "fix": { + "state": "fixed", + "versions": [ + "3.3.3" + ] + }, + "id": "GHSA-5866-49gr-22v4", + "namespace": "github:language:ruby", + "severity": "Medium", + "urls": [] + } + }, + { + "artifact": { + "cpes": [ + "cpe:2.3:a:ruby-lang:rexml:3.2.5:*:*:*:*:ruby:*:*" + ], + "id": "a26fdaf742880a0e", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "rexml", + "purl": "pkg:gem/rexml@3.2.5", + "type": "gem", + "upstreams": [], + "version": "3.2.5" + }, + "matchDetails": [ + { + "fix": { + "suggestedVersion": "3.3.3" + }, + "found": { + "versionConstraint": "<3.3.3 (semver)", + "vulnerabilityID": "GHSA-r55c-59qm-vjw6" + }, + "matcher": "ruby-gem-matcher", + "searchedBy": { + "language": "ruby", + "namespace": "github:language:ruby", + "package": { + "name": "rexml", + "version": "3.2.5" + } + }, + "type": "exact-direct-match" + } + ], + "relatedVulnerabilities": [ + { + "cvss": [ + { + "metrics": { + "baseScore": 7.5, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + }, + "source": "nvd@nist.gov", + "type": "Primary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + }, + { + "metrics": { + "baseScore": 5.3, + "exploitabilityScore": 3.9, + "impactScore": 1.5 + }, + "source": "security-advisories@github.com", + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2024-41123", + "description": "REXML is an XML toolkit for Ruby. The REXML gem before 3.3.2 has some DoS vulnerabilities when it parses an XML that has many specific characters such as whitespace character, `>]` and `]>`. The REXML gem 3.3.3 or later include the patches to fix these vulnerabilities.", + "epss": [ + { + "cve": "CVE-2024-41123", + "date": "2025-03-31", + "epss": 0.00663, + "percentile": 0.68816 + } + ], + "id": "CVE-2024-41123", + "namespace": "nvd:cpe", + "severity": "High", + "urls": [ + "https://github.com/ruby/rexml/security/advisories/GHSA-4xqq-m2hx-25v8", + "https://github.com/ruby/rexml/security/advisories/GHSA-r55c-59qm-vjw6", + "https://github.com/ruby/rexml/security/advisories/GHSA-vg3r-rm7w-2xgh", + "https://www.ruby-lang.org/en/news/2024/08/01/dos-rexml-cve-2024-41123", + "https://security.netapp.com/advisory/ntap-20241227-0005/" + ] + } + ], + "vulnerability": { + "advisories": [], + "cvss": [ + { + "metrics": { + "baseScore": 7.5, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + }, + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://github.com/advisories/GHSA-r55c-59qm-vjw6", + "description": "REXML DoS vulnerability", + "epss": [ + { + "cve": "CVE-2024-41123", + "date": "2025-03-31", + "epss": 0.00663, + "percentile": 0.68816 + } + ], + "fix": { + "state": "fixed", + "versions": [ + "3.3.3" + ] + }, + "id": "GHSA-r55c-59qm-vjw6", + "namespace": "github:language:ruby", + "severity": "Medium", + "urls": [] + } + }, + { + "artifact": { + "cpes": [ + "cpe:2.3:a:ruby-lang:rexml:3.2.5:*:*:*:*:ruby:*:*" + ], + "id": "a26fdaf742880a0e", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "rexml", + "purl": "pkg:gem/rexml@3.2.5", + "type": "gem", + "upstreams": [], + "version": "3.2.5" + }, + "matchDetails": [ + { + "fix": { + "suggestedVersion": "3.2.7" + }, + "found": { + "versionConstraint": "<3.2.7 (semver)", + "vulnerabilityID": "GHSA-vg3r-rm7w-2xgh" + }, + "matcher": "ruby-gem-matcher", + "searchedBy": { + "language": "ruby", + "namespace": "github:language:ruby", + "package": { + "name": "rexml", + "version": "3.2.5" + } + }, + "type": "exact-direct-match" + } + ], + "relatedVulnerabilities": [ + { + "cvss": [ + { + "metrics": { + "baseScore": 5.3, + "exploitabilityScore": 3.9, + "impactScore": 1.5 + }, + "source": "security-advisories@github.com", + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2024-35176", + "description": "REXML is an XML toolkit for Ruby. The REXML gem before 3.2.6 has a denial of service vulnerability when it parses an XML that has many `<`s in an attribute value. Those who need to parse untrusted XMLs may be impacted to this vulnerability. The REXML gem 3.2.7 or later include the patch to fix this vulnerability. As a workaround, don't parse untrusted XMLs.", + "epss": [ + { + "cve": "CVE-2024-35176", + "date": "2025-03-31", + "epss": 0.25862, + "percentile": 0.95808 + } + ], + "id": "CVE-2024-35176", + "namespace": "nvd:cpe", + "severity": "Medium", + "urls": [ + "https://github.com/ruby/rexml/commit/4325835f92f3f142ebd91a3fdba4e1f1ab7f1cfb", + "https://github.com/ruby/rexml/security/advisories/GHSA-vg3r-rm7w-2xgh", + "https://www.ruby-lang.org/en/news/2024/05/16/dos-rexml-cve-2024-35176", + "https://github.com/ruby/rexml/commit/4325835f92f3f142ebd91a3fdba4e1f1ab7f1cfb", + "https://github.com/ruby/rexml/security/advisories/GHSA-vg3r-rm7w-2xgh", + "https://security.netapp.com/advisory/ntap-20250306-0001/", + "https://www.ruby-lang.org/en/news/2024/05/16/dos-rexml-cve-2024-35176" + ] + } + ], + "vulnerability": { + "advisories": [], + "cvss": [ + { + "metrics": { + "baseScore": 5.3, + "exploitabilityScore": 3.9, + "impactScore": 1.5 + }, + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://github.com/advisories/GHSA-vg3r-rm7w-2xgh", + "description": "REXML contains a denial of service vulnerability", + "epss": [ + { + "cve": "CVE-2024-35176", + "date": "2025-03-31", + "epss": 0.25862, + "percentile": 0.95808 + } + ], + "fix": { + "state": "fixed", + "versions": [ + "3.2.7" + ] + }, + "id": "GHSA-vg3r-rm7w-2xgh", + "namespace": "github:language:ruby", + "severity": "Medium", + "urls": [] + } + }, + { + "artifact": { + "cpes": [ + "cpe:2.3:a:ruby-lang:webrick:1.7.0:*:*:*:*:ruby:*:*" + ], + "id": "f2f039900cdff89e", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "webrick", + "purl": "pkg:gem/webrick@1.7.0", + "type": "gem", + "upstreams": [], + "version": "1.7.0" + }, + "matchDetails": [ + { + "fix": { + "suggestedVersion": "1.8.2" + }, + "found": { + "versionConstraint": "<=1.8.1 (semver)", + "vulnerabilityID": "GHSA-6f62-3596-g6w7" + }, + "matcher": "ruby-gem-matcher", + "searchedBy": { + "language": "ruby", + "namespace": "github:language:ruby", + "package": { + "name": "webrick", + "version": "1.7.0" + } + }, + "type": "exact-direct-match" + } + ], + "relatedVulnerabilities": [ + { + "cvss": [], + "dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2024-47220", + "description": "An issue was discovered in the WEBrick toolkit through 1.8.1 for Ruby. It allows HTTP request smuggling by providing both a Content-Length header and a Transfer-Encoding header, e.g., \"GET /admin HTTP/1.1\\r\\n\" inside of a \"POST /user HTTP/1.1\\r\\n\" request. NOTE: the supplier's position is \"Webrick should not be used in production.\"", + "epss": [ + { + "cve": "CVE-2024-47220", + "date": "2025-03-31", + "epss": 0.00083, + "percentile": 0.21361 + } + ], + "id": "CVE-2024-47220", + "namespace": "nvd:cpe", + "severity": "Unknown", + "urls": [ + "https://github.com/ruby/webrick/issues/145", + "https://github.com/ruby/webrick/issues/145#issuecomment-2369994610", + "https://github.com/ruby/webrick/issues/145#issuecomment-2372838285", + "https://github.com/ruby/webrick/pull/146/commits/d88321da45dcd230ac2b4585cad4833d6d5e8841" + ] + } + ], + "vulnerability": { + "advisories": [], + "cvss": [ + { + "metrics": { + "baseScore": 7.5, + "exploitabilityScore": 3.9, + "impactScore": 3.6 + }, + "type": "Secondary", + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "vendorMetadata": {}, + "version": "3.1" + } + ], + "dataSource": "https://github.com/advisories/GHSA-6f62-3596-g6w7", + "description": "HTTP Request Smuggling in ruby webrick", + "epss": [ + { + "cve": "CVE-2024-47220", + "date": "2025-03-31", + "epss": 0.00083, + "percentile": 0.21361 + } + ], + "fix": { + "state": "fixed", + "versions": [ + "1.8.2" + ] + }, + "id": "GHSA-6f62-3596-g6w7", + "namespace": "github:language:ruby", + "severity": "High", + "urls": [] + } + } + ], + "source": { + "target": "/src", + "type": "directory" + } +} diff --git a/tests/test_data/scanners/npmaudit/npm.json b/tests/test_data/scanners/npmaudit/npm.json new file mode 100644 index 0000000..bb45dd0 --- /dev/null +++ b/tests/test_data/scanners/npmaudit/npm.json @@ -0,0 +1,693 @@ +{ + "auditReportVersion": 2, + "metadata": { + "dependencies": { + "dev": 123, + "optional": 2, + "peer": 0, + "peerOptional": 0, + "prod": 1, + "total": 123 + }, + "vulnerabilities": { + "critical": 1, + "high": 5, + "info": 0, + "low": 1, + "moderate": 12, + "total": 19 + } + }, + "vulnerabilities": { + "@convergence/jointjs-utils": { + "effects": [], + "fixAvailable": false, + "isDirect": true, + "name": "@convergence/jointjs-utils", + "nodes": [ + "node_modules/@convergence/jointjs-utils" + ], + "range": "*", + "severity": "moderate", + "via": [ + "jointjs" + ] + }, + "@convergence/mxgraph-adapter": { + "effects": [], + "fixAvailable": { + "isSemVerMajor": true, + "name": "@convergence/mxgraph-adapter", + "version": "0.3.0" + }, + "isDirect": true, + "name": "@convergence/mxgraph-adapter", + "nodes": [ + "node_modules/@convergence/mxgraph-adapter" + ], + "range": "<=0.2.0", + "severity": "moderate", + "via": [ + "mxgraph" + ] + }, + "@convergencelabs/codemirror-collab-ext": { + "effects": [], + "fixAvailable": false, + "isDirect": true, + "name": "@convergencelabs/codemirror-collab-ext", + "nodes": [ + "node_modules/@convergencelabs/codemirror-collab-ext" + ], + "range": "*", + "severity": "moderate", + "via": [ + "codemirror" + ] + }, + "bootstrap": { + "effects": [], + "fixAvailable": { + "isSemVerMajor": true, + "name": "bootstrap", + "version": "5.3.3" + }, + "isDirect": true, + "name": "bootstrap", + "nodes": [ + "node_modules/bootstrap" + ], + "range": "4.0.0 - 4.6.2", + "severity": "moderate", + "via": [ + { + "cvss": { + "score": 6.4, + "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:L" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "bootstrap", + "name": "bootstrap", + "range": ">=4.0.0 <=4.6.2", + "severity": "moderate", + "source": 1099461, + "title": "Bootstrap Cross-Site Scripting (XSS) vulnerability", + "url": "https://github.com/advisories/GHSA-vc8w-jr9v-vj7f" + } + ] + }, + "braces": { + "effects": [], + "fixAvailable": true, + "isDirect": false, + "name": "braces", + "nodes": [ + "node_modules/braces" + ], + "range": "<3.0.3", + "severity": "high", + "via": [ + { + "cvss": { + "score": 7.5, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "cwe": [ + "CWE-400", + "CWE-1050" + ], + "dependency": "braces", + "name": "braces", + "range": "<3.0.3", + "severity": "high", + "source": 1098094, + "title": "Uncontrolled resource consumption in braces", + "url": "https://github.com/advisories/GHSA-grv7-fg5c-xmjg" + } + ] + }, + "codemirror": { + "effects": [ + "@convergencelabs/codemirror-collab-ext" + ], + "fixAvailable": false, + "isDirect": false, + "name": "codemirror", + "nodes": [ + "node_modules/@convergencelabs/codemirror-collab-ext/node_modules/codemirror" + ], + "range": "<5.58.2", + "severity": "moderate", + "via": [ + { + "cvss": { + "score": 5.3, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" + }, + "cwe": [ + "CWE-400" + ], + "dependency": "codemirror", + "name": "codemirror", + "range": "<5.58.2", + "severity": "moderate", + "source": 1089869, + "title": "Regular expression denial of service in codemirror", + "url": "https://github.com/advisories/GHSA-4gw3-8f77-f72c" + } + ] + }, + "easymde": { + "effects": [], + "fixAvailable": { + "isSemVerMajor": false, + "name": "easymde", + "version": "2.20.0" + }, + "isDirect": true, + "name": "easymde", + "nodes": [ + "node_modules/easymde" + ], + "range": "<=2.16.1-30.0", + "severity": "high", + "via": [ + "marked" + ] + }, + "froala-editor": { + "effects": [], + "fixAvailable": { + "isSemVerMajor": true, + "name": "froala-editor", + "version": "4.5.0" + }, + "isDirect": true, + "name": "froala-editor", + "nodes": [ + "node_modules/froala-editor" + ], + "range": "<=4.3.0", + "severity": "moderate", + "via": [ + { + "cvss": { + "score": 6.1, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "froala-editor", + "name": "froala-editor", + "range": "<3.2.3", + "severity": "moderate", + "source": 1089186, + "title": "DOM-based cross-site scripting in Froala Editor", + "url": "https://github.com/advisories/GHSA-h236-g5gh-vq6c" + }, + { + "cvss": { + "score": 6.1, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "froala-editor", + "name": "froala-editor", + "range": "<=3.2.6", + "severity": "moderate", + "source": 1089624, + "title": "Cross-site Scripting in Froala Editor", + "url": "https://github.com/advisories/GHSA-cq6w-w5rj-p9x8" + }, + { + "cvss": { + "score": 6.1, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "froala-editor", + "name": "froala-editor", + "range": "<=4.0.6", + "severity": "moderate", + "source": 1091063, + "title": "Cross site scripting in froala-editor", + "url": "https://github.com/advisories/GHSA-97x5-cc53-cv4v" + }, + { + "cvss": { + "score": 6.1, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "froala-editor", + "name": "froala-editor", + "range": "<=4.3.0", + "severity": "moderate", + "source": 1103101, + "title": "Froala WYSIWYG editor allows cross-site scripting (XSS)", + "url": "https://github.com/advisories/GHSA-549p-5c7f-c5p4" + } + ] + }, + "glob-parent": { + "effects": [], + "fixAvailable": true, + "isDirect": false, + "name": "glob-parent", + "nodes": [ + "node_modules/glob-parent" + ], + "range": "4.0.0 - 5.1.1", + "severity": "high", + "via": [ + { + "cvss": { + "score": 7.5, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "cwe": [ + "CWE-400" + ], + "dependency": "glob-parent", + "name": "glob-parent", + "range": ">=4.0.0 <5.1.2", + "severity": "high", + "source": 1097712, + "title": "glob-parent vulnerable to Regular Expression Denial of Service in enclosure regex", + "url": "https://github.com/advisories/GHSA-ww39-953v-wcq6" + } + ] + }, + "highlight.js": { + "effects": [ + "tui-editor" + ], + "fixAvailable": false, + "isDirect": false, + "name": "highlight.js", + "nodes": [ + "node_modules/highlight.js" + ], + "range": "9.0.0 - 10.4.0", + "severity": "moderate", + "via": [ + { + "cvss": { + "score": 0, + "vectorString": null + }, + "cwe": [ + "CWE-20", + "CWE-400" + ], + "dependency": "highlight.js", + "name": "highlight.js", + "range": ">=9.0.0 <10.4.1", + "severity": "moderate", + "source": 1086450, + "title": "ReDOS vulnerabities: multiple grammars", + "url": "https://github.com/advisories/GHSA-7wwv-vh3v-89cq" + } + ] + }, + "jointjs": { + "effects": [ + "@convergence/jointjs-utils" + ], + "fixAvailable": false, + "isDirect": false, + "name": "jointjs", + "nodes": [ + "node_modules/jointjs" + ], + "range": "<3.4.2", + "severity": "moderate", + "via": [ + { + "cvss": { + "score": 5.6, + "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L" + }, + "cwe": [ + "CWE-843", + "CWE-1321" + ], + "dependency": "jointjs", + "name": "jointjs", + "range": "<3.4.2", + "severity": "moderate", + "source": 1089638, + "title": "Prototype Pollution in jointjs", + "url": "https://github.com/advisories/GHSA-f3pp-32qc-36w4" + } + ] + }, + "markdown-it": { + "effects": [], + "fixAvailable": true, + "isDirect": false, + "name": "markdown-it", + "nodes": [ + "node_modules/markdown-it" + ], + "range": "<12.3.2", + "severity": "moderate", + "via": [ + { + "cvss": { + "score": 5.3, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" + }, + "cwe": [ + "CWE-400", + "CWE-1333" + ], + "dependency": "markdown-it", + "name": "markdown-it", + "range": "<12.3.2", + "severity": "moderate", + "source": 1092663, + "title": "Uncontrolled Resource Consumption in markdown-it", + "url": "https://github.com/advisories/GHSA-6vfc-qv3f-vr6c" + } + ] + }, + "marked": { + "effects": [ + "easymde" + ], + "fixAvailable": { + "isSemVerMajor": false, + "name": "easymde", + "version": "2.20.0" + }, + "isDirect": false, + "name": "marked", + "nodes": [ + "node_modules/marked" + ], + "range": "<=4.0.9", + "severity": "high", + "via": [ + { + "cvss": { + "score": 7.5, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "cwe": [ + "CWE-400", + "CWE-1333" + ], + "dependency": "marked", + "name": "marked", + "range": "<4.0.10", + "severity": "high", + "source": 1095051, + "title": "Inefficient Regular Expression Complexity in marked", + "url": "https://github.com/advisories/GHSA-rrrm-qjm4-v8hf" + }, + { + "cvss": { + "score": 7.5, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "cwe": [ + "CWE-1333" + ], + "dependency": "marked", + "name": "marked", + "range": "<4.0.10", + "severity": "high", + "source": 1095052, + "title": "Inefficient Regular Expression Complexity in marked", + "url": "https://github.com/advisories/GHSA-5v2h-r2cx-5xgj" + } + ] + }, + "materialize-css": { + "effects": [], + "fixAvailable": false, + "isDirect": true, + "name": "materialize-css", + "nodes": [ + "node_modules/materialize-css" + ], + "range": "*", + "severity": "moderate", + "via": [ + { + "cvss": { + "score": 5.4, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "materialize-css", + "name": "materialize-css", + "range": "<=1.0.0", + "severity": "moderate", + "source": 1089509, + "title": "materialize-css vulnerable to cross-site Scripting (XSS) due to improper escape of user input", + "url": "https://github.com/advisories/GHSA-7jvx-f994-rfw2" + }, + { + "cvss": { + "score": 6.1, + "vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "materialize-css", + "name": "materialize-css", + "range": "<=1.0.0", + "severity": "moderate", + "source": 1093152, + "title": "Materialize-css vulnerable to Cross-site Scripting in autocomplete component", + "url": "https://github.com/advisories/GHSA-7752-f4gf-94gc" + }, + { + "cvss": { + "score": 6.1, + "vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "materialize-css", + "name": "materialize-css", + "range": "<=1.0.0", + "severity": "moderate", + "source": 1093154, + "title": "Materialize-css vulnerable to Cross-site Scripting in tooltip component", + "url": "https://github.com/advisories/GHSA-98f7-p5rc-jx67" + }, + { + "cvss": { + "score": 6.1, + "vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "materialize-css", + "name": "materialize-css", + "range": "<=1.0.0", + "severity": "moderate", + "source": 1093156, + "title": "Materialize-css vulnerable to Improper Neutralization of Input During Web Page Generation", + "url": "https://github.com/advisories/GHSA-rg3q-jxmp-pvjj" + } + ] + }, + "minimist": { + "effects": [], + "fixAvailable": true, + "isDirect": false, + "name": "minimist", + "nodes": [ + "node_modules/minimist" + ], + "range": "1.0.0 - 1.2.5", + "severity": "critical", + "via": [ + { + "cvss": { + "score": 9.8, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + "cwe": [ + "CWE-1321" + ], + "dependency": "minimist", + "name": "minimist", + "range": ">=1.0.0 <1.2.6", + "severity": "critical", + "source": 1097678, + "title": "Prototype Pollution in minimist", + "url": "https://github.com/advisories/GHSA-xvch-5gv4-984h" + } + ] + }, + "moment": { + "effects": [], + "fixAvailable": { + "isSemVerMajor": false, + "name": "moment", + "version": "2.30.1" + }, + "isDirect": true, + "name": "moment", + "nodes": [ + "node_modules/moment" + ], + "range": "<=2.29.3", + "severity": "high", + "via": [ + { + "cvss": { + "score": 7.5, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "cwe": [ + "CWE-400", + "CWE-1333" + ], + "dependency": "moment", + "name": "moment", + "range": ">=2.18.0 <2.29.4", + "severity": "high", + "source": 1095072, + "title": "Moment.js vulnerable to Inefficient Regular Expression Complexity", + "url": "https://github.com/advisories/GHSA-wc69-rhjr-hc9g" + }, + { + "cvss": { + "score": 7.5, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N" + }, + "cwe": [ + "CWE-22", + "CWE-27" + ], + "dependency": "moment", + "name": "moment", + "range": "<2.29.2", + "severity": "high", + "source": 1095083, + "title": "Path Traversal: 'dir/../../filename' in moment.locale", + "url": "https://github.com/advisories/GHSA-8hfj-j24r-96c4" + } + ] + }, + "mxgraph": { + "effects": [ + "@convergence/mxgraph-adapter" + ], + "fixAvailable": { + "isSemVerMajor": true, + "name": "mxgraph", + "version": "4.2.2" + }, + "isDirect": true, + "name": "mxgraph", + "nodes": [ + "node_modules/mxgraph" + ], + "range": "*", + "severity": "moderate", + "via": [ + { + "cvss": { + "score": 6.1, + "vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "mxgraph", + "name": "mxgraph", + "range": "<=4.0.0", + "severity": "moderate", + "source": 1094386, + "title": "mxGraph vulnerable to cross-site scripting in color field", + "url": "https://github.com/advisories/GHSA-xm59-jvxm-cp3v" + }, + { + "cvss": { + "score": 6.1, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "cwe": [ + "CWE-79" + ], + "dependency": "mxgraph", + "name": "mxgraph", + "range": "<=4.2.2", + "severity": "moderate", + "source": 1094402, + "title": "mxGraph vulnerable to cross-site scripting in setTooltips function", + "url": "https://github.com/advisories/GHSA-j4rv-pr9g-q8jv" + } + ] + }, + "tui-editor": { + "effects": [], + "fixAvailable": false, + "isDirect": true, + "name": "tui-editor", + "nodes": [ + "node_modules/tui-editor" + ], + "range": "*", + "severity": "moderate", + "via": [ + "highlight.js", + "markdown-it" + ] + }, + "vue": { + "effects": [], + "fixAvailable": { + "isSemVerMajor": true, + "name": "vue", + "version": "3.5.13" + }, + "isDirect": true, + "name": "vue", + "nodes": [ + "node_modules/vue" + ], + "range": "2.0.0-alpha.1 - 2.7.16", + "severity": "low", + "via": [ + { + "cvss": { + "score": 3.7, + "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L" + }, + "cwe": [ + "CWE-1333" + ], + "dependency": "vue", + "name": "vue", + "range": ">=2.0.0-alpha.1 <3.0.0-alpha.0", + "severity": "low", + "source": 1100238, + "title": "ReDoS vulnerability in vue package that is exploitable through inefficient regex evaluation in the parseHTML function", + "url": "https://github.com/advisories/GHSA-5j4c-8p2g-v4jx" + } + ] + } + } +} diff --git a/tests/test_data/scanners/results/cdk_report_result.txt b/tests/test_data/scanners/results/cdk_report_result.txt new file mode 100644 index 0000000..62494fb --- /dev/null +++ b/tests/test_data/scanners/results/cdk_report_result.txt @@ -0,0 +1,37 @@ + +starting to investigate ... +found 3 files to scan. Starting scans ... +>>>>>> begin cdk-nag result for test.yaml >>>>>> +[Error at /test-yaml/--src--test.yaml/S3Bucket3] AwsSolutions-S1: The S3 Bucket has server access logs disabled. The bucket should have server access logging enabled to provide detailed records for the requests that are made to the bucket. + +[Error at /test-yaml/--src--test.yaml/S3Bucket3] AwsSolutions-S10: The S3 Bucket or bucket policy does not require requests to use SSL. You can use HTTPS (TLS) to help prevent potential attackers from eavesdropping on or manipulating network traffic using person-in-the-middle or similar attacks. You should allow only encrypted connections over HTTPS (TLS) using the aws:SecureTransport condition on Amazon S3 bucket policies. + +Found errors +Writing CDK-NAG reports for test.yaml +<<<<<< end cdk-nag result for test.yaml <<<<<< +>>>>>> begin cdk-nag result for test.yaml >>>>>> +[Error at /cfn-and-python-test-yaml/--src--cfn_and_python--test.yaml/S3Bucket2] AwsSolutions-S1: The S3 Bucket has server access logs disabled. The bucket should have server access logging enabled to provide detailed records for the requests that are made to the bucket. + +[Error at /cfn-and-python-test-yaml/--src--cfn_and_python--test.yaml/S3Bucket2] AwsSolutions-S10: The S3 Bucket or bucket policy does not require requests to use SSL. You can use HTTPS (TLS) to help prevent potential attackers from eavesdropping on or manipulating network traffic using person-in-the-middle or similar attacks. You should allow only encrypted connections over HTTPS (TLS) using the aws:SecureTransport condition on Amazon S3 bucket policies. + +Found errors +Writing CDK-NAG reports for test.yaml +<<<<<< end cdk-nag result for test.yaml <<<<<< +>>>>>> begin cdk-nag result for cfn-to-cdk.template.json >>>>>> +/tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/stack.js:2 +${val2}`;case"AWSTemplateFormatVersion":if(val1!=null&&val2!=null&&val1!==val2)throw new Error(`Conflicting CloudFormation template versions provided: '${val1}' and '${val2}`);return val1??val2;case"Transform":return mergeSets(val1,val2);default:return mergeObjectsWithoutDuplicates(section,val1,val2)}}function mergeSets(val1,val2){const array1=val1==null?[]:Array.isArray(val1)?val1:[val1],array2=val2==null?[]:Array.isArray(val2)?val2:[val2];for(const value of array2)array1.includes(value)||array1.push(value);return array1.length===1?array1[0]:array1}function mergeObjectsWithoutDuplicates(section,dest,src){if(typeof dest!="object")throw new Error(`Expecting ${JSON.stringify(dest)} to be an object`);if(typeof src!="object")throw new Error(`Expecting ${JSON.stringify(src)} to be an object`);for(const id of Object.keys(src)){if(id in dest)throw new Error(`section '${section}' already contains '${id}'`);dest[id]=src[id]}return dest}function cfnElements(node,into=[]){cfn_element_1().CfnElement.isCfnElement(node)&&into.push(node);for(const child of constructs_1().Node.of(node).children)Stack.isStack(child)||cfnElements(child,into);return into}function rootPathTo(construct,ancestor){const scopes=constructs_1().Node.of(construct).scopes;for(let i=scopes.length-2;i>=0;i--)if(scopes[i]===ancestor)return scopes.slice(i+1);return scopes}function makeStackName(components,prefix=""){if(components.length===1){const stack_name=prefix+components[0];if(stack_name.length<=128)return stack_name}return(0,unique_resource_name_1().makeUniqueResourceName)(components,{maxLength:128,prefix})}function getCreateExportsScope(stack){const exportsName="Exports";let stackExports=stack.node.tryFindChild(exportsName);return stackExports===void 0&&(stackExports=new(constructs_1()).Construct(stack,exportsName)),stackExports}function generateExportName(stackExports,id){const stackRelativeExports=feature_flags_1().FeatureFlags.of(stackExports).isEnabled(cxapi().STACK_RELATIVE_EXPORTS_CONTEXT),stack=Stack.of(stackExports),components=[...stackExports.node.scopes.slice(stackRelativeExports?stack.node.scopes.length:2).map(c=>c.node.id),id],prefix=stack.stackName?stack.stackName+":":"",localPart=(0,uniqueid_1().makeUniqueId)(components);return prefix+localPart.slice(Math.max(0,localPart.length-255+prefix.length))}function count(xs){const ret={};for(const x of xs)x in ret?ret[x]+=1:ret[x]=1;return ret}var cfn_output_1=()=>{var tmp=require("./cfn-output");return cfn_output_1=()=>tmp,tmp},deps_1=()=>{var tmp=require("./deps");return deps_1=()=>tmp,tmp},fs_1=()=>{var tmp=require("./fs");return fs_1=()=>tmp,tmp},names_1=()=>{var tmp=require("./names");return names_1=()=>tmp,tmp},reference_1=()=>{var tmp=require("./reference");return reference_1=()=>tmp,tmp},stack_synthesizers_1=()=>{var tmp=require("./stack-synthesizers");return stack_synthesizers_1=()=>tmp,tmp},string_specializer_1=()=>{var tmp=require("./helpers-internal/string-specializer");return string_specializer_1=()=>tmp,tmp},stage_1=()=>{var tmp=require("./stage");return stage_1=()=>tmp,tmp},tag_manager_1=()=>{var tmp=require("./tag-manager");return tag_manager_1=()=>tmp,tmp},token_1=()=>{var tmp=require("./token");return token_1=()=>tmp,tmp},refs_1=()=>{var tmp=require("./private/refs");return refs_1=()=>tmp,tmp},region_info_1=()=>{var tmp=require("../../region-info");return region_info_1=()=>tmp,tmp},region_lookup_1=()=>{var tmp=require("./private/region-lookup");return region_lookup_1=()=>tmp,tmp},unique_resource_name_1=()=>{var tmp=require("./private/unique-resource-name");return unique_resource_name_1=()=>tmp,tmp},private_context_1=()=>{var tmp=require("./private/private-context");return private_context_1=()=>tmp,tmp}; + ^ +Error: section 'Resources' already contains 'CDKMetadata' + at mergeObjectsWithoutDuplicates (/tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/stack.js:2:854) + at mergeSection (/tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/stack.js:2:254) + at merge (/tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/stack.js:1:23343) + at CdkNagScanStack._toCloudFormation (/tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/stack.js:1:20290) + at CdkNagScanStack._synthesizeTemplate (/tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/stack.js:1:14299) + at DefaultStackSynthesizer.synthesizeTemplate (/tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/stack-synthesizers/stack-synthesizer.js:1:2210) + at DefaultStackSynthesizer.synthesize (/tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/stack-synthesizers/default-synthesizer.js:1:6962) + at /tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/private/synthesis.js:2:5537 + at visit (/tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/private/synthesis.js:4:372) + at visit (/tmp/cdk-nag-scan.bD1xm/node_modules/aws-cdk-lib/core/lib/private/synthesis.js:4:334) +Subprocess exited with error 1 +Writing CDK-NAG reports for cfn-to-cdk.template.json +<<<<<< end cdk-nag result for cfn-to-cdk.template.json <<<<<< diff --git a/tests/test_data/scanners/results/git_report_result.txt b/tests/test_data/scanners/results/git_report_result.txt new file mode 100644 index 0000000..5ffa00a --- /dev/null +++ b/tests/test_data/scanners/results/git_report_result.txt @@ -0,0 +1,3 @@ +>>>>>> begin tree result >>>>>> +<<<<<< end tree -x -h -a --du -I .git result <<<<<< +Not in a git repository - skipping git checks diff --git a/tests/test_data/scanners/results/grype_report_result.txt b/tests/test_data/scanners/results/grype_report_result.txt new file mode 100644 index 0000000..2bccc66 --- /dev/null +++ b/tests/test_data/scanners/results/grype_report_result.txt @@ -0,0 +1,108 @@ + +>>>>>> Begin Grype output for /src >>>>>> + +[0000] WARN no explicit name and version provided for directory source, deriving artifact ID from the given path (which is not ideal) +[0028] ERROR discovered vulnerabilities at or above the severity threshold + +<<<<<< End Grype output for /src <<<<<< + + +>>>>>> Begin Grype output for /src/ash_output/work >>>>>> + +[0000] WARN no explicit name and version provided for directory source, deriving artifact ID from the given path (which is not ideal) + +<<<<<< End Grype output for /src/ash_output/work <<<<<< + + +>>>>>> Begin Syft output for /src >>>>>> + +[0002] WARN no explicit name and version provided for directory source, deriving artifact ID from the given path (which is not ideal) + +<<<<<< End Syft output for /src <<<<<< + + +>>>>>> Begin Syft output for /src/ash_output/work >>>>>> + +[0002] WARN no explicit name and version provided for directory source, deriving artifact ID from the given path (which is not ideal) + +<<<<<< End Syft output for /src/ash_output/work <<<<<< + + +>>>>>> Begin Semgrep output for /src >>>>>> + +METRICS: Using configs from the Registry (like --config=p/ci) reports pseudonymous rule metrics to semgrep.dev. +To disable Registry rule metrics, use "--metrics=off". +Using configs only from local files (like --config=xyz.yml) does not enable metrics. + +More information: https://semgrep.dev/docs/metrics + + + +┌─────────────┐ +│ Scan Status │ +└─────────────┘ + Scanning 201 files tracked by git with 1062 Code rules: + + Language Rules Files Origin Rules + ───────────────────────────── ─────────────────── + 72 132 Community 1062 + js 157 35 + html 1 26 + json 4 23 + python 243 11 + yaml 31 7 + bash 4 4 + dockerfile 5 1 + + + +┌──────────────┐ +│ Scan Summary │ +└──────────────┘ +Semgrep's file targeting is getting a revamp! This scan wouldn't see any changes. To learn about what will be different after version 1.116.0, visit https://semgrep.dev/docs/kb/semgrep-code/semgrepignore-ignored for the new specification. + +✅ Scan completed successfully. + • Findings: 36 (36 blocking) + • Rules run: 512 + • Targets scanned: 198 + • Parsed lines: ~100.0% + • Scan skipped: + ◦ Files matching .semgrepignore patterns: 3 + • For a detailed list of skipped files and lines, run semgrep with the --verbose flag +Ran 512 rules on 198 files: 36 findings. + +<<<<<< End Semgrep output for /src <<<<<< + + +>>>>>> Begin Semgrep output for /src/ash_output/work >>>>>> + + + +┌─────────────┐ +│ Scan Status │ +└─────────────┘ + Scanning 6 files tracked by git with 1062 Code rules: + + Language Rules Files Origin Rules + ───────────────────────────── ─────────────────── + 48 6 Community 1062 + python 243 3 + yaml 31 1 + + + +┌──────────────┐ +│ Scan Summary │ +└──────────────┘ +Semgrep's file targeting is getting a revamp! This scan wouldn't see any changes. To learn about what will be different after version 1.116.0, visit https://semgrep.dev/docs/kb/semgrep-code/semgrepignore-ignored for the new specification. + +✅ Scan completed successfully. + • Findings: 14 (14 blocking) + • Rules run: 321 + • Targets scanned: 6 + • Parsed lines: ~100.0% + • No ignore information available +Ran 321 rules on 6 files: 14 findings. + +<<<<<< End Semgrep output for /src/ash_output/work <<<<<< + diff --git a/tests/test_data/scanners/results/js_report_result.txt b/tests/test_data/scanners/results/js_report_result.txt new file mode 100644 index 0000000..23a46bc --- /dev/null +++ b/tests/test_data/scanners/results/js_report_result.txt @@ -0,0 +1,13 @@ + +>>>>>> Begin npm audit output for /src >>>>>> + + +<<<<<< End npm audit output for /src <<<<<< + + +>>>>>> Begin npm audit output for /src >>>>>> + +/ash/utils/js-docker-execute.sh: line 105: /src/ash_output/scanners/npmaudit/npm.json: cannot overwrite existing file + +<<<<<< End npm audit output for /src <<<<<< + diff --git a/tests/test_data/scanners/results/py_report_result.txt b/tests/test_data/scanners/results/py_report_result.txt new file mode 100644 index 0000000..fe51f21 --- /dev/null +++ b/tests/test_data/scanners/results/py_report_result.txt @@ -0,0 +1,21 @@ +>>>>>> begin identifyipynb output for Jupyter notebook conversion >>>>>> +[2025-04-01 23:03:54] DEBUG: [ipynb] pwd: '/src' :: _ASH_SOURCE_DIR: /src :: _ASH_RUN_DIR: /tmp/ash-run-scan.qVrF +Looking for Jupyter notebook files +Found ./ipynb.ipynb + + +<<<<<< end identifyipynb output for Jupyter notebook conversion <<<<<< +>>>>>> begin bandit result for /src >>>>>> +[main] INFO profile include tests: None +[main] INFO profile exclude tests: None +[main] INFO cli include tests: None +[main] INFO cli exclude tests: None + +<<<<<< end bandit result for /src <<<<<< +>>>>>> begin bandit result for /src/ash_output/work >>>>>> +[main] INFO profile include tests: None +[main] INFO profile exclude tests: None +[main] INFO cli include tests: None +[main] INFO cli exclude tests: None + +<<<<<< end bandit result for /src/ash_output/work <<<<<< diff --git a/tests/test_data/scanners/results/yaml_report_result.txt b/tests/test_data/scanners/results/yaml_report_result.txt new file mode 100644 index 0000000..b93fe5a --- /dev/null +++ b/tests/test_data/scanners/results/yaml_report_result.txt @@ -0,0 +1,28 @@ + +>>>>>> Begin yaml scan output for /src >>>>>> + +starting to investigate ... +found 3 files to scan. Starting checkov scans ... +>>>>>> begin checkov result for Dockerfile >>>>>> +<<<<<< end checkov result for Dockerfile <<<<<< +>>>>>> begin checkov result for test.yaml >>>>>> +<<<<<< end checkov result for test.yaml <<<<<< +>>>>>> begin checkov result for test.yaml >>>>>> +<<<<<< end checkov result for test.yaml <<<<<< +found 2 files to scan. Starting cfn_nag scans ... +>>>>>> begin cfn_nag_scan result for test.yaml >>>>>> +<<<<<< end cfn_nag_scan result for test.yaml <<<<<< +>>>>>> begin cfn_nag_scan result for test.yaml >>>>>> +<<<<<< end cfn_nag_scan result for test.yaml <<<<<< + +<<<<<< End yaml scan output for /src <<<<<< + + +>>>>>> Begin yaml scan output for /src/ash_output/work >>>>>> + +starting to investigate ... +found 0 files to scan. Skipping checkov scans. +found 0 files to scan. Skipping cfn_nag scans. + +<<<<<< End yaml scan output for /src/ash_output/work <<<<<< + diff --git a/tests/test_data/scanners/semgrep/semgrep.src.ash_output.work.json b/tests/test_data/scanners/semgrep/semgrep.src.ash_output.work.json new file mode 100644 index 0000000..97ca3c0 --- /dev/null +++ b/tests/test_data/scanners/semgrep/semgrep.src.ash_output.work.json @@ -0,0 +1,749 @@ +{ + "errors": [], + "paths": { + "scanned": [ + "/src/ash_output/work/bla/dummy1.py", + "/src/ash_output/work/bla/dummy_creds", + "/src/ash_output/work/bla2/dummy2.py", + "/src/ash_output/work/bla2/dummy_creds2", + "/src/ash_output/work/ipynb.ipynb-converted.py", + "/src/ash_output/work/zip_w_space/dir with space/test.yaml" + ] + }, + "results": [ + { + "check_id": "generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value", + "end": { + "col": 41, + "line": 2, + "offset": 50 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "AWS Access Key ID Value detected. This is a sensitive credential and should not be hardcoded here. Instead, read this value from an environment variable or keep it in a separate, private file.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-798: Use of Hard-coded Credentials" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "HIGH", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2021 - Identification and Authentication Failures" + ], + "references": [ + "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures" + ], + "shortlink": "https://sg.run/GeD1", + "source": "https://semgrep.dev/r/generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value", + "source-rule-url": "https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go", + "subcategory": [ + "audit" + ], + "technology": [ + "secrets", + "aws" + ], + "vulnerability_class": [ + "Hard-coded Secrets" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/bla/dummy_creds", + "start": { + "col": 21, + "line": 2, + "offset": 30 + } + }, + { + "check_id": "generic.secrets.security.detected-aws-secret-access-key.detected-aws-secret-access-key", + "end": { + "col": 65, + "line": 3, + "offset": 115 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "AWS Secret Access Key detected", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-798: Use of Hard-coded Credentials" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "HIGH", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2021 - Identification and Authentication Failures" + ], + "references": [ + "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures" + ], + "shortlink": "https://sg.run/Bk39", + "source": "https://semgrep.dev/r/generic.secrets.security.detected-aws-secret-access-key.detected-aws-secret-access-key", + "source-rule-url": "https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go", + "subcategory": [ + "audit" + ], + "technology": [ + "secrets", + "aws" + ], + "vulnerability_class": [ + "Hard-coded Secrets" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/bla/dummy_creds", + "start": { + "col": 1, + "line": 3, + "offset": 51 + } + }, + { + "check_id": "generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value", + "end": { + "col": 41, + "line": 2, + "offset": 50 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "AWS Access Key ID Value detected. This is a sensitive credential and should not be hardcoded here. Instead, read this value from an environment variable or keep it in a separate, private file.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-798: Use of Hard-coded Credentials" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "HIGH", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2021 - Identification and Authentication Failures" + ], + "references": [ + "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures" + ], + "shortlink": "https://sg.run/GeD1", + "source": "https://semgrep.dev/r/generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value", + "source-rule-url": "https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go", + "subcategory": [ + "audit" + ], + "technology": [ + "secrets", + "aws" + ], + "vulnerability_class": [ + "Hard-coded Secrets" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/bla2/dummy_creds2", + "start": { + "col": 21, + "line": 2, + "offset": 30 + } + }, + { + "check_id": "generic.secrets.security.detected-aws-secret-access-key.detected-aws-secret-access-key", + "end": { + "col": 65, + "line": 3, + "offset": 115 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "AWS Secret Access Key detected", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-798: Use of Hard-coded Credentials" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "HIGH", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2021 - Identification and Authentication Failures" + ], + "references": [ + "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures" + ], + "shortlink": "https://sg.run/Bk39", + "source": "https://semgrep.dev/r/generic.secrets.security.detected-aws-secret-access-key.detected-aws-secret-access-key", + "source-rule-url": "https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go", + "subcategory": [ + "audit" + ], + "technology": [ + "secrets", + "aws" + ], + "vulnerability_class": [ + "Hard-coded Secrets" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/bla2/dummy_creds2", + "start": { + "col": 1, + "line": 3, + "offset": 51 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 40, + "line": 202, + "offset": 8265 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 8, + "line": 202, + "offset": 8233 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 23, + "line": 203, + "offset": 8288 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 203, + "offset": 8272 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 33, + "line": 206, + "offset": 8354 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 1, + "line": 206, + "offset": 8322 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 26, + "line": 208, + "offset": 8397 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 208, + "offset": 8378 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 29, + "line": 211, + "offset": 8445 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 211, + "offset": 8423 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 40, + "line": 226, + "offset": 9219 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 8, + "line": 226, + "offset": 9187 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 23, + "line": 227, + "offset": 9242 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 227, + "offset": 9226 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 33, + "line": 230, + "offset": 9308 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 1, + "line": 230, + "offset": 9276 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 26, + "line": 232, + "offset": 9351 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 232, + "offset": 9332 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 29, + "line": 235, + "offset": 9399 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 235, + "offset": 9377 + } + } + ], + "skipped_rules": [], + "version": "1.116.0" +} diff --git a/tests/test_data/scanners/semgrep/semgrep.src.json b/tests/test_data/scanners/semgrep/semgrep.src.json new file mode 100644 index 0000000..e2e17a3 --- /dev/null +++ b/tests/test_data/scanners/semgrep/semgrep.src.json @@ -0,0 +1,3411 @@ +{ + "errors": [ + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/ash_output/scanners/grype/semgrep.src.json:1:\n missing element", + "path": "/src/ash_output/scanners/grype/semgrep.src.json", + "type": "Syntax error" + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/content-editable/index.html:1:\n `---\nlayout: example\npermalink: /examples/content-editable/\nexampleId: content-editable\nstylesheets:\n - /examples/content-editable/content-editable.css\noverview: |-\n This example demonstrates shared rich text editing using a basic` was unexpected", + "path": "/src/js/src/examples/content-editable/index.html", + "spans": [ + { + "end": { + "col": 67, + "line": 8, + "offset": 231 + }, + "file": "/src/js/src/examples/content-editable/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 32, + "line": 10, + "offset": 29 + }, + "file": "/src/js/src/examples/content-editable/index.html", + "start": { + "col": 3, + "line": 10, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 12, + "offset": 94 + }, + "file": "/src/js/src/examples/content-editable/index.html", + "start": { + "col": 3, + "line": 11, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 67, + "line": 8, + "offset": 231 + }, + "path": "/src/js/src/examples/content-editable/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 32, + "line": 10, + "offset": 29 + }, + "path": "/src/js/src/examples/content-editable/index.html", + "start": { + "col": 3, + "line": 10, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 12, + "offset": 94 + }, + "path": "/src/js/src/examples/content-editable/index.html", + "start": { + "col": 3, + "line": 11, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/chartjs/index.html:1:\n `---\nlayout: example\npermalink: /examples/chartjs/\nexampleId: chartjs\nstylesheets:\n - /libs/nouislider/distribute/nouislider.min.css\n - /examples/chartjs/chart.css\noverview: |-\n This example demonstrates modifying visualized data, in real time, using shared controls. The chart is rendered\n using the` was unexpected", + "path": "/src/js/src/examples/chartjs/index.html", + "spans": [ + { + "end": { + "col": 12, + "line": 10, + "offset": 303 + }, + "file": "/src/js/src/examples/chartjs/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 12, + "offset": 85 + }, + "file": "/src/js/src/examples/chartjs/index.html", + "start": { + "col": 61, + "line": 10, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 12, + "line": 10, + "offset": 303 + }, + "path": "/src/js/src/examples/chartjs/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 12, + "offset": 85 + }, + "path": "/src/js/src/examples/chartjs/index.html", + "start": { + "col": 61, + "line": 10, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/_layouts/example.html:1:\n `---\nlayout: default\n---\n{% assign example = site.data.examples[page.exampleId] %}\n\n\n\n
    \n
    \n
    \n
    {{example.title}}
    \n
    \n ` was unexpected", + "path": "/src/js/src/_layouts/example.html", + "spans": [ + { + "end": { + "col": 118, + "line": 13, + "offset": 535 + }, + "file": "/src/js/src/_layouts/example.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 13, + "line": 16, + "offset": 4 + }, + "file": "/src/js/src/_layouts/example.html", + "start": { + "col": 9, + "line": 16, + "offset": 0 + } + }, + { + "end": { + "col": 11, + "line": 26, + "offset": 17 + }, + "file": "/src/js/src/_layouts/example.html", + "start": { + "col": 7, + "line": 25, + "offset": 0 + } + }, + { + "end": { + "col": 9, + "line": 28, + "offset": 6 + }, + "file": "/src/js/src/_layouts/example.html", + "start": { + "col": 3, + "line": 28, + "offset": 0 + } + }, + { + "end": { + "col": 7, + "line": 37, + "offset": 13 + }, + "file": "/src/js/src/_layouts/example.html", + "start": { + "col": 1, + "line": 36, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 118, + "line": 13, + "offset": 535 + }, + "path": "/src/js/src/_layouts/example.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 13, + "line": 16, + "offset": 4 + }, + "path": "/src/js/src/_layouts/example.html", + "start": { + "col": 9, + "line": 16, + "offset": 0 + } + }, + { + "end": { + "col": 11, + "line": 26, + "offset": 17 + }, + "path": "/src/js/src/_layouts/example.html", + "start": { + "col": 7, + "line": 25, + "offset": 0 + } + }, + { + "end": { + "col": 9, + "line": 28, + "offset": 6 + }, + "path": "/src/js/src/_layouts/example.html", + "start": { + "col": 3, + "line": 28, + "offset": 0 + } + }, + { + "end": { + "col": 7, + "line": 37, + "offset": 13 + }, + "path": "/src/js/src/_layouts/example.html", + "start": { + "col": 1, + "line": 36, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/froala/index.html:1:\n `---\nlayout: example\npermalink: /examples/froala/\nexampleId: froala\nstylesheets:\n - /examples/froala/froala.css\n - /libs/froala-editor/css/plugins/table.min.css\n - /libs/froala-editor/css/froala_editor.pkgd.min.css\n - /libs/froala-editor/css/froala_style.min.css\n - /libs/froala-editor/css/plugins/colors.min.css\noverview: |-\n This example demonstrates shared rich text editing the` was unexpected", + "path": "/src/js/src/examples/froala/index.html", + "spans": [ + { + "end": { + "col": 57, + "line": 12, + "offset": 386 + }, + "file": "/src/js/src/examples/froala/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 90, + "line": 13, + "offset": 22 + }, + "file": "/src/js/src/examples/froala/index.html", + "start": { + "col": 68, + "line": 13, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 16, + "offset": 85 + }, + "file": "/src/js/src/examples/froala/index.html", + "start": { + "col": 3, + "line": 15, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 57, + "line": 12, + "offset": 386 + }, + "path": "/src/js/src/examples/froala/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 90, + "line": 13, + "offset": 22 + }, + "path": "/src/js/src/examples/froala/index.html", + "start": { + "col": 68, + "line": 13, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 16, + "offset": 85 + }, + "path": "/src/js/src/examples/froala/index.html", + "start": { + "col": 3, + "line": 15, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/tui-editor/index.html:1:\n `---\nlayout: example\npermalink: /examples/tui-editor/\nexampleId: tui-editor\nstylesheets:\n - /examples/tui-editor/tui-editor.css\n - /libs/tui-editor/dist/tui-editor.css\n - /libs/tui-editor/dist/tui-editor-contents.css\n - /libs/codemirror/lib/codemirror.css\n - /libs/highlight.js/styles/github.css\n - /libs/@convergencelabs/codemirror-collab-ext/css/codemirror-collab-ext.css\noverview: |-\n This example integrates the` was unexpected", + "path": "/src/js/src/examples/tui-editor/index.html", + "spans": [ + { + "end": { + "col": 30, + "line": 13, + "offset": 421 + }, + "file": "/src/js/src/examples/tui-editor/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 73, + "line": 14, + "offset": 70 + }, + "file": "/src/js/src/examples/tui-editor/index.html", + "start": { + "col": 3, + "line": 14, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 18, + "offset": 194 + }, + "file": "/src/js/src/examples/tui-editor/index.html", + "start": { + "col": 52, + "line": 15, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 30, + "line": 13, + "offset": 421 + }, + "path": "/src/js/src/examples/tui-editor/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 73, + "line": 14, + "offset": 70 + }, + "path": "/src/js/src/examples/tui-editor/index.html", + "start": { + "col": 3, + "line": 14, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 18, + "offset": 194 + }, + "path": "/src/js/src/examples/tui-editor/index.html", + "start": { + "col": 52, + "line": 15, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/monaco/index.html:1:\n `---\nlayout: example\npermalink: /examples/monaco/\nexampleId: monaco\nstylesheets:\n - /libs/monaco-editor/min/vs/editor/editor.main.css\n - /libs/@convergencelabs/monaco-collab-ext/css/monaco-collab-ext.css\n - /examples/monaco/monaco.css\noverview: |-\n This example demonstrates building a realtime source code editor with the` was unexpected", + "path": "/src/js/src/examples/monaco/index.html", + "spans": [ + { + "end": { + "col": 76, + "line": 10, + "offset": 325 + }, + "file": "/src/js/src/examples/monaco/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 96, + "line": 11, + "offset": 23 + }, + "file": "/src/js/src/examples/monaco/index.html", + "start": { + "col": 73, + "line": 11, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 14, + "offset": 112 + }, + "file": "/src/js/src/examples/monaco/index.html", + "start": { + "col": 102, + "line": 12, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 76, + "line": 10, + "offset": 325 + }, + "path": "/src/js/src/examples/monaco/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 96, + "line": 11, + "offset": 23 + }, + "path": "/src/js/src/examples/monaco/index.html", + "start": { + "col": 73, + "line": 11, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 14, + "offset": 112 + }, + "path": "/src/js/src/examples/monaco/index.html", + "start": { + "col": 102, + "line": 12, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/collaborative-textarea/index.html:1:\n `---\nlayout: example\npermalink: /examples/collaborative-textarea/\nexampleId: collaborative-textarea\nstylesheets:\n - /examples/collaborative-textarea/example.css\n - /examples/collaborative-textarea/example.css\n - /libs/@convergence/html-text-collab-ext/css/html-text-collab-ext.css\"\noverview: |-\n This example demonstrates using the Convergence` was unexpected", + "path": "/src/js/src/examples/collaborative-textarea/index.html", + "spans": [ + { + "end": { + "col": 50, + "line": 10, + "offset": 346 + }, + "file": "/src/js/src/examples/collaborative-textarea/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 98, + "line": 11, + "offset": 56 + }, + "file": "/src/js/src/examples/collaborative-textarea/index.html", + "start": { + "col": 42, + "line": 11, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 14, + "offset": 122 + }, + "file": "/src/js/src/examples/collaborative-textarea/index.html", + "start": { + "col": 33, + "line": 12, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 50, + "line": 10, + "offset": 346 + }, + "path": "/src/js/src/examples/collaborative-textarea/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 98, + "line": 11, + "offset": 56 + }, + "path": "/src/js/src/examples/collaborative-textarea/index.html", + "start": { + "col": 42, + "line": 11, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 14, + "offset": 122 + }, + "path": "/src/js/src/examples/collaborative-textarea/index.html", + "start": { + "col": 33, + "line": 12, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/mxgraph/index.html:1:\n `---\nlayout: example\npermalink: /examples/mxgraph/\nexampleId: mxgraph\nstylesheets:\n - /examples/mxgraph/mxgraph.css\noverview: |-\n This example demonstrates shared editing of a graph visualized with the` was unexpected", + "path": "/src/js/src/examples/mxgraph/index.html", + "spans": [ + { + "end": { + "col": 74, + "line": 8, + "offset": 202 + }, + "file": "/src/js/src/examples/mxgraph/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 88, + "line": 9, + "offset": 29 + }, + "file": "/src/js/src/examples/mxgraph/index.html", + "start": { + "col": 59, + "line": 9, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 13, + "offset": 148 + }, + "file": "/src/js/src/examples/mxgraph/index.html", + "start": { + "col": 3, + "line": 11, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 74, + "line": 8, + "offset": 202 + }, + "path": "/src/js/src/examples/mxgraph/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 88, + "line": 9, + "offset": 29 + }, + "path": "/src/js/src/examples/mxgraph/index.html", + "start": { + "col": 59, + "line": 9, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 13, + "offset": 148 + }, + "path": "/src/js/src/examples/mxgraph/index.html", + "start": { + "col": 3, + "line": 11, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/jointjs/index.html:1:\n `---\nlayout: example\npermalink: /examples/jointjs/\nexampleId: jointjs\nstylesheets:\n - /libs/jointjs/dist/joint.css\n - /examples/jointjs/jointjs.css\n - /libs/@convergence/jointjs-utils/dist/css/convergence-jointjs-utils.css\noverview: |-\n This example demonstrates shared editing of a graph visualized with the` was unexpected", + "path": "/src/js/src/examples/jointjs/index.html", + "spans": [ + { + "end": { + "col": 74, + "line": 10, + "offset": 311 + }, + "file": "/src/js/src/examples/jointjs/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 80, + "line": 11, + "offset": 29 + }, + "file": "/src/js/src/examples/jointjs/index.html", + "start": { + "col": 51, + "line": 11, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 15, + "offset": 149 + }, + "file": "/src/js/src/examples/jointjs/index.html", + "start": { + "col": 3, + "line": 13, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 74, + "line": 10, + "offset": 311 + }, + "path": "/src/js/src/examples/jointjs/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 80, + "line": 11, + "offset": 29 + }, + "path": "/src/js/src/examples/jointjs/index.html", + "start": { + "col": 51, + "line": 11, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 15, + "offset": 149 + }, + "path": "/src/js/src/examples/jointjs/index.html", + "start": { + "col": 3, + "line": 13, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/buddy-list/index.html:1:\n `---\nlayout: example\npermalink: /examples/buddy-list/\nexampleId: buddy-list\nstylesheets:\n - /examples/buddy-list/buddy-list.css\noverview: |-\n This example demonstrates how to use the Presence API to create a simple buddy list.\n Users will show as online or offline depending on if there is at last on session connected\n for that users. This example is written in` was unexpected", + "path": "/src/js/src/examples/buddy-list/index.html", + "spans": [ + { + "end": { + "col": 45, + "line": 10, + "offset": 365 + }, + "file": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 86, + "line": 10, + "offset": 1 + }, + "file": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 85, + "line": 10, + "offset": 0 + } + }, + { + "end": { + "col": 64, + "line": 13, + "offset": 27 + }, + "file": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 37, + "line": 13, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 14, + "offset": 3 + }, + "file": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 1, + "line": 14, + "offset": 0 + } + }, + { + "end": { + "col": 10, + "line": 19, + "offset": 9 + }, + "file": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 1, + "line": 19, + "offset": 0 + } + }, + { + "end": { + "col": 13, + "line": 68, + "offset": 12 + }, + "file": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 1, + "line": 68, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 45, + "line": 10, + "offset": 365 + }, + "path": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 86, + "line": 10, + "offset": 1 + }, + "path": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 85, + "line": 10, + "offset": 0 + } + }, + { + "end": { + "col": 64, + "line": 13, + "offset": 27 + }, + "path": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 37, + "line": 13, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 14, + "offset": 3 + }, + "path": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 1, + "line": 14, + "offset": 0 + } + }, + { + "end": { + "col": 10, + "line": 19, + "offset": 9 + }, + "path": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 1, + "line": 19, + "offset": 0 + } + }, + { + "end": { + "col": 13, + "line": 68, + "offset": 12 + }, + "path": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 1, + "line": 68, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/ace/index.html:1:\n `---\nlayout: example\npermalink: /examples/ace/\nexampleId: ace\nstylesheets:\n - /libs/@convergence/ace-collab-ext/css/ace-collab-ext.css\n - /examples/ace/ace.css\noverview: |-\n This example demonstrates building a realtime source code editor with the` was unexpected", + "path": "/src/js/src/examples/ace/index.html", + "spans": [ + { + "end": { + "col": 76, + "line": 9, + "offset": 249 + }, + "file": "/src/js/src/examples/ace/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 69, + "line": 10, + "offset": 23 + }, + "file": "/src/js/src/examples/ace/index.html", + "start": { + "col": 46, + "line": 10, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 13, + "offset": 102 + }, + "file": "/src/js/src/examples/ace/index.html", + "start": { + "col": 96, + "line": 11, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 76, + "line": 9, + "offset": 249 + }, + "path": "/src/js/src/examples/ace/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 69, + "line": 10, + "offset": 23 + }, + "path": "/src/js/src/examples/ace/index.html", + "start": { + "col": 46, + "line": 10, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 13, + "offset": 102 + }, + "path": "/src/js/src/examples/ace/index.html", + "start": { + "col": 96, + "line": 11, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/easymde/index.html:1:\n `---\nlayout: example\npermalink: /examples/easymde/\nexampleId: easymde\nstylesheets:\n - /libs/easymde/dist/easymde.min.css\n - /libs/@convergencelabs/codemirror-collab-ext/css/codemirror-collab-ext.css\n - /examples/easymde/easymde.css\noverview: |-\n This example demonstrates editing of markdown with styling applied in line using the` was unexpected", + "path": "/src/js/src/examples/easymde/index.html", + "spans": [ + { + "end": { + "col": 87, + "line": 10, + "offset": 333 + }, + "file": "/src/js/src/examples/easymde/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 7, + "line": 12, + "offset": 33 + }, + "file": "/src/js/src/examples/easymde/index.html", + "start": { + "col": 57, + "line": 11, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 14, + "offset": 120 + }, + "file": "/src/js/src/examples/easymde/index.html", + "start": { + "col": 57, + "line": 12, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 87, + "line": 10, + "offset": 333 + }, + "path": "/src/js/src/examples/easymde/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 7, + "line": 12, + "offset": 33 + }, + "path": "/src/js/src/examples/easymde/index.html", + "start": { + "col": 57, + "line": 11, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 14, + "offset": 120 + }, + "path": "/src/js/src/examples/easymde/index.html", + "start": { + "col": 57, + "line": 12, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/index.html:1:\n `---\ntitle: The Realtime Collaboration Engine\nlayout: default\npermalink: /\n---` was unexpected", + "path": "/src/js/src/index.html", + "spans": [ + { + "end": { + "col": 4, + "line": 5, + "offset": 77 + }, + "file": "/src/js/src/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 4, + "line": 5, + "offset": 77 + }, + "path": "/src/js/src/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + } + ] + ] + }, + { + "code": 3, + "level": "warn", + "message": "Syntax error at line /src/js/src/examples/codemirror/index.html:1:\n `---\nlayout: example\npermalink: /examples/codemirror/\nexampleId: codemirror\nstylesheets:\n - /libs/codemirror/lib/codemirror.css\n - /libs/@convergencelabs/codemirror-collab-ext/css/codemirror-collab-ext.css\n - /examples/codemirror/codemirror.css\noverview: |-\n This example demonstrates building a realtime source code editor with the` was unexpected", + "path": "/src/js/src/examples/codemirror/index.html", + "spans": [ + { + "end": { + "col": 76, + "line": 10, + "offset": 335 + }, + "file": "/src/js/src/examples/codemirror/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 81, + "line": 11, + "offset": 23 + }, + "file": "/src/js/src/examples/codemirror/index.html", + "start": { + "col": 58, + "line": 11, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 14, + "offset": 112 + }, + "file": "/src/js/src/examples/codemirror/index.html", + "start": { + "col": 110, + "line": 12, + "offset": 0 + } + } + ], + "type": [ + "PartialParsing", + [ + { + "end": { + "col": 76, + "line": 10, + "offset": 335 + }, + "path": "/src/js/src/examples/codemirror/index.html", + "start": { + "col": 1, + "line": 1, + "offset": 0 + } + }, + { + "end": { + "col": 81, + "line": 11, + "offset": 23 + }, + "path": "/src/js/src/examples/codemirror/index.html", + "start": { + "col": 58, + "line": 11, + "offset": 0 + } + }, + { + "end": { + "col": 4, + "line": 14, + "offset": 112 + }, + "path": "/src/js/src/examples/codemirror/index.html", + "start": { + "col": 110, + "line": 12, + "offset": 0 + } + } + ] + ] + } + ], + "paths": { + "scanned": [ + "/src/.DS_Store", + "/src/.gitignore", + "/src/ash_output/ash-ignore-report.txt", + "/src/ash_output/ash-scan-set-files-list.txt", + "/src/ash_output/scanners/bandit/src.ash_output.work.json", + "/src/ash_output/scanners/bandit/src.json", + "/src/ash_output/scanners/cdk/cfn-to-cdk.template.json_cdk_nag_results/AwsSolutions-cfn-and-python-ash-cf2cdk-output-test-yaml-cdk-nag-results-cfn-to-cdk-template-json-NagReport.csv", + "/src/ash_output/scanners/cdk/src.cfn_and_python.ash_cf2cdk_output.test.yaml_cdk_nag_results.cfn-to-cdk.template.json.txt", + "/src/ash_output/scanners/cdk/src.cfn_and_python.test.yaml.txt", + "/src/ash_output/scanners/cdk/src.test.yaml.txt", + "/src/ash_output/scanners/cdk/test.yaml_cdk_nag_results/AwsSolutions-cfn-and-python-test-yaml-NagReport.csv", + "/src/ash_output/scanners/cdk/test.yaml_cdk_nag_results/AwsSolutions-test-yaml-NagReport.csv", + "/src/ash_output/scanners/cdk/test.yaml_cdk_nag_results/cfn-and-python-test-yaml.template.json", + "/src/ash_output/scanners/cdk/test.yaml_cdk_nag_results/test-yaml.template.json", + "/src/ash_output/scanners/cfn_nag/src.cfn_and_python.test.yaml.txt", + "/src/ash_output/scanners/cfn_nag/src.test.yaml.txt", + "/src/ash_output/scanners/checkov/src.cfn_and_python.test.yaml.json", + "/src/ash_output/scanners/checkov/src.js.docker.Dockerfile.json", + "/src/ash_output/scanners/checkov/src.test.yaml.json", + "/src/ash_output/scanners/git/tree.txt", + "/src/ash_output/scanners/grype/grype.src.ash_output.work.json", + "/src/ash_output/scanners/grype/grype.src.json", + "/src/ash_output/scanners/grype/semgrep.src.json", + "/src/ash_output/scanners/grype/syft.src.ash_output.work.json", + "/src/ash_output/scanners/grype/syft.src.json", + "/src/ash_output/scanners/npmaudit/npm.json", + "/src/ash_output/scanners/results/cdk_report_result.txt", + "/src/ash_output/scanners/results/git_report_result.txt", + "/src/ash_output/scanners/results/grype_report_result.txt", + "/src/ash_output/scanners/results/js_report_result.txt", + "/src/ash_output/scanners/results/py_report_result.txt", + "/src/ash_output/scanners/results/yaml_report_result.txt", + "/src/ash_output/work/bla/dummy1.py", + "/src/ash_output/work/bla/dummy_creds", + "/src/ash_output/work/bla2/dummy2.py", + "/src/ash_output/work/bla2/dummy_creds2", + "/src/ash_output/work/ipynb.ipynb-converted.py", + "/src/ash_output/work/zip_w_space/dir with space/test.yaml", + "/src/bla.zip", + "/src/bla2.zip", + "/src/cdk/.DS_Store", + "/src/cdk/README.md", + "/src/cdk/app/.DS_Store", + "/src/cdk/app/__init__.py", + "/src/cdk/app/__pycache__/__init__.cpython-38.pyc", + "/src/cdk/app/__pycache__/__init__.cpython-39.pyc", + "/src/cdk/app/__pycache__/app_stack.cpython-38.pyc", + "/src/cdk/app/__pycache__/app_stack.cpython-39.pyc", + "/src/cdk/app/app_stack.py", + "/src/cdk/app copy.py", + "/src/cdk/app.py", + "/src/cdk/cdk.json", + "/src/cdk/cdk.out/AppStack.assets.json", + "/src/cdk/cdk.out/AppStack.template.json", + "/src/cdk/cdk.out/AwsSolutions-AppStack-NagReport.csv", + "/src/cdk/cdk.out/cdk.out", + "/src/cdk/cdk.out/manifest.json", + "/src/cdk/cdk.out/tree.json", + "/src/cdk/replace.py", + "/src/cdk/requirements-dev.txt", + "/src/cdk/requirements.txt", + "/src/cdk/source.bat", + "/src/cfn_and_python/.DS_Store", + "/src/cfn_and_python/ash_cf2cdk_output/.DS_Store", + "/src/cfn_and_python/ash_cf2cdk_output/test.yaml_cdk_nag_results/AwsSolutions-cfn-to-cdk-NagReport.csv", + "/src/cfn_and_python/ash_cf2cdk_output/test.yaml_cdk_nag_results/cfn-to-cdk.template.json", + "/src/cfn_and_python/py-test-dll.py", + "/src/cfn_and_python/test.yaml", + "/src/dummy2.py", + "/src/dummy_creds2", + "/src/ipynb.ipynb", + "/src/js/.DS_Store", + "/src/js/.gitignore", + "/src/js/.travis.yml", + "/src/js/Gemfile", + "/src/js/Gemfile.lock", + "/src/js/LICENSE", + "/src/js/README.md", + "/src/js/_config.yml", + "/src/js/docker/Dockerfile", + "/src/js/docker/bin/boot.sh", + "/src/js/docker/confd/conf.d/config.js.toml", + "/src/js/docker/nginx/default.conf", + "/src/js/package-lock.json", + "/src/js/package.json", + "/src/js/scripts/build-materialize.js", + "/src/js/scripts/clean.js", + "/src/js/scripts/copy-libs.js", + "/src/js/scripts/docker-build.sh", + "/src/js/scripts/docker-prepare.sh", + "/src/js/scripts/docker-run.sh", + "/src/js/src/.DS_Store", + "/src/js/src/_data/categories.yaml", + "/src/js/src/_data/examples.yaml", + "/src/js/src/_includes/footer.html", + "/src/js/src/_includes/header.html", + "/src/js/src/_includes/html-footer.html", + "/src/js/src/_includes/html-head.html", + "/src/js/src/_includes/meta-data.html", + "/src/js/src/_includes/scripts.html", + "/src/js/src/_includes/side-nav-group.html", + "/src/js/src/_includes/side-nav.html", + "/src/js/src/_layouts/default.html", + "/src/js/src/_layouts/example.html", + "/src/js/src/assets/.DS_Store", + "/src/js/src/assets/css/examples-common.css", + "/src/js/src/assets/css/materialize.min.css", + "/src/js/src/assets/css/style.css", + "/src/js/src/assets/favicon/favicon-16x16.png", + "/src/js/src/assets/favicon/favicon-32x32.png", + "/src/js/src/assets/favicon/favicon-96x96.png", + "/src/js/src/assets/favicon/favicon.ico", + "/src/js/src/assets/fonts/CenturyGothic.ttf", + "/src/js/src/assets/img/GitHub-Mark-32px.png", + "/src/js/src/assets/img/GitHub-Mark-Light-32px.png", + "/src/js/src/assets/img/logo.png", + "/src/js/src/assets/js/example-helpers.js", + "/src/js/src/assets/js/exampleId.js", + "/src/js/src/assets/js/examples.js", + "/src/js/src/assets/js/js_report_result.txt", + "/src/js/src/assets/js/package-lock.json", + "/src/js/src/config.example.js", + "/src/js/src/examples/.DS_Store", + "/src/js/src/examples/ace/README.md", + "/src/js/src/examples/ace/ace.css", + "/src/js/src/examples/ace/ace.js", + "/src/js/src/examples/ace/default_editor_contents.js", + "/src/js/src/examples/ace/index.html", + "/src/js/src/examples/buddy-list/README.md", + "/src/js/src/examples/buddy-list/buddy-list.css", + "/src/js/src/examples/buddy-list/buddy-list.js", + "/src/js/src/examples/buddy-list/index.html", + "/src/js/src/examples/buddy-list/users.js", + "/src/js/src/examples/chartjs/README.md", + "/src/js/src/examples/chartjs/chart.css", + "/src/js/src/examples/chartjs/chart.js", + "/src/js/src/examples/chartjs/index.html", + "/src/js/src/examples/chat-room/README.md", + "/src/js/src/examples/chat-room/chat-components.js", + "/src/js/src/examples/chat-room/chat-room.css", + "/src/js/src/examples/chat-room/chat-room.js", + "/src/js/src/examples/chat-room/index.html", + "/src/js/src/examples/codemirror/README.md", + "/src/js/src/examples/codemirror/codemirror-adapter.js", + "/src/js/src/examples/codemirror/codemirror.css", + "/src/js/src/examples/codemirror/codemirror.js", + "/src/js/src/examples/codemirror/default_editor_contents.js", + "/src/js/src/examples/codemirror/index.html", + "/src/js/src/examples/collaborative-textarea/README.md", + "/src/js/src/examples/collaborative-textarea/data.js", + "/src/js/src/examples/collaborative-textarea/example.css", + "/src/js/src/examples/collaborative-textarea/example.js", + "/src/js/src/examples/collaborative-textarea/index.html", + "/src/js/src/examples/content-editable/content-editable.css", + "/src/js/src/examples/content-editable/content-editable.js", + "/src/js/src/examples/content-editable/index.html", + "/src/js/src/examples/easymde/README.md", + "/src/js/src/examples/easymde/default_editor_contents.js", + "/src/js/src/examples/easymde/easymde.css", + "/src/js/src/examples/easymde/easymde.js", + "/src/js/src/examples/easymde/index.html", + "/src/js/src/examples/froala/README.md", + "/src/js/src/examples/froala/froala.css", + "/src/js/src/examples/froala/froala.js", + "/src/js/src/examples/froala/index.html", + "/src/js/src/examples/input-elements/README.md", + "/src/js/src/examples/input-elements/example.css", + "/src/js/src/examples/input-elements/example.js", + "/src/js/src/examples/input-elements/index.html", + "/src/js/src/examples/jointjs/default-graph-data.js", + "/src/js/src/examples/jointjs/index.html", + "/src/js/src/examples/jointjs/jointjs.css", + "/src/js/src/examples/jointjs/jointjs.js", + "/src/js/src/examples/monaco/README.md", + "/src/js/src/examples/monaco/default_editor_contents.js", + "/src/js/src/examples/monaco/index.html", + "/src/js/src/examples/monaco/monaco-adapter.js", + "/src/js/src/examples/monaco/monaco.css", + "/src/js/src/examples/monaco/monaco.js", + "/src/js/src/examples/mxgraph/default-graph.js", + "/src/js/src/examples/mxgraph/index.html", + "/src/js/src/examples/mxgraph/mxgraph.css", + "/src/js/src/examples/mxgraph/mxgraph.js", + "/src/js/src/examples/pointer/README.md", + "/src/js/src/examples/pointer/assets/cursor.png", + "/src/js/src/examples/pointer/index.html", + "/src/js/src/examples/pointer/pointer.css", + "/src/js/src/examples/pointer/pointer.js", + "/src/js/src/examples/tui-editor/README.md", + "/src/js/src/examples/tui-editor/default_editor_contents.js", + "/src/js/src/examples/tui-editor/index.html", + "/src/js/src/examples/tui-editor/tui-editor.css", + "/src/js/src/examples/tui-editor/tui-editor.js", + "/src/js/src/index.html", + "/src/js/src/optional.js", + "/src/py-test-dll.py", + "/src/test.yaml", + "/src/zip_w_space.zip" + ] + }, + "results": [ + { + "check_id": "generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value", + "end": { + "col": 41, + "line": 2, + "offset": 50 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "AWS Access Key ID Value detected. This is a sensitive credential and should not be hardcoded here. Instead, read this value from an environment variable or keep it in a separate, private file.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-798: Use of Hard-coded Credentials" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "HIGH", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2021 - Identification and Authentication Failures" + ], + "references": [ + "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures" + ], + "shortlink": "https://sg.run/GeD1", + "source": "https://semgrep.dev/r/generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value", + "source-rule-url": "https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go", + "subcategory": [ + "audit" + ], + "technology": [ + "secrets", + "aws" + ], + "vulnerability_class": [ + "Hard-coded Secrets" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/bla/dummy_creds", + "start": { + "col": 21, + "line": 2, + "offset": 30 + } + }, + { + "check_id": "generic.secrets.security.detected-aws-secret-access-key.detected-aws-secret-access-key", + "end": { + "col": 65, + "line": 3, + "offset": 115 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "AWS Secret Access Key detected", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-798: Use of Hard-coded Credentials" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "HIGH", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2021 - Identification and Authentication Failures" + ], + "references": [ + "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures" + ], + "shortlink": "https://sg.run/Bk39", + "source": "https://semgrep.dev/r/generic.secrets.security.detected-aws-secret-access-key.detected-aws-secret-access-key", + "source-rule-url": "https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go", + "subcategory": [ + "audit" + ], + "technology": [ + "secrets", + "aws" + ], + "vulnerability_class": [ + "Hard-coded Secrets" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/bla/dummy_creds", + "start": { + "col": 1, + "line": 3, + "offset": 51 + } + }, + { + "check_id": "generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value", + "end": { + "col": 41, + "line": 2, + "offset": 50 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "AWS Access Key ID Value detected. This is a sensitive credential and should not be hardcoded here. Instead, read this value from an environment variable or keep it in a separate, private file.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-798: Use of Hard-coded Credentials" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "HIGH", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2021 - Identification and Authentication Failures" + ], + "references": [ + "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures" + ], + "shortlink": "https://sg.run/GeD1", + "source": "https://semgrep.dev/r/generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value", + "source-rule-url": "https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go", + "subcategory": [ + "audit" + ], + "technology": [ + "secrets", + "aws" + ], + "vulnerability_class": [ + "Hard-coded Secrets" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/bla2/dummy_creds2", + "start": { + "col": 21, + "line": 2, + "offset": 30 + } + }, + { + "check_id": "generic.secrets.security.detected-aws-secret-access-key.detected-aws-secret-access-key", + "end": { + "col": 65, + "line": 3, + "offset": 115 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "AWS Secret Access Key detected", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-798: Use of Hard-coded Credentials" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "HIGH", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2021 - Identification and Authentication Failures" + ], + "references": [ + "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures" + ], + "shortlink": "https://sg.run/Bk39", + "source": "https://semgrep.dev/r/generic.secrets.security.detected-aws-secret-access-key.detected-aws-secret-access-key", + "source-rule-url": "https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go", + "subcategory": [ + "audit" + ], + "technology": [ + "secrets", + "aws" + ], + "vulnerability_class": [ + "Hard-coded Secrets" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/bla2/dummy_creds2", + "start": { + "col": 1, + "line": 3, + "offset": 51 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 40, + "line": 202, + "offset": 8265 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 8, + "line": 202, + "offset": 8233 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 23, + "line": 203, + "offset": 8288 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 203, + "offset": 8272 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 33, + "line": 206, + "offset": 8354 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 1, + "line": 206, + "offset": 8322 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 26, + "line": 208, + "offset": 8397 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 208, + "offset": 8378 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 29, + "line": 211, + "offset": 8445 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 211, + "offset": 8423 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 40, + "line": 226, + "offset": 9219 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 8, + "line": 226, + "offset": 9187 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 23, + "line": 227, + "offset": 9242 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 227, + "offset": 9226 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 33, + "line": 230, + "offset": 9308 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 1, + "line": 230, + "offset": 9276 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 26, + "line": 232, + "offset": 9351 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 232, + "offset": 9332 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 29, + "line": 235, + "offset": 9399 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/ash_output/work/ipynb.ipynb-converted.py", + "start": { + "col": 7, + "line": 235, + "offset": 9377 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 40, + "line": 5, + "offset": 75 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/cfn_and_python/py-test-dll.py", + "start": { + "col": 8, + "line": 5, + "offset": 43 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 23, + "line": 6, + "offset": 98 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/cfn_and_python/py-test-dll.py", + "start": { + "col": 7, + "line": 6, + "offset": 82 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 33, + "line": 9, + "offset": 164 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/cfn_and_python/py-test-dll.py", + "start": { + "col": 1, + "line": 9, + "offset": 132 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 26, + "line": 11, + "offset": 207 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/cfn_and_python/py-test-dll.py", + "start": { + "col": 7, + "line": 11, + "offset": 188 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 29, + "line": 14, + "offset": 255 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/cfn_and_python/py-test-dll.py", + "start": { + "col": 7, + "line": 14, + "offset": 233 + } + }, + { + "check_id": "generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value", + "end": { + "col": 41, + "line": 2, + "offset": 50 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "AWS Access Key ID Value detected. This is a sensitive credential and should not be hardcoded here. Instead, read this value from an environment variable or keep it in a separate, private file.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-798: Use of Hard-coded Credentials" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "HIGH", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2021 - Identification and Authentication Failures" + ], + "references": [ + "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures" + ], + "shortlink": "https://sg.run/GeD1", + "source": "https://semgrep.dev/r/generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value", + "source-rule-url": "https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go", + "subcategory": [ + "audit" + ], + "technology": [ + "secrets", + "aws" + ], + "vulnerability_class": [ + "Hard-coded Secrets" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/dummy_creds2", + "start": { + "col": 21, + "line": 2, + "offset": 30 + } + }, + { + "check_id": "generic.secrets.security.detected-aws-secret-access-key.detected-aws-secret-access-key", + "end": { + "col": 65, + "line": 3, + "offset": 115 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "AWS Secret Access Key detected", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-798: Use of Hard-coded Credentials" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "HIGH", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2021 - Identification and Authentication Failures" + ], + "references": [ + "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures" + ], + "shortlink": "https://sg.run/Bk39", + "source": "https://semgrep.dev/r/generic.secrets.security.detected-aws-secret-access-key.detected-aws-secret-access-key", + "source-rule-url": "https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go", + "subcategory": [ + "audit" + ], + "technology": [ + "secrets", + "aws" + ], + "vulnerability_class": [ + "Hard-coded Secrets" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/dummy_creds2", + "start": { + "col": 1, + "line": 3, + "offset": 51 + } + }, + { + "check_id": "dockerfile.security.missing-user.missing-user", + "end": { + "col": 17, + "line": 17, + "offset": 428 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "fix": "USER non-root\nCMD [\"/boot.sh\"]", + "lines": "requires login", + "message": "By not specifying a USER, a program in the container may run as 'root'. This is a security hazard. If an attacker can control a process running as root, they may have control over the container. Ensure that the last USER in a Dockerfile is a USER other than 'root'.", + "metadata": { + "category": "security", + "confidence": "MEDIUM", + "cwe": [ + "CWE-269: Improper Privilege Management" + ], + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A04:2021 - Insecure Design" + ], + "references": [ + "https://owasp.org/Top10/A04_2021-Insecure_Design" + ], + "shortlink": "https://sg.run/Gbvn", + "source": "https://semgrep.dev/r/dockerfile.security.missing-user.missing-user", + "subcategory": [ + "audit" + ], + "technology": [ + "dockerfile" + ], + "vulnerability_class": [ + "Improper Authorization" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/js/docker/Dockerfile", + "start": { + "col": 1, + "line": 17, + "offset": 412 + } + }, + { + "check_id": "generic.nginx.security.insecure-redirect.insecure-redirect", + "end": { + "col": 15, + "line": 18, + "offset": 339 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Detected an insecure redirect in this nginx configuration. If no scheme is specified, nginx will forward the request with the incoming scheme. This could result in unencrypted communications. To fix this, include the 'https' scheme.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-319: Cleartext Transmission of Sensitive Information" + ], + "impact": "LOW", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A03:2017 - Sensitive Data Exposure", + "A02:2021 - Cryptographic Failures" + ], + "references": [ + "https://owasp.org/Top10/A02_2021-Cryptographic_Failures" + ], + "shortlink": "https://sg.run/8y14", + "source": "https://semgrep.dev/r/generic.nginx.security.insecure-redirect.insecure-redirect", + "subcategory": [ + "audit" + ], + "technology": [ + "nginx" + ], + "vulnerability_class": [ + "Mishandled Sensitive Information" + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/js/docker/nginx/default.conf", + "start": { + "col": 15, + "line": 10, + "offset": 173 + } + }, + { + "check_id": "html.security.audit.missing-integrity.missing-integrity", + "end": { + "col": 69, + "line": 17, + "offset": 715 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "This tag is missing an 'integrity' subresource integrity attribute. The 'integrity' attribute allows for the browser to verify that externally hosted files (for example from a CDN) are delivered without unexpected manipulation. Without this attribute, if an attacker can modify the externally hosted resource, this could lead to XSS and other types of attacks. To prevent this, include the base64-encoded cryptographic hash of the resource (file) you\u2019re telling the browser to fetch in the 'integrity' attribute for all externally hosted files.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-353: Missing Support for Integrity Check" + ], + "impact": "LOW", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures" + ], + "shortlink": "https://sg.run/krXA", + "source": "https://semgrep.dev/r/html.security.audit.missing-integrity.missing-integrity", + "subcategory": [ + "audit" + ], + "technology": [ + "html" + ], + "vulnerability_class": [ + "Cryptographic Issues" + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/js/src/examples/buddy-list/index.html", + "start": { + "col": 1, + "line": 17, + "offset": 647 + } + }, + { + "check_id": "javascript.browser.security.insecure-document-method.insecure-document-method", + "end": { + "col": 58, + "line": 6, + "offset": 209 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "LOW", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2017 - Cross-Site Scripting (XSS)", + "A03:2021 - Injection" + ], + "references": [ + "https://owasp.org/Top10/A03_2021-Injection" + ], + "shortlink": "https://sg.run/LwA9", + "source": "https://semgrep.dev/r/javascript.browser.security.insecure-document-method.insecure-document-method", + "subcategory": [ + "audit" + ], + "technology": [ + "browser" + ], + "vulnerability_class": [ + "Cross-Site-Scripting (XSS)" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/js/src/examples/collaborative-textarea/example.js", + "start": { + "col": 1, + "line": 6, + "offset": 152 + } + }, + { + "check_id": "html.security.audit.missing-integrity.missing-integrity", + "end": { + "col": 68, + "line": 21, + "offset": 802 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "This tag is missing an 'integrity' subresource integrity attribute. The 'integrity' attribute allows for the browser to verify that externally hosted files (for example from a CDN) are delivered without unexpected manipulation. Without this attribute, if an attacker can modify the externally hosted resource, this could lead to XSS and other types of attacks. To prevent this, include the base64-encoded cryptographic hash of the resource (file) you\u2019re telling the browser to fetch in the 'integrity' attribute for all externally hosted files.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-353: Missing Support for Integrity Check" + ], + "impact": "LOW", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures" + ], + "shortlink": "https://sg.run/krXA", + "source": "https://semgrep.dev/r/html.security.audit.missing-integrity.missing-integrity", + "subcategory": [ + "audit" + ], + "technology": [ + "html" + ], + "vulnerability_class": [ + "Cryptographic Issues" + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/js/src/examples/froala/index.html", + "start": { + "col": 1, + "line": 21, + "offset": 735 + } + }, + { + "check_id": "javascript.browser.security.insecure-document-method.insecure-document-method", + "end": { + "col": 67, + "line": 35, + "offset": 1105 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "LOW", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2017 - Cross-Site Scripting (XSS)", + "A03:2021 - Injection" + ], + "references": [ + "https://owasp.org/Top10/A03_2021-Injection" + ], + "shortlink": "https://sg.run/LwA9", + "source": "https://semgrep.dev/r/javascript.browser.security.insecure-document-method.insecure-document-method", + "subcategory": [ + "audit" + ], + "technology": [ + "browser" + ], + "vulnerability_class": [ + "Cross-Site-Scripting (XSS)" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/js/src/examples/pointer/pointer.js", + "start": { + "col": 5, + "line": 35, + "offset": 1043 + } + }, + { + "check_id": "javascript.browser.security.insecure-document-method.insecure-document-method", + "end": { + "col": 106, + "line": 110, + "offset": 3507 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "LOW", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2017 - Cross-Site Scripting (XSS)", + "A03:2021 - Injection" + ], + "references": [ + "https://owasp.org/Top10/A03_2021-Injection" + ], + "shortlink": "https://sg.run/LwA9", + "source": "https://semgrep.dev/r/javascript.browser.security.insecure-document-method.insecure-document-method", + "subcategory": [ + "audit" + ], + "technology": [ + "browser" + ], + "vulnerability_class": [ + "Cross-Site-Scripting (XSS)" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/js/src/examples/pointer/pointer.js", + "start": { + "col": 3, + "line": 110, + "offset": 3404 + } + }, + { + "check_id": "javascript.browser.security.insecure-document-method.insecure-document-method", + "end": { + "col": 87, + "line": 175, + "offset": 5372 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "LOW", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2017 - Cross-Site Scripting (XSS)", + "A03:2021 - Injection" + ], + "references": [ + "https://owasp.org/Top10/A03_2021-Injection" + ], + "shortlink": "https://sg.run/LwA9", + "source": "https://semgrep.dev/r/javascript.browser.security.insecure-document-method.insecure-document-method", + "subcategory": [ + "audit" + ], + "technology": [ + "browser" + ], + "vulnerability_class": [ + "Cross-Site-Scripting (XSS)" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/js/src/examples/pointer/pointer.js", + "start": { + "col": 3, + "line": 175, + "offset": 5288 + } + }, + { + "check_id": "javascript.browser.security.insecure-document-method.insecure-document-method", + "end": { + "col": 49, + "line": 192, + "offset": 5890 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "LOW", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2017 - Cross-Site Scripting (XSS)", + "A03:2021 - Injection" + ], + "references": [ + "https://owasp.org/Top10/A03_2021-Injection" + ], + "shortlink": "https://sg.run/LwA9", + "source": "https://semgrep.dev/r/javascript.browser.security.insecure-document-method.insecure-document-method", + "subcategory": [ + "audit" + ], + "technology": [ + "browser" + ], + "vulnerability_class": [ + "Cross-Site-Scripting (XSS)" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/js/src/examples/pointer/pointer.js", + "start": { + "col": 5, + "line": 192, + "offset": 5846 + } + }, + { + "check_id": "javascript.browser.security.insecure-document-method.insecure-document-method", + "end": { + "col": 103, + "line": 214, + "offset": 6485 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "LOW", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A07:2017 - Cross-Site Scripting (XSS)", + "A03:2021 - Injection" + ], + "references": [ + "https://owasp.org/Top10/A03_2021-Injection" + ], + "shortlink": "https://sg.run/LwA9", + "source": "https://semgrep.dev/r/javascript.browser.security.insecure-document-method.insecure-document-method", + "subcategory": [ + "audit" + ], + "technology": [ + "browser" + ], + "vulnerability_class": [ + "Cross-Site-Scripting (XSS)" + ] + }, + "severity": "ERROR", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/js/src/examples/pointer/pointer.js", + "start": { + "col": 3, + "line": 214, + "offset": 6385 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 40, + "line": 5, + "offset": 75 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/py-test-dll.py", + "start": { + "col": 8, + "line": 5, + "offset": 43 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 23, + "line": 6, + "offset": 98 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/py-test-dll.py", + "start": { + "col": 7, + "line": 6, + "offset": 82 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 33, + "line": 9, + "offset": 164 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/py-test-dll.py", + "start": { + "col": 1, + "line": 9, + "offset": 132 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 26, + "line": 11, + "offset": 207 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/py-test-dll.py", + "start": { + "col": 7, + "line": 11, + "offset": 188 + } + }, + { + "check_id": "python.lang.security.deserialization.pickle.avoid-dill", + "end": { + "col": 29, + "line": 14, + "offset": 255 + }, + "extra": { + "engine_kind": "OSS", + "fingerprint": "requires login", + "lines": "requires login", + "message": "Avoid using `dill`, which uses `pickle`, which is known to lead to code execution vulnerabilities. When unpickling, the serialized data could be manipulated to run arbitrary code. Instead, consider serializing the relevant data as JSON or a similar text-based serialization format.", + "metadata": { + "category": "security", + "confidence": "LOW", + "cwe": [ + "CWE-502: Deserialization of Untrusted Data" + ], + "cwe2021-top25": true, + "cwe2022-top25": true, + "impact": "MEDIUM", + "license": "Semgrep Rules License v1.0. For more details, visit semgrep.dev/legal/rules-license", + "likelihood": "LOW", + "owasp": [ + "A08:2017 - Insecure Deserialization", + "A08:2021 - Software and Data Integrity Failures" + ], + "references": [ + "https://docs.python.org/3/library/pickle.html" + ], + "shortlink": "https://sg.run/vzjA", + "source": "https://semgrep.dev/r/python.lang.security.deserialization.pickle.avoid-dill", + "subcategory": [ + "audit" + ], + "technology": [ + "python" + ], + "vulnerability_class": [ + "Insecure Deserialization " + ] + }, + "severity": "WARNING", + "validation_state": "NO_VALIDATOR" + }, + "path": "/src/py-test-dll.py", + "start": { + "col": 7, + "line": 14, + "offset": 233 + } + } + ], + "skipped_rules": [], + "version": "1.116.0" +} diff --git a/tests/test_data/scanners/syft/syft.src.ash_output.work.json b/tests/test_data/scanners/syft/syft.src.ash_output.work.json new file mode 100644 index 0000000..29d1a8c --- /dev/null +++ b/tests/test_data/scanners/syft/syft.src.ash_output.work.json @@ -0,0 +1,212 @@ +{ + "artifactRelationships": [], + "artifacts": [], + "descriptor": { + "configuration": { + "catalogers": { + "requested": { + "default": [ + "directory", + "file" + ] + }, + "used": [ + "alpm-db-cataloger", + "apk-db-cataloger", + "binary-classifier-cataloger", + "cargo-auditable-binary-cataloger", + "cocoapods-cataloger", + "conan-cataloger", + "dart-pubspec-lock-cataloger", + "deb-archive-cataloger", + "dotnet-deps-binary-cataloger", + "dotnet-packages-lock-cataloger", + "dpkg-db-cataloger", + "elf-binary-package-cataloger", + "elixir-mix-lock-cataloger", + "erlang-otp-application-cataloger", + "erlang-rebar-lock-cataloger", + "file-content-cataloger", + "file-digest-cataloger", + "file-executable-cataloger", + "file-metadata-cataloger", + "github-action-workflow-usage-cataloger", + "github-actions-usage-cataloger", + "go-module-binary-cataloger", + "go-module-file-cataloger", + "graalvm-native-image-cataloger", + "haskell-cataloger", + "java-archive-cataloger", + "java-gradle-lockfile-cataloger", + "java-jvm-cataloger", + "java-pom-cataloger", + "javascript-lock-cataloger", + "linux-kernel-cataloger", + "lua-rock-cataloger", + "nix-store-cataloger", + "opam-cataloger", + "php-composer-lock-cataloger", + "php-pecl-serialized-cataloger", + "portage-cataloger", + "python-installed-package-cataloger", + "python-package-cataloger", + "rpm-archive-cataloger", + "rpm-db-cataloger", + "ruby-gemfile-cataloger", + "ruby-gemspec-cataloger", + "rust-cargo-lock-cataloger", + "swift-package-manager-cataloger", + "swipl-pack-cataloger", + "terraform-lock-cataloger", + "wordpress-plugins-cataloger" + ] + }, + "data-generation": { + "generate-cpes": true + }, + "files": { + "content": { + "globs": null, + "skip-files-above-size": 0 + }, + "hashers": [ + "sha-1", + "sha-256" + ], + "selection": "owned-by-package" + }, + "licenses": { + "coverage": 75, + "include-unknown-license-content": false + }, + "packages": { + "binary": [ + "python-binary", + "python-binary-lib", + "pypy-binary-lib", + "go-binary", + "julia-binary", + "helm", + "redis-binary", + "java-binary-openjdk", + "java-binary-ibm", + "java-binary-oracle", + "java-binary-graalvm", + "java-binary-jdk", + "nodejs-binary", + "go-binary-hint", + "busybox-binary", + "util-linux-binary", + "haproxy-binary", + "perl-binary", + "php-cli-binary", + "php-fpm-binary", + "php-apache-binary", + "php-composer-binary", + "httpd-binary", + "memcached-binary", + "traefik-binary", + "arangodb-binary", + "postgresql-binary", + "mysql-binary", + "mysql-binary", + "mysql-binary", + "xtrabackup-binary", + "mariadb-binary", + "rust-standard-library-linux", + "rust-standard-library-macos", + "ruby-binary", + "erlang-binary", + "erlang-alpine-binary", + "erlang-library", + "swipl-binary", + "dart-binary", + "haskell-ghc-binary", + "haskell-cabal-binary", + "haskell-stack-binary", + "consul-binary", + "nginx-binary", + "bash-binary", + "openssl-binary", + "gcc-binary", + "fluent-bit-binary", + "wordpress-cli-binary", + "curl-binary", + "lighttpd-binary", + "proftpd-binary", + "zstd-binary", + "xz-binary", + "gzip-binary", + "sqlcipher-binary", + "jq-binary" + ], + "dotnet": { + "dep-packages-must-claim-dll": true, + "dep-packages-must-have-dll": false, + "relax-dll-claims-when-bundling-detected": true + }, + "golang": { + "local-mod-cache-dir": "/home/ash-user/go/pkg/mod", + "local-vendor-dir": "", + "main-module-version": { + "from-build-settings": true, + "from-contents": true, + "from-ld-flags": true + }, + "proxies": [ + "https://proxy.golang.org", + "direct" + ], + "search-local-mod-cache-licenses": false, + "search-local-vendor-licenses": false, + "search-remote-licenses": false + }, + "java-archive": { + "include-indexed-archives": true, + "include-unindexed-archives": false, + "maven-base-url": "https://repo1.maven.org/maven2", + "maven-localrepository-dir": "/home/ash-user/.m2/repository", + "max-parent-recursive-depth": 0, + "resolve-transitive-dependencies": false, + "use-maven-localrepository": false, + "use-network": false + }, + "javascript": { + "include-dev-dependencies": false, + "npm-base-url": "https://registry.npmjs.org", + "search-remote-licenses": false + }, + "linux-kernel": { + "catalog-modules": true + }, + "python": { + "guess-unpinned-requirements": false + } + }, + "relationships": { + "exclude-binary-packages-with-file-ownership-overlap": true, + "package-file-ownership": true, + "package-file-ownership-overlap": true + }, + "search": { + "scope": "squashed" + } + }, + "name": "syft", + "version": "1.22.0" + }, + "distro": {}, + "schema": { + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-16.0.24.json", + "version": "16.0.24" + }, + "source": { + "id": "4c871ffeacfa73d9b7f0e2ca4317d561c70e538bf0b9ae2c34646ebaeca229ae", + "metadata": { + "path": "/src/ash_output/work" + }, + "name": "/src/ash_output/work", + "type": "directory", + "version": "" + } +} diff --git a/tests/test_data/scanners/syft/syft.src.json b/tests/test_data/scanners/syft/syft.src.json new file mode 100644 index 0000000..5e216a5 --- /dev/null +++ b/tests/test_data/scanners/syft/syft.src.json @@ -0,0 +1,2645 @@ +{ + "artifactRelationships": [ + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "12bd7b7ee148e358", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "13ff99db317f8573", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "14176b9eca79e87a", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "16045bdb9d920af1", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "17b345dac35947e1", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "1d184295bb8d623a", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "25565ac499ca8e06", + "type": "evident-by" + }, + { + "child": "00b8355b886f263a", + "metadata": { + "kind": "primary" + }, + "parent": "263fb40f075d14c9", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "2699d9640be232a9", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "27b029bc8282952f", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "2ae15984bc28a9b9", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "37407b2f7118e9db", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "38ad5d25099585c8", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "39a68e2873d37321", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "4330b9f610ce4739", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "5622b9866735dab6", + "type": "evident-by" + }, + { + "child": "12bd7b7ee148e358", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "13ff99db317f8573", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "14176b9eca79e87a", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "16045bdb9d920af1", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "17b345dac35947e1", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "1d184295bb8d623a", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "25565ac499ca8e06", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "263fb40f075d14c9", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "2699d9640be232a9", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "27b029bc8282952f", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "2ae15984bc28a9b9", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "37407b2f7118e9db", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "38ad5d25099585c8", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "39a68e2873d37321", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "4330b9f610ce4739", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "5622b9866735dab6", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "5ba8bf58fee2c00d", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "5e92e4e07ea3d2dd", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "66eebf9e45e92f1f", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "716c5832a32cff84", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "77688a6306fdb097", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "979a7bc52695e74d", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "a26fdaf742880a0e", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "a2c2e93d97f5411e", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "b49184edad3abf63", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "bfc8013588f52fea", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "c86c31bf310165e3", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "d107460091076bc4", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "da67fdfebec1e080", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "e0545e27f25feef7", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "f2f039900cdff89e", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "f67bd9b281d23652", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "ffa443f0ded4ba13", + "parent": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "type": "contains" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "5ba8bf58fee2c00d", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "5e92e4e07ea3d2dd", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "66eebf9e45e92f1f", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "716c5832a32cff84", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "77688a6306fdb097", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "979a7bc52695e74d", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "a26fdaf742880a0e", + "type": "evident-by" + }, + { + "child": "91962c9b6089d393", + "metadata": { + "kind": "primary" + }, + "parent": "a2c2e93d97f5411e", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "b49184edad3abf63", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "bfc8013588f52fea", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "c86c31bf310165e3", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "d107460091076bc4", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "da67fdfebec1e080", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "e0545e27f25feef7", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "f2f039900cdff89e", + "type": "evident-by" + }, + { + "child": "eb2c69f97e43333e", + "metadata": { + "kind": "primary" + }, + "parent": "f67bd9b281d23652", + "type": "evident-by" + }, + { + "child": "9393d02a22a0ba42", + "metadata": { + "kind": "primary" + }, + "parent": "ffa443f0ded4ba13", + "type": "evident-by" + } + ], + "artifacts": [ + { + "cpes": [ + { + "cpe": "cpe:2.3:a:\\@convergence\\/examples:\\@convergence\\/examples:0.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "javascript-lock-cataloger", + "id": "263fb40f075d14c9", + "language": "javascript", + "licenses": [ + { + "locations": [ + { + "accessPath": "/js/package-lock.json", + "annotations": { + "evidence": "primary" + }, + "path": "/js/package-lock.json" + } + ], + "spdxExpression": "MIT", + "type": "declared", + "urls": [], + "value": "MIT" + } + ], + "locations": [ + { + "accessPath": "/js/package-lock.json", + "annotations": { + "evidence": "primary" + }, + "path": "/js/package-lock.json" + } + ], + "metadata": { + "integrity": "", + "resolved": "" + }, + "metadataType": "javascript-npm-package-lock-entry", + "name": "@convergence/examples", + "purl": "pkg:npm/%40convergence/examples@0.2.1", + "type": "npm", + "version": "0.2.1" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:addressable:addressable:2.7.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:addressable:2.7.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:addressable:2.7.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:addressable:2.7.0:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "1d184295bb8d623a", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "addressable", + "purl": "pkg:gem/addressable@2.7.0", + "type": "gem", + "version": "2.7.0" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:python-aws-cdk-lib:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws-cdk-lib:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws_cdk_lib:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws_cdk_lib:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws-cdk:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws-cdk:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws_cdk:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws_cdk:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws-cdk-lib:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws-cdk-lib:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws_cdk_lib:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws_cdk_lib:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws-cdk-lib:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws-cdk-lib:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws_cdk_lib:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws_cdk_lib:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws-cdk:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws-cdk:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws_cdk:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws_cdk:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws-cdk:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws-cdk:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws_cdk:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws_cdk:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws-cdk-lib:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws-cdk-lib:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws_cdk_lib:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws_cdk_lib:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws:python-aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws:python_aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-aws:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_aws:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws-cdk:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws-cdk:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws_cdk:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws_cdk:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws:aws-cdk-lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:aws:aws_cdk_lib:2.15.0:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "python-package-cataloger", + "id": "ffa443f0ded4ba13", + "language": "python", + "licenses": [], + "locations": [ + { + "accessPath": "/cdk/requirements.txt", + "annotations": { + "evidence": "primary" + }, + "path": "/cdk/requirements.txt" + } + ], + "metadata": { + "name": "aws-cdk-lib", + "versionConstraint": "==2.15.0" + }, + "metadataType": "python-pip-requirements-entry", + "name": "aws-cdk-lib", + "purl": "pkg:pypi/aws-cdk-lib@2.15.0", + "type": "python", + "version": "2.15.0" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:colorator:colorator:1.1.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:colorator:1.1.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:colorator:1.1.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:colorator:1.1.0:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "c86c31bf310165e3", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "colorator", + "purl": "pkg:gem/colorator@1.1.0", + "type": "gem", + "version": "1.1.0" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:concurrent-ruby:concurrent-ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:concurrent-ruby:concurrent_ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:concurrent_ruby:concurrent-ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:concurrent_ruby:concurrent_ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:concurrent:concurrent-ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:concurrent:concurrent_ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:concurrent-ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:concurrent_ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:concurrent-ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:concurrent_ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:concurrent-ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:concurrent_ruby:1.1.8:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "37407b2f7118e9db", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "concurrent-ruby", + "purl": "pkg:gem/concurrent-ruby@1.1.8", + "type": "gem", + "version": "1.1.8" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:em-websocket:em-websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:em-websocket:em_websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:em_websocket:em-websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:em_websocket:em_websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:em-websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:em_websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:em-websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:em_websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:em-websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:em_websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:em:em-websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:em:em_websocket:0.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "17b345dac35947e1", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "em-websocket", + "purl": "pkg:gem/em-websocket@0.5.2", + "type": "gem", + "version": "0.5.2" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:eventmachine:eventmachine:1.2.7:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:eventmachine:1.2.7:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:eventmachine:1.2.7:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:eventmachine:1.2.7:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "38ad5d25099585c8", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "eventmachine", + "purl": "pkg:gem/eventmachine@1.2.7", + "type": "gem", + "version": "1.2.7" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:ffi:1.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:ffi:1.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:ffi:1.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ffi:ffi:1.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "da67fdfebec1e080", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "ffi", + "purl": "pkg:gem/ffi@1.15.1", + "type": "gem", + "version": "1.15.1" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:forwardable-extended:forwardable-extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:forwardable-extended:forwardable_extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:forwardable_extended:forwardable-extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:forwardable_extended:forwardable_extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:forwardable:forwardable-extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:forwardable:forwardable_extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:forwardable-extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:forwardable_extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:forwardable-extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:forwardable_extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:forwardable-extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:forwardable_extended:2.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "d107460091076bc4", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "forwardable-extended", + "purl": "pkg:gem/forwardable-extended@2.6.0", + "type": "gem", + "version": "2.6.0" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:http-parser.rb:http-parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:http-parser.rb:http_parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:http_parser.rb:http-parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:http_parser.rb:http_parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:http-parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:http_parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:http-parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:http_parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:http:http-parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:http:http_parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:http-parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:http_parser.rb:0.6.0:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "2ae15984bc28a9b9", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "http_parser.rb", + "purl": "pkg:gem/http_parser.rb@0.6.0", + "type": "gem", + "version": "0.6.0" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:i18n:0.9.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:i18n:0.9.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:i18n:i18n:0.9.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:i18n:0.9.5:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "e0545e27f25feef7", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "i18n", + "purl": "pkg:gem/i18n@0.9.5", + "type": "gem", + "version": "0.9.5" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll:3.9.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll:3.9.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll:3.9.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll:3.9.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "b49184edad3abf63", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "jekyll", + "purl": "pkg:gem/jekyll@3.9.1", + "type": "gem", + "version": "3.9.1" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:jekyll-feed:jekyll-feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll-feed:jekyll_feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_feed:jekyll-feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_feed:jekyll_feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll-feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll_feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll-feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll_feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll-feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll_feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll-feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll_feed:0.15.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "5ba8bf58fee2c00d", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "jekyll-feed", + "purl": "pkg:gem/jekyll-feed@0.15.1", + "type": "gem", + "version": "0.15.1" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:jekyll-sass-converter:jekyll-sass-converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll-sass-converter:jekyll_sass_converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_sass_converter:jekyll-sass-converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_sass_converter:jekyll_sass_converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll-sass:jekyll-sass-converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll-sass:jekyll_sass_converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_sass:jekyll-sass-converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_sass:jekyll_sass_converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll-sass-converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll_sass_converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll-sass-converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll_sass_converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll-sass-converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll_sass_converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll-sass-converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll_sass_converter:1.5.2:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "5622b9866735dab6", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "jekyll-sass-converter", + "purl": "pkg:gem/jekyll-sass-converter@1.5.2", + "type": "gem", + "version": "1.5.2" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:jekyll-seo-tag:jekyll-seo-tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll-seo-tag:jekyll_seo_tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_seo_tag:jekyll-seo-tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_seo_tag:jekyll_seo_tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll-seo:jekyll-seo-tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll-seo:jekyll_seo_tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_seo:jekyll-seo-tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_seo:jekyll_seo_tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll-seo-tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll_seo_tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll-seo-tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll_seo_tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll-seo-tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll_seo_tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll-seo-tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll_seo_tag:2.7.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "14176b9eca79e87a", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "jekyll-seo-tag", + "purl": "pkg:gem/jekyll-seo-tag@2.7.1", + "type": "gem", + "version": "2.7.1" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:jekyll-sitemap:jekyll-sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll-sitemap:jekyll_sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_sitemap:jekyll-sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_sitemap:jekyll_sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll-sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll_sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll-sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll_sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll-sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll_sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll-sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll_sitemap:1.4.0:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "16045bdb9d920af1", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "jekyll-sitemap", + "purl": "pkg:gem/jekyll-sitemap@1.4.0", + "type": "gem", + "version": "1.4.0" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:jekyll-watch:jekyll-watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll-watch:jekyll_watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_watch:jekyll-watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll_watch:jekyll_watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll-watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:jekyll_watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll-watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:jekyll_watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll-watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:jekyll:jekyll_watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll-watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:jekyll_watch:2.2.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "12bd7b7ee148e358", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "jekyll-watch", + "purl": "pkg:gem/jekyll-watch@2.2.1", + "type": "gem", + "version": "2.2.1" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:kramdown_project:kramdown:2.3.1:*:*:*:*:ruby:*:*", + "source": "nvd-cpe-dictionary" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "27b029bc8282952f", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "kramdown", + "purl": "pkg:gem/kramdown@2.3.1", + "type": "gem", + "version": "2.3.1" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:liquid:4.0.3:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:liquid:4.0.3:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:liquid:liquid:4.0.3:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:liquid:4.0.3:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "4330b9f610ce4739", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "liquid", + "purl": "pkg:gem/liquid@4.0.3", + "type": "gem", + "version": "4.0.3" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:listen:3.5.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:listen:3.5.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:listen:listen:3.5.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:listen:3.5.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "39a68e2873d37321", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "listen", + "purl": "pkg:gem/listen@3.5.1", + "type": "gem", + "version": "3.5.1" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:mercenary:mercenary:0.3.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:mercenary:0.3.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:mercenary:0.3.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:mercenary:0.3.6:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "bfc8013588f52fea", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "mercenary", + "purl": "pkg:gem/mercenary@0.3.6", + "type": "gem", + "version": "0.3.6" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:minima:2.5.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:minima:2.5.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:minima:minima:2.5.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:minima:2.5.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "13ff99db317f8573", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "minima", + "purl": "pkg:gem/minima@2.5.1", + "type": "gem", + "version": "2.5.1" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:pathutil:0.16.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:pathutil:0.16.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:pathutil:pathutil:0.16.2:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:pathutil:0.16.2:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "77688a6306fdb097", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "pathutil", + "purl": "pkg:gem/pathutil@0.16.2", + "type": "gem", + "version": "0.16.2" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:public-suffix:public-suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:public-suffix:public_suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:public_suffix:public-suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:public_suffix:public_suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:public-suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:public_suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:public-suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:public_suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:public:public-suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:public:public_suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:public-suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:public_suffix:4.0.6:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "979a7bc52695e74d", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "public_suffix", + "purl": "pkg:gem/public_suffix@4.0.6", + "type": "gem", + "version": "4.0.6" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:python-pytest:python-pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-pytest:python_pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_pytest:python-pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_pytest:python_pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:pytest:python-pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:pytest:python_pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python-pytest:pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python:python-pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python:python_pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python_pytest:pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:pytest:pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:python:pytest:6.2.5:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "python-package-cataloger", + "id": "a2c2e93d97f5411e", + "language": "python", + "licenses": [], + "locations": [ + { + "accessPath": "/cdk/requirements-dev.txt", + "annotations": { + "evidence": "primary" + }, + "path": "/cdk/requirements-dev.txt" + } + ], + "metadata": { + "name": "pytest", + "versionConstraint": "==6.2.5" + }, + "metadataType": "python-pip-requirements-entry", + "name": "pytest", + "purl": "pkg:pypi/pytest@6.2.5", + "type": "python", + "version": "6.2.5" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:rb-fsevent:rb-fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rb-fsevent:rb_fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rb_fsevent:rb-fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rb_fsevent:rb_fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:rb-fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:rb_fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:rb-fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:rb_fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:rb-fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:rb_fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rb:rb-fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rb:rb_fsevent:0.11.0:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "66eebf9e45e92f1f", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "rb-fsevent", + "purl": "pkg:gem/rb-fsevent@0.11.0", + "type": "gem", + "version": "0.11.0" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:rb-inotify:rb-inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rb-inotify:rb_inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rb_inotify:rb-inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rb_inotify:rb_inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:rb-inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:rb_inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:rb-inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:rb_inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:rb-inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:rb_inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rb:rb-inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rb:rb_inotify:0.10.1:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "2699d9640be232a9", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "rb-inotify", + "purl": "pkg:gem/rb-inotify@0.10.1", + "type": "gem", + "version": "0.10.1" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:rexml:3.2.5:*:*:*:*:ruby:*:*", + "source": "nvd-cpe-dictionary" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "a26fdaf742880a0e", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "rexml", + "purl": "pkg:gem/rexml@3.2.5", + "type": "gem", + "version": "3.2.5" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:rouge:3.26.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:rouge:3.26.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:rouge:rouge:3.26.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:rouge:3.26.0:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "f67bd9b281d23652", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "rouge", + "purl": "pkg:gem/rouge@3.26.0", + "type": "gem", + "version": "3.26.0" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:safe-yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:safe_yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:safe-yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:safe_yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:safe-yaml:safe-yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:safe-yaml:safe_yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:safe_yaml:safe-yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:safe_yaml:safe_yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:safe-yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:safe_yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:safe:safe-yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:safe:safe_yaml:1.0.5:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "5e92e4e07ea3d2dd", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "safe_yaml", + "purl": "pkg:gem/safe_yaml@1.0.5", + "type": "gem", + "version": "1.0.5" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:sass:3.7.4:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:sass:3.7.4:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:sass:3.7.4:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sass:sass:3.7.4:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "716c5832a32cff84", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "sass", + "purl": "pkg:gem/sass@3.7.4", + "type": "gem", + "version": "3.7.4" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:sass-listen:sass-listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sass-listen:sass_listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sass_listen:sass-listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sass_listen:sass_listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:sass-listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby-lang:sass_listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:sass-listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby_lang:sass_listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:sass-listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:ruby:sass_listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sass:sass-listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + }, + { + "cpe": "cpe:2.3:a:sass:sass_listen:4.0.0:*:*:*:*:*:*:*", + "source": "syft-generated" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "25565ac499ca8e06", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "sass-listen", + "purl": "pkg:gem/sass-listen@4.0.0", + "type": "gem", + "version": "4.0.0" + }, + { + "cpes": [ + { + "cpe": "cpe:2.3:a:ruby-lang:webrick:1.7.0:*:*:*:*:ruby:*:*", + "source": "nvd-cpe-dictionary" + } + ], + "foundBy": "ruby-gemfile-cataloger", + "id": "f2f039900cdff89e", + "language": "ruby", + "licenses": [], + "locations": [ + { + "accessPath": "/js/Gemfile.lock", + "annotations": { + "evidence": "primary" + }, + "path": "/js/Gemfile.lock" + } + ], + "name": "webrick", + "purl": "pkg:gem/webrick@1.7.0", + "type": "gem", + "version": "1.7.0" + } + ], + "descriptor": { + "configuration": { + "catalogers": { + "requested": { + "default": [ + "directory", + "file" + ] + }, + "used": [ + "alpm-db-cataloger", + "apk-db-cataloger", + "binary-classifier-cataloger", + "cargo-auditable-binary-cataloger", + "cocoapods-cataloger", + "conan-cataloger", + "dart-pubspec-lock-cataloger", + "deb-archive-cataloger", + "dotnet-deps-binary-cataloger", + "dotnet-packages-lock-cataloger", + "dpkg-db-cataloger", + "elf-binary-package-cataloger", + "elixir-mix-lock-cataloger", + "erlang-otp-application-cataloger", + "erlang-rebar-lock-cataloger", + "file-content-cataloger", + "file-digest-cataloger", + "file-executable-cataloger", + "file-metadata-cataloger", + "github-action-workflow-usage-cataloger", + "github-actions-usage-cataloger", + "go-module-binary-cataloger", + "go-module-file-cataloger", + "graalvm-native-image-cataloger", + "haskell-cataloger", + "java-archive-cataloger", + "java-gradle-lockfile-cataloger", + "java-jvm-cataloger", + "java-pom-cataloger", + "javascript-lock-cataloger", + "linux-kernel-cataloger", + "lua-rock-cataloger", + "nix-store-cataloger", + "opam-cataloger", + "php-composer-lock-cataloger", + "php-pecl-serialized-cataloger", + "portage-cataloger", + "python-installed-package-cataloger", + "python-package-cataloger", + "rpm-archive-cataloger", + "rpm-db-cataloger", + "ruby-gemfile-cataloger", + "ruby-gemspec-cataloger", + "rust-cargo-lock-cataloger", + "swift-package-manager-cataloger", + "swipl-pack-cataloger", + "terraform-lock-cataloger", + "wordpress-plugins-cataloger" + ] + }, + "data-generation": { + "generate-cpes": true + }, + "files": { + "content": { + "globs": null, + "skip-files-above-size": 0 + }, + "hashers": [ + "sha-1", + "sha-256" + ], + "selection": "owned-by-package" + }, + "licenses": { + "coverage": 75, + "include-unknown-license-content": false + }, + "packages": { + "binary": [ + "python-binary", + "python-binary-lib", + "pypy-binary-lib", + "go-binary", + "julia-binary", + "helm", + "redis-binary", + "java-binary-openjdk", + "java-binary-ibm", + "java-binary-oracle", + "java-binary-graalvm", + "java-binary-jdk", + "nodejs-binary", + "go-binary-hint", + "busybox-binary", + "util-linux-binary", + "haproxy-binary", + "perl-binary", + "php-cli-binary", + "php-fpm-binary", + "php-apache-binary", + "php-composer-binary", + "httpd-binary", + "memcached-binary", + "traefik-binary", + "arangodb-binary", + "postgresql-binary", + "mysql-binary", + "mysql-binary", + "mysql-binary", + "xtrabackup-binary", + "mariadb-binary", + "rust-standard-library-linux", + "rust-standard-library-macos", + "ruby-binary", + "erlang-binary", + "erlang-alpine-binary", + "erlang-library", + "swipl-binary", + "dart-binary", + "haskell-ghc-binary", + "haskell-cabal-binary", + "haskell-stack-binary", + "consul-binary", + "nginx-binary", + "bash-binary", + "openssl-binary", + "gcc-binary", + "fluent-bit-binary", + "wordpress-cli-binary", + "curl-binary", + "lighttpd-binary", + "proftpd-binary", + "zstd-binary", + "xz-binary", + "gzip-binary", + "sqlcipher-binary", + "jq-binary" + ], + "dotnet": { + "dep-packages-must-claim-dll": true, + "dep-packages-must-have-dll": false, + "relax-dll-claims-when-bundling-detected": true + }, + "golang": { + "local-mod-cache-dir": "/home/ash-user/go/pkg/mod", + "local-vendor-dir": "", + "main-module-version": { + "from-build-settings": true, + "from-contents": true, + "from-ld-flags": true + }, + "proxies": [ + "https://proxy.golang.org", + "direct" + ], + "search-local-mod-cache-licenses": false, + "search-local-vendor-licenses": false, + "search-remote-licenses": false + }, + "java-archive": { + "include-indexed-archives": true, + "include-unindexed-archives": false, + "maven-base-url": "https://repo1.maven.org/maven2", + "maven-localrepository-dir": "/home/ash-user/.m2/repository", + "max-parent-recursive-depth": 0, + "resolve-transitive-dependencies": false, + "use-maven-localrepository": false, + "use-network": false + }, + "javascript": { + "include-dev-dependencies": false, + "npm-base-url": "https://registry.npmjs.org", + "search-remote-licenses": false + }, + "linux-kernel": { + "catalog-modules": true + }, + "python": { + "guess-unpinned-requirements": false + } + }, + "relationships": { + "exclude-binary-packages-with-file-ownership-overlap": true, + "package-file-ownership": true, + "package-file-ownership-overlap": true + }, + "search": { + "scope": "squashed" + } + }, + "name": "syft", + "version": "1.22.0" + }, + "distro": {}, + "files": [ + { + "digests": [ + { + "algorithm": "sha1", + "value": "814ebbc1cdccf4cc6993f4938ab8b6966e5d8468" + }, + { + "algorithm": "sha256", + "value": "1b47e0bc152af7cea75a0b8a830dc0366a96c3bfc83b4841279c1725deda148c" + } + ], + "id": "91962c9b6089d393", + "location": { + "path": "/cdk/requirements-dev.txt" + }, + "metadata": { + "groupID": 20, + "mimeType": "text/plain", + "mode": 644, + "size": 14, + "type": "RegularFile", + "userID": 504 + } + }, + { + "digests": [ + { + "algorithm": "sha1", + "value": "22c42423f67755c49c53730828eb6880bc195d97" + }, + { + "algorithm": "sha256", + "value": "c2636af113b27ccaedd87fd00a03555253c177d76401875a621aa743265e12ce" + } + ], + "id": "9393d02a22a0ba42", + "location": { + "path": "/cdk/requirements.txt" + }, + "metadata": { + "groupID": 20, + "mimeType": "text/plain", + "mode": 644, + "size": 61, + "type": "RegularFile", + "userID": 504 + }, + "unknowns": [ + "python-package-cataloger: unable to determine package version in requirements.txt line: \"constructs>=10.0.0,<11.0.0\"" + ] + }, + { + "digests": [ + { + "algorithm": "sha1", + "value": "208895c5d54de40087ffd8ffd526adef3878d0d7" + }, + { + "algorithm": "sha256", + "value": "23bb1596e3f0e9c49b82e60f26b7b8711181ba53b39870caedb86745fd4f6bcb" + } + ], + "id": "eb2c69f97e43333e", + "location": { + "path": "/js/Gemfile.lock" + }, + "metadata": { + "groupID": 20, + "mimeType": "text/plain", + "mode": 644, + "size": 1803, + "type": "RegularFile", + "userID": 504 + } + }, + { + "digests": [ + { + "algorithm": "sha1", + "value": "ed14c7050ae5c273a406a85477b4b453979dfd09" + }, + { + "algorithm": "sha256", + "value": "62408e7f0febbed2ec5b27cf5c208c677555ebb1bfbb8150f41d68b9eab0a4c8" + } + ], + "id": "00b8355b886f263a", + "location": { + "path": "/js/package-lock.json" + }, + "metadata": { + "groupID": 20, + "mimeType": "application/json", + "mode": 644, + "size": 85479, + "type": "RegularFile", + "userID": 504 + } + }, + { + "id": "f4f0a38439b9c67e", + "location": { + "path": "/js/src/assets/js/package-lock.json" + }, + "unknowns": [ + "javascript-lock-cataloger: unable to determine packages" + ] + } + ], + "schema": { + "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-16.0.24.json", + "version": "16.0.24" + }, + "source": { + "id": "56c9e1c4b5360a5b089004396ed37fe3a20f7c4f5954072098039789ba3ab68a", + "metadata": { + "path": "/src" + }, + "name": "/src", + "type": "directory", + "version": "" + } +} diff --git a/tests/test_execution_engine.py b/tests/test_execution_engine.py new file mode 100644 index 0000000..30b5623 --- /dev/null +++ b/tests/test_execution_engine.py @@ -0,0 +1,107 @@ +"""Unit tests for execution engine module.""" + +import logging +from typing import Dict, Optional, Type + +from automated_security_helper.execution_engine import ( + ExecutionStrategy, + ScanExecutionEngine, +) +from automated_security_helper.models.scanner_plugin import ScannerPlugin + +from automated_security_helper.config.config import ( + ASHConfig, + SASTScannerConfig, + SASTScannerListConfig, + SBOMScannerConfig, + SBOMScannerListConfig, +) +from automated_security_helper.models.data_interchange import ExportFormat +from automated_security_helper.scanners.bandit_scanner import BanditScanner + +from tests.conftest import TEST_OUTPUT_DIR, TEST_SOURCE_DIR + + +class MockEngine(ScanExecutionEngine): + """Test execution engine with pre-configured scanner factory.""" + + def __init__( + self, + strategy: ExecutionStrategy = ExecutionStrategy.PARALLEL, + source_dir=None, + output_dir=None, + ): + # Set up paths and logging first + source_dir = source_dir or TEST_SOURCE_DIR + output_dir = output_dir or TEST_OUTPUT_DIR + self.logger = logging.getLogger("test_execution_engine") + + # Initialize engine state and scanners + self._scanners = {} # Start with empty scanner registry + self._enabled_scanners = None # Default to all enabled + self._initialized = False # Will be set after initialization + + # Configure and create scanner factory + class TestScannerFactory: + """Scanner factory for testing.""" + + def __init__(self, logger): + self._logger = logger + self._scanners = { + "bandit": BanditScanner, + # 'cfnnag': CFNNagScanner + } + self._logger.debug( + f"Initialized scanner factory with: {list(self._scanners.keys())}" + ) + + def available_scanners(self) -> Dict[str, Type[ScannerPlugin]]: + return self._scanners.copy() + + def get_scanner_class(self, name: str) -> Optional[Type[ScannerPlugin]]: + return self._scanners.get(name.lower().strip()) + + def register_scanner( + self, scanner_name: str, scanner_class: Type[ScannerPlugin] + ) -> None: + name = scanner_name.lower().strip() + self._scanners[name] = scanner_class + self._logger.debug(f"Registered scanner: {name}") + + def create_scanner(self, scanner_name: str, **kwargs) -> ScannerPlugin: + name = scanner_name.lower().strip() + scanner_class = self.get_scanner_class(name) + if not scanner_class: + raise ValueError(f"Scanner {name} not found") + return scanner_class(**kwargs) + + # Create scanner factory and mark engine as uninitialized + self._scanner_factory = TestScannerFactory(self.logger) + self._initialized = False + + # Initialize parent with scanner factory ready + super().__init__( + source_dir=source_dir, + output_dir=output_dir, + strategy=strategy, + logger=self.logger, + ) + + # Default config for testing + self._config = ASHConfig( + project_name="test", + fail_on_findings=False, + sast=SASTScannerConfig( + output_formats=[ExportFormat.JSON], + enabled=True, + scanners=SASTScannerListConfig(), + ), + sbom=SBOMScannerConfig( + output_formats=[ExportFormat.JSON], + enabled=True, + scanners=SBOMScannerListConfig(), + ), + ) + + # Register and enable scanners + self.ensure_initialized(self._config) diff --git a/tests/test_execution_engine_results.py b/tests/test_execution_engine_results.py new file mode 100644 index 0000000..b92b118 --- /dev/null +++ b/tests/test_execution_engine_results.py @@ -0,0 +1,38 @@ +"""Tests for ExecutionEngine result handling.""" + +from pathlib import Path +from typing import Dict, Any + +import pytest + +from automated_security_helper.execution_engine import ScanExecutionEngine +from automated_security_helper.models.scan_results import ScanResultsContainer + + +@pytest.fixture +def mock_scanner_results() -> Dict[str, Any]: + """Fixture providing mock scanner results.""" + return { + "findings": [{"id": 1, "severity": "HIGH"}, {"id": 2, "severity": "LOW"}], + "metadata": {"version": "1.0.0", "scanner": "test_scanner"}, + } + + +def test_execution_engine_wraps_results( + tmp_path: Path, mock_scanner_results: Dict[str, Any] +): + """Test that ExecutionEngine properly wraps scanner results.""" + engine = ScanExecutionEngine() + + # Mock scanner plugin that returns our test results + class MockScanner: + def scan(self): + return mock_scanner_results + + result = engine._execute_scanner((MockScanner(), None)) + + assert "container" in result + assert isinstance(result["container"], ScanResultsContainer) + assert result["container"].findings == mock_scanner_results["findings"] + assert result["container"].metadata == mock_scanner_results["metadata"] + assert result["container"].raw_results == mock_scanner_results diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py new file mode 100644 index 0000000..cc38749 --- /dev/null +++ b/tests/test_orchestrator.py @@ -0,0 +1,57 @@ +"""Tests for orchestrator module.""" + +import json +import os +from pathlib import Path +import tempfile +import pytest +import yaml + +from automated_security_helper.config.default_config import DEFAULT_ASH_CONFIG +from automated_security_helper.orchestrator import ASHScanOrchestrator + + +@pytest.fixture +def default_config(): + """Create a temporary configuration file.""" + with tempfile.NamedTemporaryFile(suffix=".yaml", delete=False) as f: + # Create minimal config that gets transformed to ASHConfig + DEFAULT_ASH_CONFIG + yaml.safe_dump(json.loads(DEFAULT_ASH_CONFIG.model_dump_json()), f) + f.flush() + yield f.name + os.unlink(f.name) + + +def test_execute_scan_with_config(test_source_dir: Path, test_output_dir: Path): + """Test executing scan with configuration file.""" + # Create minimal config that gets transformed to ASHConfig + try: + with open(test_source_dir.joinpath("ash.yaml"), "w") as f: + config_to_dump: dict = json.loads(DEFAULT_ASH_CONFIG.model_dump_json()) + with open(f.name, "w") as f: + yaml.safe_dump(config_to_dump, f) + + orchestrator = ASHScanOrchestrator( + source_dir=test_source_dir.as_posix(), + output_dir=test_output_dir.as_posix(), + config_path=f.name, + ) + result = orchestrator.execute_scan() + assert isinstance(result, dict) + assert "scanners" in result + finally: + pass + os.unlink(f.name) + + +def test_execute_scan_no_config(test_source_dir: Path, test_output_dir: Path): + """Test executing scan whout configuration file.""" + orchestrator = ASHScanOrchestrator( + source_dir=str(test_source_dir), + output_dir=str(test_output_dir), + config_path=None, + ) + result = orchestrator.execute_scan() + assert isinstance(result, dict) + assert "scanners" in result diff --git a/tests/test_output_formatter.py b/tests/test_output_formatter.py new file mode 100644 index 0000000..0e93406 --- /dev/null +++ b/tests/test_output_formatter.py @@ -0,0 +1,107 @@ +"""Unit tests for output formatter module.""" + +import json +import pytest +from automated_security_helper.models.core import Location +from automated_security_helper.models.iac_scan import IaCVulnerability +from automated_security_helper.output_formatter import ( + OutputFormatter, + JSONReporter, + HTMLReporter, + CSVReporter, +) +from automated_security_helper.models.asharp_model import ASHARPModel + + +@pytest.fixture +def sample_ash_model(): + model = ASHARPModel( + findings=[ + IaCVulnerability( + id="finding-1", + title="AwsSolutionsChecks/finding-1 - SQL Injection", + compliance_frameworks=["AwsSolutionsChecks"], + description="SQL Injection vulnerability", + severity="HIGH", + location=Location( + file_path="path/to/file", snippet="sql.query(user_input + ';GO')" + ), + resource_name="LambdaFunction1", + rule_id="AwsSolutionsChecks/finding-1", + status="open", + ) + ], + metadata={"scanner": "test_scanner", "timestamp": "2023-01-01T00:00:00Z"}, + ) + return model + + +def test_json_formatter(sample_ash_model): + formatter = JSONReporter() + output = formatter.format(sample_ash_model) + + # Verify output is valid JSON + parsed = json.loads(output) + assert "findings" in parsed + assert "metadata" in parsed + assert parsed["findings"][0] == sample_ash_model.findings[0].model_dump() + assert parsed["metadata"] == sample_ash_model.metadata.model_dump() + + +def test_html_formatter(sample_ash_model): + formatter = HTMLReporter() + output = formatter.format(sample_ash_model) + + # Verify basic HTML structure + assert "" in output + assert "" in output + assert "ASH Results" in output + assert "

    Security Scan Results

    " in output + + # Verify content is included and properly escaped + assert "SQL Injection vulnerability" in output + assert "<" not in output # Verify no double-escaping + + +def test_csv_formatter(sample_ash_model): + formatter = CSVReporter() + output = formatter.format(sample_ash_model) + + # Verify CSV structure + lines = output.strip().split("\n") + assert len(lines) >= 1 # At least header row + + # Verify header row + header = lines[0].split(",") + assert "Finding ID" in header + assert "Severity" in header + assert "Description" in header + assert "Location" in header + + +def test_output_formatter_format_selection(): + formatter = OutputFormatter() + model = ASHARPModel() + + # Test JSON format + json_output = formatter.format(model, "json") + assert isinstance(json_output, str) + assert json.loads(json_output) # Verify valid JSON + + # Test HTML format + html_output = formatter.format(model, "html") + assert isinstance(html_output, str) + assert "" in html_output + + # Test CSV format + csv_output = formatter.format(model, "csv") + assert isinstance(csv_output, str) + assert "Finding ID,Severity,Description,Location" in csv_output + + +def test_output_formatter_invalid_format(): + formatter = OutputFormatter() + model = ASHARPModel() + + with pytest.raises(ValueError): + formatter.format(model, "invalid_format") diff --git a/tests/test_result_processor.py b/tests/test_result_processor.py new file mode 100644 index 0000000..c0c4593 --- /dev/null +++ b/tests/test_result_processor.py @@ -0,0 +1,124 @@ +"""Unit tests for result processor module.""" + +import json +import pytest +from typing import Any, Dict, List, Union +from automated_security_helper.models.data_interchange import ReportMetadata +from automated_security_helper.result_processor import ( + ResultProcessor, + IResultParser, + ASHARPModel, +) + + +class MockParser(IResultParser): + def parse( + self, raw_results: str + ) -> Dict[str, Union[List[Dict[str, Any]], Dict[str, str]]]: + """Parse raw results and return findings and metadata.""" + default_data = { + "findings": [ + { + "id": "TEST-1", + "severity": "HIGH", + "description": "Test finding", + "location": "test.py:10", + } + ], + "metadata": {"scanner": "mock-scanner", "version": "1.0.0"}, + } + try: + data = json.loads(raw_results) + if "findings" in data: + default_data["findings"] = data["findings"] + if "metadata" in data: + default_data["metadata"].update(data["metadata"]) + except json.JSONDecodeError: + pass # Use default data on invalid JSON + return default_data + + +def test_ash_model(): + """Test ASHARPModel initialization and property assignment.""" + model = ASHARPModel() + assert isinstance(model.findings, list) + assert isinstance(model.metadata, ReportMetadata) + assert len(model.findings) == 0 + assert len(model.metadata.__dict__.keys()) == 6 + + # Test property assignment + test_findings = [{"id": "TEST"}] + test_metadata = {"scanner": "test"} + model.findings = test_findings + model.metadata = test_metadata + assert len(model.findings) == 1 + assert model.findings[0]["id"] == "TEST" + assert model.metadata["scanner"] == "test" + + +def test_result_processor_registration(): + """Test parser registration.""" + processor = ResultProcessor() + processor.register_parser("mock-scanner", MockParser) + parser = processor.get_parser("mock-scanner") + assert isinstance(parser, MockParser) + + +def test_get_parser(): + """Test get_parser with invalid scanner type.""" + processor = ResultProcessor() + with pytest.raises(ValueError): + processor.get_parser("invalid-scanner") + + +def test_process_results(): + processor = ResultProcessor() + processor.register_parser("mock-scanner", MockParser) + + raw_results = """{ + "findings": [ + { + "id": "TEST-1", + "severity": "HIGH", + "description": "Test finding", + "location": "test.py:10" + } + ], + "metadata": { + "scanner": "mock-scanner", + "version": "1.0.0" + } + }""" + model = processor.process_results("mock-scanner", raw_results) + + # Verify processed results + assert isinstance(model, ASHARPModel) + assert len(model.findings) == 1 + assert model.findings[0]["id"] == "TEST-1" + assert model.findings[0]["severity"] == "HIGH" + + +def test_build_ash_model(): + processor = ResultProcessor() + parsed_results = { + "findings": [ + { + "id": "TEST-1", + "severity": "HIGH", + "description": "Test finding", + "location": "test.py:10", + } + ], + "metadata": {"scanner": "mock-scanner", "version": "1.0.0"}, + } + + model = processor._build_ash_model(parsed_results) + assert isinstance(model, ASHARPModel) + assert len(model.findings) == 1 + assert model.metadata["scanner"] == "mock-scanner" + + +def test_process_results_invalid_scanner(): + processor = ResultProcessor() + with pytest.raises(ValueError): + processor.process_results("invalid-scanner", "{}") diff --git a/utils/cdk-addon-py.py b/utils/cdk-addon-py.py index c5500ef..afef8a6 100644 --- a/utils/cdk-addon-py.py +++ b/utils/cdk-addon-py.py @@ -1,35 +1,39 @@ - def add_cdk_nag_imports(filename): - cdk_nag_imports = ["\nfrom cdk_nag import AwsSolutionsChecks","\nfrom aws_cdk import App, Aspects"] + cdk_nag_imports = [ + "\nfrom cdk_nag import AwsSolutionsChecks", + "\nfrom aws_cdk import App, Aspects", + ] for cdk_import in cdk_nag_imports: - with open(filename, 'r') as cdk_app_file: + with open(filename, "r") as cdk_app_file: cdk_app_file_data = cdk_app_file.read() if cdk_import in cdk_app_file_data: -# print(cdk_import + ' already exists') + # print(cdk_import + ' already exists') pass else: - missing_import = '#!/usr/bin/env python3' + cdk_import - cdk_app_file_data = cdk_app_file_data.replace('#!/usr/bin/env python3', missing_import ) - with open(filename, 'w') as cdk_app_file: -# print('Adding '+ missing_import) + missing_import = "#!/usr/bin/env python3" + cdk_import + cdk_app_file_data = cdk_app_file_data.replace( + "#!/usr/bin/env python3", missing_import + ) + with open(filename, "w") as cdk_app_file: + # print('Adding '+ missing_import) cdk_app_file.write(cdk_app_file_data) + def add_cdk_nag_checks(filename): cdk_nag_check = "\nAspects.of(app).add(AwsSolutionsChecks())\n" - with open(filename, 'r') as cdk_app_file: + with open(filename, "r") as cdk_app_file: cdk_app_file_data = cdk_app_file.read() if cdk_nag_check in cdk_app_file_data: -# print(cdk_nag_check + ' already exists') + # print(cdk_nag_check + ' already exists') pass else: - missing_check = cdk_nag_check + 'app.synth()' - cdk_app_file_data = cdk_app_file_data.replace('app.synth()', missing_check ) - with open(filename, 'w') as cdk_app_file: -# print('Adding '+ missing_check) + missing_check = cdk_nag_check + "app.synth()" + cdk_app_file_data = cdk_app_file_data.replace("app.synth()", missing_check) + with open(filename, "w") as cdk_app_file: + # print('Adding '+ missing_check) cdk_app_file.write(cdk_app_file_data) - filename = "app.py" add_cdk_nag_imports(filename) add_cdk_nag_checks(filename) diff --git a/utils/cdk-docker-execute.sh b/utils/cdk-docker-execute.sh index f76ee07..3920d1f 100644 --- a/utils/cdk-docker-execute.sh +++ b/utils/cdk-docker-execute.sh @@ -44,7 +44,12 @@ cd ${_ASH_SOURCE_DIR} debug_echo "[cdk] pwd: '$(pwd)' :: _ASH_SOURCE_DIR: "${_ASH_SOURCE_DIR}" :: _ASH_RUN_DIR: ${_ASH_RUN_DIR}" # Set REPORT_PATH to the report location, then touch it to ensure it exists -REPORT_PATH="${_ASH_OUTPUT_DIR}/work/cdk_report_result.txt" +SCANNER_DIR="${_ASH_OUTPUT_DIR}/scanners" +RESULTS_DIR="${SCANNER_DIR}/results" + +mkdir -p "${RESULTS_DIR}" + +REPORT_PATH="${RESULTS_DIR}/cdk_report_result.txt" rm ${REPORT_PATH} 2> /dev/null touch ${REPORT_PATH} @@ -73,15 +78,20 @@ cd ${_ASH_OUTPUT_DIR} # # Create a directory to hold all the cdk_nag results from ASH # -DIRECTORY="ash_cf2cdk_output" +DIRECTORY="cdk" # Check if this directory already exist from previous ASH run -if [[ -n "${_ASH_OUTPUT_DIR}" && -d "${_ASH_OUTPUT_DIR}/$DIRECTORY" ]]; then - rm -rf "${_ASH_OUTPUT_DIR}/$DIRECTORY" +if [[ -n "${SCANNER_DIR}" && -d "${SCANNER_DIR}/$DIRECTORY" ]]; then + rm -rf "${SCANNER_DIR}/$DIRECTORY" fi -mkdir -p "${_ASH_OUTPUT_DIR}/$DIRECTORY" 2> /dev/null +mkdir -p "${SCANNER_DIR}/$DIRECTORY" 2> /dev/null RC=0 +if [ -d "${SCANNER_DIR}/cdk" ]; then + rm -rf "${SCANNER_DIR}/cdk" +fi +mkdir -p "${SCANNER_DIR}/cdk" + # # Uncomment the diagnostic output below to get details about # the environment and node versions @@ -125,54 +135,56 @@ if [ "${#cfn_files[@]}" -gt 0 ]; then echo "found ${#cfn_files[@]} files to scan. Starting scans ..." >> ${REPORT_PATH} for file in "${cfn_files[@]}"; do - cfn_filename=`basename $file` - echo ">>>>>> begin cdk-nag result for ${cfn_filename} >>>>>>" >> ${REPORT_PATH} - # - # Generate the CDK application inserting the CloudFormation template - # - # /usr/bin/python3 cfn_to_cdk/template_generator.py $file - # - # Use CDK to synthesize the CDK application, - # running CDK-NAG on the inserted CloudFormation template - # - debug_echo "Importing CloudFormation template file ${file} to apply CDK Nag rules against it" - npx cdk synth --context fileName="${file}" --quiet 2>> ${REPORT_PATH} - CRC=$? - - RC=$(bumprc $RC $CRC) - - # - # Check to see if there is output to copy, if so, create a folder and copy the files - # - fileName="*.template.json" - # echo "checking for ${fileName}" >> ${REPORT_PATH} - # find -type f -name ${fileName} >> ${REPORT_PATH} 2>&1 - # ls ${fileName} >> ${REPORT_PATH} 2>&1 - fileExists=$(find ${CDK_WORK_DIR}/cdk.out -type f -name ${fileName} | wc -l) - # echo "fileExists = ${fileExists}" >> ${REPORT_PATH} - reportsName="AwsSolutions-*-NagReport.csv" - # echo "checking for ${reportsName}" >> ${REPORT_PATH} - # find -type f -name ${reportsName} >> ${REPORT_PATH} 2>&1 - # ls ${reportsName} >> ${REPORT_PATH} 2>&1 - reportsExist=$(find ${CDK_WORK_DIR}/cdk.out -type f -name ${reportsName} | wc -l) - # echo "reportsExist = ${reportsExist}" >> ${REPORT_PATH} - if [ "${fileExists}" -gt 0 -o "${reportsExist}" -gt 0 ]; then - mkdir -p ${_ASH_OUTPUT_DIR}/${DIRECTORY}/${cfn_filename}_cdk_nag_results - - echo "Writing CDK-NAG reports for ${cfn_filename}" >> ${REPORT_PATH} + cleanfile=$(echo $file | sed 's/\//./g;s/^\.//g') + if [[ "${file1}" != "aggregated_results.txt.json" ]]; then + echo ">>>>>> begin cdk-nag result for ${cfn_filename} >>>>>>" >> ${REPORT_PATH} # - # Copy and then remove these files to avoid permission setting errors when running in a single container + # Generate the CDK application inserting the CloudFormation template # - cp ${CDK_WORK_DIR}/cdk.out/*.template.json ${_ASH_OUTPUT_DIR}/${DIRECTORY}/${cfn_filename}_cdk_nag_results/ >/dev/null 2>&1 - rm ${CDK_WORK_DIR}/cdk.out/*.template.json >/dev/null 2>&1 - cp ${CDK_WORK_DIR}/cdk.out/AwsSolutions-*-NagReport.csv ${_ASH_OUTPUT_DIR}/${DIRECTORY}/${cfn_filename}_cdk_nag_results/ >/dev/null 2>&1 - rm ${CDK_WORK_DIR}/cdk.out/AwsSolutions-*-NagReport.csv >/dev/null 2>&1 - else - echo "No CDK-NAG reports generated for ${cfn_filename}" >> ${REPORT_PATH} - fi + # /usr/bin/python3 cfn_to_cdk/template_generator.py $file + # + # Use CDK to synthesize the CDK application, + # running CDK-NAG on the inserted CloudFormation template + # + debug_echo "Importing CloudFormation template file ${file} to apply CDK Nag rules against it" + eval "npx cdk synth --context fileName=\"${file}\" --quiet" >> "${REPORT_PATH}" 2>&1 > "${SCANNER_DIR}/cdk/${cleanfile}.txt" + CRC=$? + + RC=$(bumprc $RC $CRC) - echo "<<<<<< end cdk-nag result for ${cfn_filename} <<<<<<" >> ${REPORT_PATH} + # + # Check to see if there is output to copy, if so, create a folder and copy the files + # + fileName="*.template.json" + # echo "checking for ${fileName}" >> ${REPORT_PATH} + # find -type f -name ${fileName} >> ${REPORT_PATH} 2>&1 + # ls ${fileName} >> ${REPORT_PATH} 2>&1 + fileExists=$(find ${CDK_WORK_DIR}/cdk.out -type f -name ${fileName} | wc -l) + # echo "fileExists = ${fileExists}" >> ${REPORT_PATH} + reportsName="AwsSolutions-*-NagReport.csv" + # echo "checking for ${reportsName}" >> ${REPORT_PATH} + # find -type f -name ${reportsName} >> ${REPORT_PATH} 2>&1 + # ls ${reportsName} >> ${REPORT_PATH} 2>&1 + reportsExist=$(find ${CDK_WORK_DIR}/cdk.out -type f -name ${reportsName} | wc -l) + # echo "reportsExist = ${reportsExist}" >> ${REPORT_PATH} + if [ "${fileExists}" -gt 0 -o "${reportsExist}" -gt 0 ]; then + mkdir -p ${SCANNER_DIR}/${DIRECTORY}/${cfn_filename}_cdk_nag_results + + echo "Writing CDK-NAG reports for ${cfn_filename}" >> ${REPORT_PATH} + # + # Copy and then remove these files to avoid permission setting errors when running in a single container + # + cp ${CDK_WORK_DIR}/cdk.out/*.template.json ${SCANNER_DIR}/${DIRECTORY}/${cfn_filename}_cdk_nag_results/ >/dev/null 2>&1 + rm ${CDK_WORK_DIR}/cdk.out/*.template.json >/dev/null 2>&1 + cp ${CDK_WORK_DIR}/cdk.out/AwsSolutions-*-NagReport.csv ${SCANNER_DIR}/${DIRECTORY}/${cfn_filename}_cdk_nag_results/ >/dev/null 2>&1 + rm ${CDK_WORK_DIR}/cdk.out/AwsSolutions-*-NagReport.csv >/dev/null 2>&1 + else + echo "No CDK-NAG reports generated for ${cfn_filename}" >> ${REPORT_PATH} + fi + + echo "<<<<<< end cdk-nag result for ${cfn_filename} <<<<<<" >> ${REPORT_PATH} + fi done else echo "found ${#cfn_files[@]} files to scan. Skipping scans." >> ${REPORT_PATH} diff --git a/utils/cdk-nag-scan/cdk.json b/utils/cdk-nag-scan/cdk.json index 51e77d8..a828bc0 100644 --- a/utils/cdk-nag-scan/cdk.json +++ b/utils/cdk-nag-scan/cdk.json @@ -1,9 +1,42 @@ { "app": "npx ts-node --prefer-ts-exts bin/cdk-nag-scan.ts", - "watch": { - "include": [ - "**" + "context": { + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" ], + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false + }, + "watch": { "exclude": [ "README.md", "cdk*.json", @@ -14,42 +47,9 @@ "yarn.lock", "node_modules", "test" - ] - }, - "context": { - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": [ - "aws", - "aws-cn" ], - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, - "@aws-cdk/aws-iam:minimizePolicies": true, - "@aws-cdk/core:validateSnapshotRemovalPolicy": true, - "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, - "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, - "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, - "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, - "@aws-cdk/core:enablePartitionLiterals": true, - "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, - "@aws-cdk/aws-iam:standardizedServicePrincipals": true, - "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, - "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, - "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, - "@aws-cdk/aws-route53-patters:useCertificate": true, - "@aws-cdk/customresources:installLatestAwsSdkDefault": false, - "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, - "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, - "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, - "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, - "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, - "@aws-cdk/aws-redshift:columnId": true, - "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, - "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, - "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, - "@aws-cdk/aws-kms:aliasNameRef": true, - "@aws-cdk/core:includePrefixInUniqueNameGeneration": true + "include": [ + "**" + ] } } diff --git a/utils/cdk-nag-scan/package.json b/utils/cdk-nag-scan/package.json index ee017db..5c27fe0 100644 --- a/utils/cdk-nag-scan/package.json +++ b/utils/cdk-nag-scan/package.json @@ -1,14 +1,12 @@ { - "name": "cdk-nag-scan", - "version": "0.1.0", "bin": { "cdk-nag-scan": "bin/cdk-nag-scan.js" }, - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "test": "jest", - "cdk": "cdk" + "dependencies": { + "aws-cdk-lib": "^2.87.0", + "cdk-nag": "^2.27.61", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" }, "devDependencies": { "@types/jest": "^29.5.1", @@ -19,10 +17,12 @@ "ts-node": "^10.9.1", "typescript": "~5.1.3" }, - "dependencies": { - "aws-cdk-lib": "^2.87.0", - "cdk-nag": "^2.27.61", - "constructs": "^10.0.0", - "source-map-support": "^0.5.21" - } + "name": "cdk-nag-scan", + "scripts": { + "build": "tsc", + "cdk": "cdk", + "test": "jest", + "watch": "tsc -w" + }, + "version": "0.1.0" } diff --git a/utils/cdk-nag-scan/tsconfig.json b/utils/cdk-nag-scan/tsconfig.json index aaa7dc5..b863adc 100644 --- a/utils/cdk-nag-scan/tsconfig.json +++ b/utils/cdk-nag-scan/tsconfig.json @@ -1,25 +1,25 @@ { "compilerOptions": { - "target": "ES2020", - "module": "commonjs", + "alwaysStrict": true, + "declaration": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, "lib": [ "es2020", "dom" ], - "declaration": true, - "strict": true, + "module": "commonjs", + "noFallthroughCasesInSwitch": false, "noImplicitAny": true, - "strictNullChecks": true, + "noImplicitReturns": true, "noImplicitThis": true, - "alwaysStrict": true, "noUnusedLocals": false, "noUnusedParameters": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": false, - "inlineSourceMap": true, - "inlineSources": true, - "experimentalDecorators": true, + "strict": true, + "strictNullChecks": true, "strictPropertyInitialization": false, + "target": "ES2020", "typeRoots": [ "./node_modules/@types" ] diff --git a/utils/cfn-to-cdk/README.md b/utils/cfn-to-cdk/README.md deleted file mode 100644 index a1959d8..0000000 --- a/utils/cfn-to-cdk/README.md +++ /dev/null @@ -1,65 +0,0 @@ - -# Welcome to your CDK Python project! - -You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`cfn_to_cdk_stack`) -which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic. - -The `cdk.json` file tells the CDK Toolkit how to execute your app. - -This project is set up like a standard Python project. The initialization process also creates -a virtualenv within this project, stored under the .venv directory. To create the virtualenv -it assumes that there is a `python3` executable in your path with access to the `venv` package. -If for any reason the automatic creation of the virtualenv fails, you can create the virtualenv -manually once the init process completes. - -To manually create a virtualenv on MacOS and Linux: - -``` -$ python3 -m venv .venv -``` - -After the init process completes and the virtualenv is created, you can use the following -step to activate your virtualenv. - -``` -$ source .venv/bin/activate -``` - -If you are a Windows platform, you would activate the virtualenv like this: - -``` -% .venv\Scripts\activate.bat -``` - -Once the virtualenv is activated, you can install the required dependencies. - -``` -$ pip install -r requirements.txt -``` - -At this point you can now synthesize the CloudFormation template for this code. - -``` -$ cdk synth -``` - -You can now begin exploring the source code, contained in the hello directory. -There is also a very trivial test included that can be run like this: - -``` -$ pytest -``` - -To add additional dependencies, for example other CDK libraries, just add to -your requirements.txt file and rerun the `pip install -r requirements.txt` -command. - -## Useful commands - - * `cdk ls` list all stacks in the app - * `cdk synth` emits the synthesized CloudFormation template - * `cdk deploy` deploy this stack to your default AWS account/region - * `cdk diff` compare deployed stack with current state - * `cdk docs` open CDK documentation - -Enjoy! diff --git a/utils/cfn-to-cdk/app.py b/utils/cfn-to-cdk/app.py deleted file mode 100644 index 8efb3cc..0000000 --- a/utils/cfn-to-cdk/app.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 -from aws_cdk import App, Aspects -from cdk_nag import AwsSolutionsChecks - -import aws_cdk as cdk - -from cfn_to_cdk.cfn_to_cdk_stack import CfnToCdkStack - - -app = cdk.App() -CfnToCdkStack(app, "cfn-to-cdk") - - -Aspects.of(app).add(AwsSolutionsChecks()) -app.synth() diff --git a/utils/cfn-to-cdk/cdk.json b/utils/cfn-to-cdk/cdk.json deleted file mode 100644 index 8a20c3b..0000000 --- a/utils/cfn-to-cdk/cdk.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "app": "python3 app.py", - "watch": { - "include": [ - "**" - ], - "exclude": [ - "README.md", - "cdk*.json", - "requirements*.txt", - "source.bat", - "**/__init__.py", - "python/__pycache__", - "tests" - ] - }, - "context": { - "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, - "@aws-cdk/core:stackRelativeExports": true, - "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, - "@aws-cdk/aws-lambda:recognizeVersionProps": true, - "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/core:target-partitions": [ - "aws", - "aws-cn" - ] - } -} diff --git a/utils/cfn-to-cdk/cfn_to_cdk/cfn.json b/utils/cfn-to-cdk/cfn_to_cdk/cfn.json deleted file mode 100644 index 8f4b876..0000000 --- a/utils/cfn-to-cdk/cfn_to_cdk/cfn.json +++ /dev/null @@ -1,513 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "AWS CloudFormation Sample Template vpc_single_instance_in_subnet.template: Sample template showing how to create a VPC and add an EC2 instance with an Elastic IP address and a security group. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.", - "Parameters": { - "InstanceType": { - "Description": "WebServer EC2 instance type", - "Type": "String", - "Default": "m1.small", - "AllowedValues": [ - "t1.micro", - "m1.small", - "m1.medium", - "m1.large", - "m1.xlarge", - "m2.xlarge", - "m2.2xlarge", - "m2.4xlarge", - "m3.xlarge", - "m3.2xlarge", - "c1.medium", - "c1.xlarge", - "cc1.4xlarge", - "cc2.8xlarge", - "cg1.4xlarge" - ], - "ConstraintDescription": "must be a valid EC2 instance type." - }, - "KeyName": { - "Description": "Name of an existing EC2 KeyPair to enable SSH access to the instance", - "Type": "String", - "MinLength": "1", - "MaxLength": "255", - "AllowedPattern": "[\\x20-\\x7E]*", - "ConstraintDescription": "can contain only ASCII characters." - }, - "SSHLocation": { - "Description": " The IP address range that can be used to SSH to the EC2 instances", - "Type": "String", - "MinLength": "9", - "MaxLength": "18", - "Default": "0.0.0.0/0", - "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", - "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." - } - }, - "Mappings": { - "RegionMap": { - "us-east-1": { - "AMI": "ami-7f418316" - }, - "us-west-1": { - "AMI": "ami-951945d0" - }, - "us-west-2": { - "AMI": "ami-16fd7026" - }, - "eu-west-1": { - "AMI": "ami-24506250" - }, - "sa-east-1": { - "AMI": "ami-3e3be423" - }, - "ap-southeast-1": { - "AMI": "ami-74dda626" - }, - "ap-southeast-2": { - "AMI": "ami-b3990e89" - }, - "ap-northeast-1": { - "AMI": "ami-dcfa4edd" - } - } - }, - "Resources": { - "VPC": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.16.0.0/16", - "Tags": [ - { - "Key": "Application", - "Value": { - "Ref": "AWS::StackId" - } - } - ] - } - }, - "Subnet": { - "Type": "AWS::EC2::Subnet", - "Properties": { - "VpcId": { - "Ref": "VPC" - }, - "CidrBlock": "10.16.0.0/24", - "Tags": [ - { - "Key": "Application", - "Value": { - "Ref": "AWS::StackId" - } - } - ] - } - }, - "InternetGateway": { - "Type": "AWS::EC2::InternetGateway", - "Properties": { - "Tags": [ - { - "Key": "Application", - "Value": { - "Ref": "AWS::StackId" - } - } - ] - } - }, - "AttachGateway": { - "Type": "AWS::EC2::VPCGatewayAttachment", - "Properties": { - "VpcId": { - "Ref": "VPC" - }, - "InternetGatewayId": { - "Ref": "InternetGateway" - } - } - }, - "RouteTable": { - "Type": "AWS::EC2::RouteTable", - "Properties": { - "VpcId": { - "Ref": "VPC" - }, - "Tags": [ - { - "Key": "Application", - "Value": { - "Ref": "AWS::StackId" - } - } - ] - } - }, - "Route": { - "Type": "AWS::EC2::Route", - "DependsOn": "AttachGateway", - "Properties": { - "RouteTableId": { - "Ref": "RouteTable" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "InternetGateway" - } - } - }, - "SubnetRouteTableAssociation": { - "Type": "AWS::EC2::SubnetRouteTableAssociation", - "Properties": { - "SubnetId": { - "Ref": "Subnet" - }, - "RouteTableId": { - "Ref": "RouteTable" - } - } - }, - "NetworkAcl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "VPC" - }, - "Tags": [ - { - "Key": "Application", - "Value": { - "Ref": "AWS::StackId" - } - } - ] - } - }, - "InboundHTTPNetworkAclEntry": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "NetworkAcl" - }, - "RuleNumber": "100", - "Protocol": "6", - "RuleAction": "allow", - "Egress": "false", - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": "80", - "To": "80" - } - } - }, - "InboundSSHNetworkAclEntry": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "NetworkAcl" - }, - "RuleNumber": "101", - "Protocol": "6", - "RuleAction": "allow", - "Egress": "false", - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": "22", - "To": "22" - } - } - }, - "InboundResponsePortsNetworkAclEntry": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "NetworkAcl" - }, - "RuleNumber": "102", - "Protocol": "6", - "RuleAction": "allow", - "Egress": "false", - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": "1024", - "To": "65535" - } - } - }, - "OutBoundHTTPNetworkAclEntry": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "NetworkAcl" - }, - "RuleNumber": "100", - "Protocol": "6", - "RuleAction": "allow", - "Egress": "true", - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": "80", - "To": "80" - } - } - }, - "OutBoundHTTPSNetworkAclEntry": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "NetworkAcl" - }, - "RuleNumber": "101", - "Protocol": "6", - "RuleAction": "allow", - "Egress": "true", - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": "443", - "To": "443" - } - } - }, - "OutBoundResponsePortsNetworkAclEntry": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "NetworkAcl" - }, - "RuleNumber": "102", - "Protocol": "6", - "RuleAction": "allow", - "Egress": "true", - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": "1024", - "To": "65535" - } - } - }, - "SubnetNetworkAclAssociation": { - "Type": "AWS::EC2::SubnetNetworkAclAssociation", - "Properties": { - "SubnetId": { - "Ref": "Subnet" - }, - "NetworkAclId": { - "Ref": "NetworkAcl" - } - } - }, - "IPAddress": { - "Type": "AWS::EC2::EIP", - "DependsOn": "AttachGateway", - "Properties": { - "Domain": "vpc", - "InstanceId": { - "Ref": "WebServerInstance" - } - } - }, - "InstanceSecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "VPC" - }, - "GroupDescription": "Enable SSH access via port 22", - "SecurityGroupIngress": [ - {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"}, - {"IpProtocol" : "tcp", "FromPort" : "443", "ToPort" : "443", "CidrIp" : "0.0.0.0/0"}, - {"IpProtocol" : "icmp", "FromPort" : "8", "ToPort" : "-1", "CidrIp" : "0.0.0.0/0" }, - {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "0.0.0.0/0"}} - ] - } - }, - "WebServerInstance": { - "Type": "AWS::EC2::Instance", - "Metadata": { - "Comment": "Install a simple PHP application", - "AWS::CloudFormation::Init": { - "config": { - "packages": { - "yum": { - "httpd": [], - "php": [] - } - }, - "files": { - "/var/www/html/index.php": { - "content": { - "Fn::Join": [ - "", - [ - "AWS CloudFormation sample PHP application';\n", - "?>\n" - ] - ] - }, - "mode": "000644", - "owner": "apache", - "group": "apache" - }, - "/etc/cfn/cfn-hup.conf": { - "content": { - "Fn::Join": [ - "", - [ - "[main]\n", - "stack=", - { - "Ref": "AWS::StackId" - }, - "\n", - "region=", - { - "Ref": "AWS::Region" - }, - "\n" - ] - ] - }, - "mode": "000400", - "owner": "root", - "group": "root" - }, - "/etc/cfn/hooks.d/cfn-auto-reloader.conf": { - "content": { - "Fn::Join": [ - "", - [ - "[cfn-auto-reloader-hook]\n", - "triggers=post.update\n", - "path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init\n", - "action=/opt/aws/bin/cfn-init -s ", - { - "Ref": "AWS::StackId" - }, - " -r WebServerInstance ", - " --region ", - { - "Ref": "AWS::Region" - }, - "\n", - "runas=root\n" - ] - ] - } - } - }, - "services": { - "sysvinit": { - "httpd": { - "enabled": "true", - "ensureRunning": "true" - }, - "sendmail": { - "enabled": "false", - "ensureRunning": "false" - } - } - } - } - } - }, - "Properties": { - "ImageId": "ami-a4c7edb2", - "SecurityGroupIds": [ - { - "Ref": "InstanceSecurityGroup" - } - ], - "SubnetId": { - "Ref": "Subnet" - }, - "InstanceType": { - "Ref": "InstanceType" - }, - "KeyName": { - "Ref": "KeyName" - }, - "Tags": [ - { - "Key": "Application", - "Value": { - "Ref": "AWS::StackId" - } - } - ], - "UserData": { - "Fn::Base64": { - "Fn::Join": [ - "", - [ - "#!/bin/bash\n", - "yum update -y aws-cfn-bootstrap\n", - "# Helper function\n", - "function error_exit\n", - "{\n", - " /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '", - { - "Ref": "WebServerWaitHandle" - }, - "'\n", - " exit 1\n", - "}\n", - "# Install the simple web page\n", - "/opt/aws/bin/cfn-init -s ", - { - "Ref": "AWS::StackId" - }, - " -r WebServerInstance ", - " --region ", - { - "Ref": "AWS::Region" - }, - " || error_exit 'Failed to run cfn-init'\n", - "# Start up the cfn-hup daemon to listen for changes to the Web Server metadata\n", - "/opt/aws/bin/cfn-hup || error_exit 'Failed to start cfn-hup'\n", - "# All done so signal success\n", - "/opt/aws/bin/cfn-signal -e 0 -r \"WebServer setup complete\" '", - { - "Ref": "WebServerWaitHandle" - }, - "'\n" - ] - ] - } - } - } - }, - "WebServerWaitHandle": { - "Type": "AWS::CloudFormation::WaitConditionHandle" - }, - "WebServerWaitCondition": { - "Type": "AWS::CloudFormation::WaitCondition", - "DependsOn": "WebServerInstance", - "Properties": { - "Handle": { - "Ref": "WebServerWaitHandle" - }, - "Timeout": "300" - } - } - }, - "Outputs": { - "URL": { - "Value": { - "Fn::Join": [ - "", - [ - "http://", - { - "Fn::GetAtt": [ - "WebServerInstance", - "PublicIp" - ] - } - ] - ] - }, - "Description": "Newly created application URL" - } - } -} \ No newline at end of file diff --git a/utils/cfn-to-cdk/cfn_to_cdk/cfn_to_cdk_stack.py b/utils/cfn-to-cdk/cfn_to_cdk/cfn_to_cdk_stack.py deleted file mode 100644 index 80f8b27..0000000 --- a/utils/cfn-to-cdk/cfn_to_cdk/cfn_to_cdk_stack.py +++ /dev/null @@ -1,13 +0,0 @@ -import aws_cdk as cdk -from aws_cdk import cloudformation_include as cfn_inc -from constructs import Construct - - -class CfnToCdkStack(cdk.Stack): - - def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: - super().__init__(scope, construct_id, **kwargs) - - - template0 = cfn_inc.CfnInclude(self, "/app/test.yaml", - template_file="/app/test.yaml") diff --git a/utils/cfn-to-cdk/cfn_to_cdk/cfn_to_cdk_stack.py.j2 b/utils/cfn-to-cdk/cfn_to_cdk/cfn_to_cdk_stack.py.j2 deleted file mode 100644 index 90280c3..0000000 --- a/utils/cfn-to-cdk/cfn_to_cdk/cfn_to_cdk_stack.py.j2 +++ /dev/null @@ -1,14 +0,0 @@ -import aws_cdk as cdk -from aws_cdk import cloudformation_include as cfn_inc -from constructs import Construct - - -class CfnToCdkStack(cdk.Stack): - - def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: - super().__init__(scope, construct_id, **kwargs) - - {% for id, file in enumerate(files) %} - template{{ id }} = cfn_inc.CfnInclude(self, "{{ file }}", - template_file="{{ file }}") - {% endfor -%} diff --git a/utils/cfn-to-cdk/cfn_to_cdk/template_generator.py b/utils/cfn-to-cdk/cfn_to_cdk/template_generator.py deleted file mode 100755 index 2c30fda..0000000 --- a/utils/cfn-to-cdk/cfn_to_cdk/template_generator.py +++ /dev/null @@ -1,10 +0,0 @@ -from jinja2 import Template -import sys -files = sys.argv[1:] -#print (files) -with open('/utils/cfn-to-cdk/cfn_to_cdk/cfn_to_cdk_stack.py.j2') as f: - template = Template(f.read()) - -b=template.render(enumerate=enumerate, files=files) -with open("/utils/cfn-to-cdk/cfn_to_cdk/cfn_to_cdk_stack.py", "w") as fh: - fh.write(b) diff --git a/utils/cfn-to-cdk/requirements-dev.txt b/utils/cfn-to-cdk/requirements-dev.txt deleted file mode 100644 index 9270945..0000000 --- a/utils/cfn-to-cdk/requirements-dev.txt +++ /dev/null @@ -1 +0,0 @@ -pytest==6.2.5 diff --git a/utils/cfn-to-cdk/requirements.txt b/utils/cfn-to-cdk/requirements.txt deleted file mode 100644 index 12445d2..0000000 --- a/utils/cfn-to-cdk/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -constructs>=10.0.0,<11.0.0 -jsii>=1.60.1 \ No newline at end of file diff --git a/utils/get-scan-set.py b/utils/get-scan-set.py deleted file mode 100755 index 0a12ffe..0000000 --- a/utils/get-scan-set.py +++ /dev/null @@ -1,181 +0,0 @@ -#!/usr/bin/env python -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 - -import re -import sys -from datetime import datetime -from typing import List -from pathspec import PathSpec -from pathlib import Path -import argparse -import os -from glob import glob - -ASH_INCLUSIONS=[ - '.git', - "**/cdk.out/asset.*", - "!**/*.template.json", # CDK output template default path pattern -] - - -def red(msg) -> str: - return "\033[91m{}\033[00m".format(msg) - -def green(msg) -> str: - return "\033[92m{}\033[00m".format(msg) - -def yellow(msg) -> str: - return "\033[33m{}\033[00m".format(msg) - -def lightPurple(msg) -> str: - return "\033[94m{}\033[00m".format(msg) - -def purple(msg) -> str: - return "\033[95m{}\033[00m".format(msg) - -def cyan(msg) -> str: - return "\033[96m{}\033[00m".format(msg) - -def gray(msg) -> str: - return "\033[97m{}\033[00m".format(msg) - -def black(msg) -> str: - return "\033[98m{}\033[00m".format(msg) - -def debug_echo(*msg, debug: bool = False) -> str: - if debug: - print(yellow(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] [get-scan-set.py] DEBUG:"), *msg, file=sys.stderr) - -def get_ash_ignorespec_lines( - path, - ignorefiles: List[str] = [], - debug: bool = False, -) -> List[str]: - dotignores = [ - f"{path}/.ignore", - *[ - item - for item in glob(f"{path}/**/.ignore") - ] - ] - # ashignores = [ - # f"{path}/.ashignore", - # *[ - # item - # for item in glob(f"{path}/**/.ashignore") - # ] - # ] - gitignores = [ - f"{path}/.gitignore", - *[ - item - for item in glob(f"{path}/**/.gitignore") - ] - ] - all_ignores = list(set([ - *dotignores, - *gitignores, - # *ashignores, - *[ - f"{path}/{file}" - for file in ignorefiles - ] - ])) - lines = [] - for ignorefile in all_ignores: - if os.path.isfile(ignorefile): - clean = re.sub(rf"^{re.escape(path)}", '${SOURCE_DIR}', ignorefile) - debug_echo(f"Found .ignore file: {clean}", debug=debug) - lines.append(f"######### START CONTENTS: {clean} #########") - with open(ignorefile) as f: - lines.extend(f.readlines()) - lines.append(f"######### END CONTENTS: {clean} #########") - lines.append("") - lines = [ line.strip() for line in lines ] - lines.append(f"######### START CONTENTS: ASH_INCLUSIONS #########") - lines.extend(ASH_INCLUSIONS) - lines.append(f"######### END CONTENTS: ASH_INCLUSIONS #########") - return lines - -def get_ash_ignorespec( - lines: List[str], - debug: bool = False, -) -> PathSpec: - debug_echo("Generating spec from collected ignorespec lines", debug=debug) - spec = PathSpec.from_lines('gitwildmatch', lines) - return spec - -def get_files_not_matching_spec( - path, - spec, - debug: bool = False, -): - full = [] - included = [] - for item in os.walk(path): - for file in item[2]: - full.append(os.path.join(item[0], file)) - inc_full = os.path.join(item[0], file) - clean = re.sub(rf"^{re.escape(path)}", '${SOURCE_DIR}', inc_full) - if not spec.match_file(inc_full): - if '/node_modules/aws-cdk' not in inc_full: - debug_echo(f"Matched file for scan set: {clean}", debug=debug) - included.append(inc_full) - # elif '/.git/' not in inc_full: - # debug_echo(f"Ignoring file matching spec: {clean}", debug=debug) - included = sorted(set(included)) - return included - -if __name__ == "__main__": - # set up argparse - parser = argparse.ArgumentParser(description="Get list of files not matching .gitignore underneath SourceDir arg path") - parser.add_argument("--source", help="path to scan", default=os.getcwd(), type=str) - parser.add_argument("--output", help="output path to save the ash-ignore-report.txt and ash-scan-set-files-list.txt files to", default=None, type=str) - parser.add_argument("--ignorefile", help="ignore file to use in addition to the standard gitignore", default=[], type=str, nargs='*') - parser.add_argument("--debug", help="Enables debug logging", action=argparse.BooleanOptionalAction) - args = parser.parse_args() - - ashignore_content = None - ashscanset_list = None - ashignore_imported = False - ashscanset_imported = False - - if args.output: - ashignore_path = Path(args.output).joinpath('ash-ignore-report.txt') - ashscanset_path = Path(args.output).joinpath('ash-scan-set-files-list.txt') - if ashignore_path.exists(): - with open(ashignore_path) as f: - ashignore_content = f.readlines() - ashignore_imported = True - print(cyan(f"Imported ash-ignore-report.txt from {args.output}"), file=sys.stderr) - if ashscanset_path.exists(): - with open(ashscanset_path) as f: - ashscanset_list = f.readlines() - ashscanset_imported = True - print(cyan(f"Imported ash-scan-set-files-list.txt from {args.output}"), file=sys.stderr) - - if not ashignore_content: - ashignore_content = get_ash_ignorespec_lines(args.source, args.ignorefile, debug=args.debug) - - if not ashscanset_list: - spec = get_ash_ignorespec(ashignore_content, debug=args.debug) - ashscanset_list = get_files_not_matching_spec(args.source, spec, debug=args.debug) - - for file in ashscanset_list: - print(file, file=sys.stdout) - - if args.output: - if ashignore_imported == False: - debug_echo(f"Writing ash-ignore-report.txt to {args.output}", debug=args.debug) - if not ashignore_path.parent.exists(): - ashignore_path.parent.mkdir(parents=True) - with open(ashignore_path, "w") as f: - f.write("\n".join(ashignore_content)) - - if ashscanset_imported == False: - debug_echo(f"Writing ash-scan-set-files-list.txt to {args.output}", debug=args.debug) - if not ashscanset_path.parent.exists(): - ashscanset_path.parent.mkdir(parents=True) - with open(ashscanset_path, "w") as f: - f.write("\n".join(ashscanset_list)) diff --git a/utils/git-docker-execute.sh b/utils/git-docker-execute.sh index dd665b4..2d20b96 100644 --- a/utils/git-docker-execute.sh +++ b/utils/git-docker-execute.sh @@ -49,7 +49,12 @@ if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ]]; then fi # Set REPORT_PATH to the report location, then touch it to ensure it exists -REPORT_PATH="${_ASH_OUTPUT_DIR}/work/git_report_result.txt" +SCANNER_DIR="${_ASH_OUTPUT_DIR}/scanners" +RESULTS_DIR="${SCANNER_DIR}/results" + +mkdir -p "${RESULTS_DIR}" + +REPORT_PATH="${RESULTS_DIR}/git_report_result.txt" rm ${REPORT_PATH} 2> /dev/null touch ${REPORT_PATH} @@ -60,7 +65,12 @@ echo ">>>>>> begin tree result >>>>>>" >> "${REPORT_PATH}" if [ "$_ASH_IS_GIT_REPOSITORY" -eq 1 ]; then echo "Git repository detected. Ensure your .gitignore configuration excludes all the files that you intend to ignore." >> "${REPORT_PATH}" fi; -tree ${_TREE_FLAGS} "${_ASH_SOURCE_DIR}" >> "${REPORT_PATH}" 2>&1 + +if [ -d "${SCANNER_DIR}/git" ]; then + rm -rf "${SCANNER_DIR}/git" +fi +mkdir -p "${SCANNER_DIR}/git" +tree ${_TREE_FLAGS} "${_ASH_SOURCE_DIR}" >> "${REPORT_PATH}" 2>&1 > "${SCANNER_DIR}/git/tree.txt" echo "<<<<<< end tree ${_TREE_FLAGS} result <<<<<<" >> "${REPORT_PATH}" # @@ -80,16 +90,16 @@ else # # Configure the repo to check for AWS secrets # - git secrets --register-aws >>"${REPORT_PATH}" 2>&1 + git secrets --register-aws >>"${REPORT_PATH}" 2>&1 >> "${SCANNER_DIR}/git/git_secrets_aws.txt" # # List the Git secrets configuration # echo "git config --local --get-regexp \"^secrets\\..*\$\" output:" >>"${REPORT_PATH}" 2>&1 - git config --local --get-regexp "^secrets\..*$" >>"${REPORT_PATH}" 2>&1 + git config --local --get-regexp "^secrets\..*$" >>"${REPORT_PATH}" 2>&1 >> "${SCANNER_DIR}/git/git_secrets.txt" echo ">>>>>> begin git secrets --scan result >>>>>>" >> "${REPORT_PATH}" - git secrets --scan >> "${REPORT_PATH}" 2>&1 + git secrets --scan >> "${REPORT_PATH}" 2>&1 >> "${SCANNER_DIR}/git/git_secrets.txt" GRC=$? RC=$(bumprc $RC $GRC) echo "<<<<<< end git secrets --scan result <<<<<<" >> "${REPORT_PATH}" diff --git a/utils/grype-docker-execute.sh b/utils/grype-docker-execute.sh index 53c1b92..1afbc71 100644 --- a/utils/grype-docker-execute.sh +++ b/utils/grype-docker-execute.sh @@ -74,7 +74,12 @@ cd "${_ASH_SOURCE_DIR}" debug_echo "[grype] pwd: '$(pwd)' :: _ASH_SOURCE_DIR: ${_ASH_SOURCE_DIR} :: _ASH_RUN_DIR: ${_ASH_RUN_DIR}" # Set REPORT_PATH to the report location, then touch it to ensure it exists -REPORT_PATH="${_ASH_OUTPUT_DIR}/work/grype_report_result.txt" +SCANNER_DIR="${_ASH_OUTPUT_DIR}/scanners" +RESULTS_DIR="${SCANNER_DIR}/results" + +mkdir -p "${RESULTS_DIR}" + +REPORT_PATH="${RESULTS_DIR}/grype_report_result.txt" rm ${REPORT_PATH} 2> /dev/null touch ${REPORT_PATH} @@ -89,7 +94,15 @@ if [[ "${ASH_OUTPUT_FORMAT:-text}" != "text" ]]; then GRYPE_ARGS="-o json ${GRYPE_ARGS}" SYFT_ARGS="-o json ${SYFT_ARGS}" SEMGREP_ARGS="--json ${SEMGREP_ARGS}" + TEEFILEEXT="json" +else + TEEFILEEXT="txt" +fi + +if [ -d "${SCANNER_DIR}/grype" ]; then + rm -rf "${SCANNER_DIR}/grype" fi +mkdir -p "${SCANNER_DIR}/grype" # # Run Grype @@ -98,13 +111,14 @@ debug_echo "[grype] Starting all scanners within the Grype scanner tool set" for i in "${!scan_paths[@]}"; do scan_path=${scan_paths[$i]} + cleanfile=$(echo $scan_path | sed 's/\//./g;s/^\.//g') cd ${scan_path} debug_echo "[grype] Starting Grype scan of ${scan_path}" # debug_show_tree ${scan_path} ${REPORT_PATH} echo -e "\n>>>>>> Begin Grype output for ${scan_path} >>>>>>\n" >> ${REPORT_PATH} debug_echo "[grype] grype ${GRYPE_ARGS} dir:${scan_path}" - grype ${GRYPE_ARGS} dir:${scan_path} >> ${REPORT_PATH} 2>&1 + grype ${GRYPE_ARGS} dir:${scan_path} >> ${REPORT_PATH} 2>&1 > "${SCANNER_DIR}/grype/grype.${cleanfile}.${TEEFILEEXT}" SRC=$? RC=$(bumprc $RC $SRC) @@ -118,13 +132,14 @@ done for i in "${!scan_paths[@]}"; do scan_path=${scan_paths[$i]} + cleanfile=$(echo $scan_path | sed 's/\//./g;s/^\.//g') cd ${scan_path} debug_echo "[grype] Starting Syft scan of ${scan_path}" # debug_show_tree ${scan_path} ${REPORT_PATH} echo -e "\n>>>>>> Begin Syft output for ${scan_path} >>>>>>\n" >> ${REPORT_PATH} debug_echo "[grype] syft ${SYFT_ARGS} ${scan_path}" - syft ${SYFT_ARGS} ${scan_path} >> ${REPORT_PATH} 2>&1 + syft ${SYFT_ARGS} ${scan_path} >> ${REPORT_PATH} 2>&1 > "${SCANNER_DIR}/grype/syft.${cleanfile}.${TEEFILEEXT}" SRC=$? RC=$(bumprc $RC $SRC) @@ -138,13 +153,14 @@ done for i in "${!scan_paths[@]}"; do scan_path=${scan_paths[$i]} + cleanfile=$(echo $scan_path | sed 's/\//./g;s/^\.//g') cd ${scan_path} debug_echo "[grype] Starting Semgrep scan of ${scan_path}" # debug_show_tree ${scan_path} ${REPORT_PATH} echo -e "\n>>>>>> Begin Semgrep output for ${scan_path} >>>>>>\n" >> ${REPORT_PATH} debug_echo "[grype] semgrep ${SEMGREP_ARGS} $scan_path" - semgrep ${SEMGREP_ARGS} $scan_path >> ${REPORT_PATH} 2>&1 + semgrep ${SEMGREP_ARGS} $scan_path >> ${REPORT_PATH} 2>&1 > "${SCANNER_DIR}/grype/semgrep.${cleanfile}.${TEEFILEEXT}" SRC=$? RC=$(bumprc $RC $SRC) diff --git a/utils/js-docker-execute.sh b/utils/js-docker-execute.sh index f87b1d6..2b08d80 100755 --- a/utils/js-docker-execute.sh +++ b/utils/js-docker-execute.sh @@ -44,7 +44,12 @@ cd "${_ASH_SOURCE_DIR}" debug_echo "[js] pwd: '$(pwd)' :: _ASH_SOURCE_DIR: ${_ASH_SOURCE_DIR} :: _ASH_RUN_DIR: ${_ASH_RUN_DIR}" # Set REPORT_PATH to the report location, then touch it to ensure it exists -REPORT_PATH="${_ASH_OUTPUT_DIR}/work/js_report_result.txt" +SCANNER_DIR="${_ASH_OUTPUT_DIR}/scanners" +RESULTS_DIR="${SCANNER_DIR}/results" + +mkdir -p "${RESULTS_DIR}" + +REPORT_PATH="${RESULTS_DIR}/js_report_result.txt" rm ${REPORT_PATH} 2> /dev/null touch ${REPORT_PATH} @@ -56,11 +61,19 @@ debug_echo "[js] ASH_OUTPUT_FORMAT: '${ASH_OUTPUT_FORMAT:-text}'" if [[ "${ASH_OUTPUT_FORMAT:-text}" != "text" ]]; then debug_echo "[js] Output format is not 'text', setting output format options to JSON to enable easy translation into desired output format" AUDIT_ARGS="--json ${AUDIT_ARGS}" + TEEFILEEXT="json" +else + TEEFILEEXT="txt" fi if [[ $OFFLINE == "YES" ]]; then debug_echo "[js] JavaScript package auditing is not available in offline mode" else + if [ -d "${SCANNER_DIR}/npmaudit" ]; then + rm -rf "${SCANNER_DIR}/npmaudit" + fi + mkdir -p "${SCANNER_DIR}/npmaudit" + for i in "${!scan_paths[@]}"; do scan_path=${scan_paths[$i]} @@ -89,7 +102,7 @@ else echo -e "\n>>>>>> Begin ${audit_command} audit output for ${scan_path} >>>>>>\n" >> ${REPORT_PATH} - eval "${audit_command} audit ${AUDIT_ARGS} >> ${REPORT_PATH} 2>&1" + eval "${audit_command} audit ${AUDIT_ARGS}" >> ${REPORT_PATH} 2>&1 > "${SCANNER_DIR}/npmaudit/${audit_command}.${TEEFILEEXT}" NRC=$? RC=$(bumprc $RC $NRC) diff --git a/utils/py-docker-execute.sh b/utils/py-docker-execute.sh index 6215642..b5cc528 100644 --- a/utils/py-docker-execute.sh +++ b/utils/py-docker-execute.sh @@ -44,7 +44,12 @@ cd "${_ASH_SOURCE_DIR}" debug_echo "[py] pwd: '$(pwd)' :: _ASH_SOURCE_DIR: ${_ASH_SOURCE_DIR} :: _ASH_RUN_DIR: ${_ASH_RUN_DIR}" # Set REPORT_PATH to the report location, then touch it to ensure it exists -REPORT_PATH="${_ASH_OUTPUT_DIR}/work/py_report_result.txt" +SCANNER_DIR="${_ASH_OUTPUT_DIR}/scanners" +RESULTS_DIR="${SCANNER_DIR}/results" + +mkdir -p "${RESULTS_DIR}" + +REPORT_PATH="${RESULTS_DIR}/py_report_result.txt" rm ${REPORT_PATH} 2> /dev/null touch ${REPORT_PATH} @@ -72,15 +77,25 @@ debug_echo "[py] ASH_OUTPUT_FORMAT: '${ASH_OUTPUT_FORMAT:-text}'" if [[ "${ASH_OUTPUT_FORMAT:-text}" != "text" ]]; then debug_echo "[py] Output format is not 'text', setting output format options to JSON to enable easy translation into desired output format" BANDIT_ARGS="-f json ${BANDIT_ARGS}" + TEEFILEEXT="json" +else + TEEFILEEXT="txt" fi +if [ -d "${SCANNER_DIR}/bandit" ]; then + rm -rf "${SCANNER_DIR}/bandit" +fi +mkdir -p "${SCANNER_DIR}/bandit" + for i in "${!scan_paths[@]}"; do scan_path=${scan_paths[$i]} cd ${scan_path} + + cleanfile=$(echo $scan_path | sed 's/\//./g;s/^\.//g') echo ">>>>>> begin bandit result for ${scan_path} >>>>>>" >> ${REPORT_PATH} - python3 -m bandit ${BANDIT_ARGS} -r $(pwd) >> ${REPORT_PATH} 2>&1 + python3 -m bandit ${BANDIT_ARGS} -r $(pwd) >> ${REPORT_PATH} 2>&1 > "${SCANNER_DIR}/bandit/${cleanfile}.${TEEFILEEXT}" BRC=$? RC=$(bumprc $RC $BRC) echo >> ${REPORT_PATH} # ensure that we have a newline separating end-of-section diff --git a/utils/yaml-docker-execute.sh b/utils/yaml-docker-execute.sh index 48c3a70..ead19af 100644 --- a/utils/yaml-docker-execute.sh +++ b/utils/yaml-docker-execute.sh @@ -44,7 +44,12 @@ cd "${_ASH_SOURCE_DIR}" debug_echo "[yaml] pwd: '$(pwd)' :: _ASH_SOURCE_DIR: ${_ASH_SOURCE_DIR} :: _ASH_RUN_DIR: ${_ASH_RUN_DIR}" # Set REPORT_PATH to the report location, then touch it to ensure it exists -REPORT_PATH="${_ASH_OUTPUT_DIR}/work/yaml_report_result.txt" +SCANNER_DIR="${_ASH_OUTPUT_DIR}/scanners" +RESULTS_DIR="${SCANNER_DIR}/results" + +mkdir -p "${RESULTS_DIR}" + +REPORT_PATH="${RESULTS_DIR}/yaml_report_result.txt" rm ${REPORT_PATH} 2> /dev/null touch ${REPORT_PATH} @@ -71,8 +76,10 @@ if [[ "${ASH_OUTPUT_FORMAT:-text}" != "text" ]]; then debug_echo "[yaml] Output format is not 'text', setting output format options to JSON to enable easy translation into desired output format" CHECKOV_ARGS="${CHECKOV_ARGS} --output=json" CFNNAG_ARGS="--output-format json ${CFNNAG_ARGS}" + TEEFILEEXT="json" else CFNNAG_ARGS="--output-format txt ${CFNNAG_ARGS}" + TEEFILEEXT="txt" fi if [[ $OFFLINE == "YES" ]]; then @@ -92,7 +99,7 @@ do # # find only files that appear to contain CloudFormation templates # - cfn_files=($(readlink -f $(grep -lri 'AWSTemplateFormatVersion' . --exclude-dir={cdk.out,utils,.aws-sam,ash_cf2cdk_output} --exclude=ash) 2>/dev/null)) + cfn_files=($(readlink -f $(grep -lri 'AWSTemplateFormatVersion' . --exclude-dir={cdk.out,utils,.aws-sam,ash_cf2cdk_output,ash_output} --exclude=ash) 2>/dev/null)) # # For checkov scanning, add in files that are GitLab CI files or container build files @@ -100,6 +107,7 @@ do checkov_files=($(readlink -f $(find . \( -iname ".gitlab-ci.yml" \ -or -iname "*Dockerfile*" \ -or -iname "*.tf" \ + -or -iname "aggregated_results.txt.json" \ -or -iname "*.tf.json" \) \ -not -path "./.git/*" \ -not -path "./.github/*" \ @@ -114,16 +122,23 @@ do ##Vendor Issue: https://github.com/bridgecrewio/checkov/issues/5627 export CHECKOV_RENDER_MAX_LEN=0 + if [ -d "${SCANNER_DIR}/checkov" ]; then + rm -rf "${SCANNER_DIR}/checkov" + fi + mkdir -p "${SCANNER_DIR}/checkov" + for file in "${checkov_files[@]}"; do #echo $cfn_files file1=`basename $file` + ## Get clean relative file name so it remains unique + cleanfile=$(echo $file | sed 's/\//./g;s/^\.//g') echo ">>>>>> begin checkov result for ${file1} >>>>>>" >> ${REPORT_PATH} # # Run the checkov scan on the file # checkov_call="checkov ${CHECKOV_ARGS} -f '${file}'" debug_echo "[yaml] Running checkov ${checkov_call}" - eval $checkov_call >> ${REPORT_PATH} 2>&1 + eval $checkov_call >> ${REPORT_PATH} 2>&1 > "${SCANNER_DIR}/checkov/${cleanfile}.${TEEFILEEXT}" CHRC=$? echo "<<<<<< end checkov result for ${file1} <<<<<<" >> ${REPORT_PATH} RC=$(bumprc $RC $CHRC) @@ -135,16 +150,25 @@ do if [ "${#cfn_files[@]}" -gt 0 ]; then echo "found ${#cfn_files[@]} files to scan. Starting cfn_nag scans ..." >> ${REPORT_PATH} + if [ -d "${SCANNER_DIR}/cfn_nag" ]; then + rm -rf "${SCANNER_DIR}/cfn_nag" + fi + mkdir -p "${SCANNER_DIR}/cfn_nag" + for file in "${cfn_files[@]}"; do file1=`basename $file` - echo ">>>>>> begin cfn_nag_scan result for ${file1} >>>>>>" >> ${REPORT_PATH} - # - # Run the cfn_nag scan on the file - # - cfn_nag_scan ${CFNNAG_ARGS} --input-path "${file}" >> ${REPORT_PATH} 2>&1 - CNRC=$? - echo "<<<<<< end cfn_nag_scan result for ${file1} <<<<<<" >> ${REPORT_PATH} - RC=$(bumprc $RC $CNRC) + ## Get clean relative file name so it remains unique + cleanfile=$(echo $file | sed 's/\//./g;s/^\.//g') + if [[ "${file1}" != "aggregated_results.txt.json" ]]; then + echo ">>>>>> begin cfn_nag_scan result for ${file1} >>>>>>" >> ${REPORT_PATH} + # + # Run the cfn_nag scan on the file + # + eval "cfn_nag_scan ${CFNNAG_ARGS} --input-path \"${file}\"" >> ${REPORT_PATH} 2>&1 > "${SCANNER_DIR}/cfn_nag/${cleanfile}.txt" + CNRC=$? + echo "<<<<<< end cfn_nag_scan result for ${file1} <<<<<<" >> ${REPORT_PATH} + RC=$(bumprc $RC $CNRC) + fi done else echo "found ${#cfn_files[@]} files to scan. Skipping cfn_nag scans." >> ${REPORT_PATH}