From ae6a01c2e40de0bbac5fce0711e570e2a38a1746 Mon Sep 17 00:00:00 2001 From: Matteo Lodi <30625432+mlodic@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:14:50 +0200 Subject: [PATCH 01/27] removed organization sponsor link --- .github/FUNDING.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 98cbcaa6..52d5c38b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1 @@ open_collective: intelowl-project -github: intelowlproject \ No newline at end of file From 0a6560ee1c83732c654b218f629c381fd9391282 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:15:10 +0200 Subject: [PATCH 02/27] build(deps): bump gunicorn from 25.2.0 to 25.3.0 (#1163) Bumps [gunicorn](https://github.com/benoitc/gunicorn) from 25.2.0 to 25.3.0. - [Release notes](https://github.com/benoitc/gunicorn/releases) - [Commits](https://github.com/benoitc/gunicorn/compare/25.2.0...25.3.0) --- updated-dependencies: - dependency-name: gunicorn dependency-version: 25.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a6d333bd..1690b2bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dependencies = [ "croniter==6.2.2", "certego-saas==0.7.12", # Server Gateway Interface - "gunicorn==25.2.0", + "gunicorn==25.3.0", # Data stores "elasticsearch==9.3.0", "psycopg2-binary==2.9.11", diff --git a/uv.lock b/uv.lock index 92a075a7..9d64b8d9 100644 --- a/uv.lock +++ b/uv.lock @@ -529,7 +529,7 @@ requires-dist = [ { name = "djangorestframework", specifier = "==3.17.1" }, { name = "elasticsearch", specifier = "==9.3.0" }, { name = "feedparser", specifier = "==6.0.12" }, - { name = "gunicorn", specifier = "==25.2.0" }, + { name = "gunicorn", specifier = "==25.3.0" }, { name = "joblib", specifier = "==1.5.3" }, { name = "numpy", specifier = "==2.4.3" }, { name = "pandas", specifier = "==3.0.1" }, @@ -550,14 +550,14 @@ lint = [{ name = "ruff", specifier = "==0.15.8" }] [[package]] name = "gunicorn" -version = "25.2.0" +version = "25.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/13/dd3f8e40ea3ee907a6cbf3d1f1f81afcc3ecd0087d313baabfe95372f15c/gunicorn-25.2.0.tar.gz", hash = "sha256:10bd7adb36d44945d97d0a1fdf9a0fb086ae9c7b39e56b4dece8555a6bf4a09c", size = 632709, upload-time = "2026-03-24T22:49:54.433Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/f4/e78fa054248fab913e2eab0332c6c2cb07421fca1ce56d8fe43b6aef57a4/gunicorn-25.3.0.tar.gz", hash = "sha256:f74e1b2f9f76f6cd1ca01198968bd2dd65830edc24b6e8e4d78de8320e2fe889", size = 634883, upload-time = "2026-03-27T00:00:26.092Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/53/fb024445837e02cd5cf989cf349bfac6f3f433c05184ea5d49c8ade751c6/gunicorn-25.2.0-py3-none-any.whl", hash = "sha256:88f5b444d0055bf298435384af7294f325e2273fd37ba9f9ff7b98e0a1e5dfdc", size = 211659, upload-time = "2026-03-24T22:49:52.528Z" }, + { url = "https://files.pythonhosted.org/packages/43/c8/8aaf447698c4d59aa853fd318eed300b5c9e44459f242ab8ead6c9c09792/gunicorn-25.3.0-py3-none-any.whl", hash = "sha256:cacea387dab08cd6776501621c295a904fe8e3b7aae9a1a3cbb26f4e7ed54660", size = 208403, upload-time = "2026-03-27T00:00:27.386Z" }, ] [[package]] From 3b207a7158e652ec0eb1769fdd5f648a42d64009 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:15:26 +0200 Subject: [PATCH 03/27] build(deps): bump requests from 2.33.0 to 2.33.1 (#1164) Bumps [requests](https://github.com/psf/requests) from 2.33.0 to 2.33.1. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.33.0...v2.33.1) --- updated-dependencies: - dependency-name: requests dependency-version: 2.33.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1690b2bf..35bb8583 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "feedparser==6.0.12", "stix2==3.0.2", # Utilities - "requests==2.33.0", + "requests==2.33.1", "slack-sdk==3.41.0", ] diff --git a/uv.lock b/uv.lock index 9d64b8d9..04f45c9b 100644 --- a/uv.lock +++ b/uv.lock @@ -534,7 +534,7 @@ requires-dist = [ { name = "numpy", specifier = "==2.4.3" }, { name = "pandas", specifier = "==3.0.1" }, { name = "psycopg2-binary", specifier = "==2.9.11" }, - { name = "requests", specifier = "==2.33.0" }, + { name = "requests", specifier = "==2.33.1" }, { name = "scikit-learn", specifier = "==1.8.0" }, { name = "slack-sdk", specifier = "==3.41.0" }, { name = "stix2", specifier = "==3.0.2" }, @@ -812,7 +812,7 @@ wheels = [ [[package]] name = "requests" -version = "2.33.0" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -820,9 +820,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] From c45451805661b2c3f9c451eb4f4d7b5b550aca7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:15:38 +0200 Subject: [PATCH 04/27] build(deps): bump pandas from 3.0.1 to 3.0.2 (#1165) Bumps [pandas](https://github.com/pandas-dev/pandas) from 3.0.1 to 3.0.2. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Commits](https://github.com/pandas-dev/pandas/compare/v3.0.1...v3.0.2) --- updated-dependencies: - dependency-name: pandas dependency-version: 3.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 38 +++++++++++++++++++------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 35bb8583..d1c3d9a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ "psycopg2-binary==2.9.11", # ML / data science "scikit-learn==1.8.0", - "pandas==3.0.1", + "pandas==3.0.2", "numpy==2.4.3", "joblib==1.5.3", "datasketch==1.9.0", diff --git a/uv.lock b/uv.lock index 04f45c9b..10d31f12 100644 --- a/uv.lock +++ b/uv.lock @@ -532,7 +532,7 @@ requires-dist = [ { name = "gunicorn", specifier = "==25.3.0" }, { name = "joblib", specifier = "==1.5.3" }, { name = "numpy", specifier = "==2.4.3" }, - { name = "pandas", specifier = "==3.0.1" }, + { name = "pandas", specifier = "==3.0.2" }, { name = "psycopg2-binary", specifier = "==2.9.11" }, { name = "requests", specifier = "==2.33.1" }, { name = "scikit-learn", specifier = "==1.8.0" }, @@ -690,30 +690,30 @@ wheels = [ [[package]] name = "pandas" -version = "3.0.1" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509, upload-time = "2026-02-17T22:18:59.498Z" }, - { url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561, upload-time = "2026-02-17T22:19:02.265Z" }, - { url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506, upload-time = "2026-02-17T22:19:05.017Z" }, - { url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196, upload-time = "2026-02-17T22:19:07.204Z" }, - { url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859, upload-time = "2026-02-17T22:19:09.436Z" }, - { url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584, upload-time = "2026-02-17T22:19:11.562Z" }, - { url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769, upload-time = "2026-02-17T22:19:13.926Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855, upload-time = "2026-02-17T22:19:16.09Z" }, - { url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673, upload-time = "2026-02-17T22:19:18.304Z" }, - { url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967, upload-time = "2026-02-17T22:19:20.726Z" }, - { url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575, upload-time = "2026-02-17T22:19:24.919Z" }, - { url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921, upload-time = "2026-02-17T22:19:27.181Z" }, - { url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191, upload-time = "2026-02-17T22:19:29.411Z" }, - { url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256, upload-time = "2026-02-17T22:19:32.34Z" }, - { url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047, upload-time = "2026-02-17T22:19:34.605Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/da/99/b342345300f13440fe9fe385c3c481e2d9a595ee3bab4d3219247ac94e9a/pandas-3.0.2.tar.gz", hash = "sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043", size = 4645855, upload-time = "2026-03-31T06:48:30.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/ca/3e639a1ea6fcd0617ca4e8ca45f62a74de33a56ae6cd552735470b22c8d3/pandas-3.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5918ba197c951dec132b0c5929a00c0bf05d5942f590d3c10a807f6e15a57d3", size = 10321105, upload-time = "2026-03-31T06:46:57.327Z" }, + { url = "https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668", size = 9864088, upload-time = "2026-03-31T06:46:59.935Z" }, + { url = "https://files.pythonhosted.org/packages/5c/2b/341f1b04bbca2e17e13cd3f08c215b70ef2c60c5356ef1e8c6857449edc7/pandas-3.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:710246ba0616e86891b58ab95f2495143bb2bc83ab6b06747c74216f583a6ac9", size = 10369066, upload-time = "2026-03-31T06:47:02.792Z" }, + { url = "https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e", size = 10876780, upload-time = "2026-03-31T06:47:06.205Z" }, + { url = "https://files.pythonhosted.org/packages/98/fe/2249ae5e0a69bd0ddf17353d0a5d26611d70970111f5b3600cdc8be883e7/pandas-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c3b723df9087a9a9a840e263ebd9f88b64a12075d1bf2ea401a5a42f254f084d", size = 11375181, upload-time = "2026-03-31T06:47:09.383Z" }, + { url = "https://files.pythonhosted.org/packages/de/64/77a38b09e70b6464883b8d7584ab543e748e42c1b5d337a2ee088e0df741/pandas-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3096110bf9eac0070b7208465f2740e2d8a670d5cb6530b5bb884eca495fd39", size = 11928899, upload-time = "2026-03-31T06:47:12.686Z" }, + { url = "https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991", size = 9746574, upload-time = "2026-03-31T06:47:15.64Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/21304ae06a25e8bf9fc820d69b29b2c495b2ae580d1e143146c309941760/pandas-3.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:5fdbfa05931071aba28b408e59226186b01eb5e92bea2ab78b65863ca3228d84", size = 9047156, upload-time = "2026-03-31T06:47:18.595Z" }, + { url = "https://files.pythonhosted.org/packages/72/20/7defa8b27d4f330a903bb68eea33be07d839c5ea6bdda54174efcec0e1d2/pandas-3.0.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:dbc20dea3b9e27d0e66d74c42b2d0c1bed9c2ffe92adea33633e3bedeb5ac235", size = 10756238, upload-time = "2026-03-31T06:47:22.012Z" }, + { url = "https://files.pythonhosted.org/packages/e9/95/49433c14862c636afc0e9b2db83ff16b3ad92959364e52b2955e44c8e94c/pandas-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b75c347eff42497452116ce05ef461822d97ce5b9ff8df6edacb8076092c855d", size = 10408520, upload-time = "2026-03-31T06:47:25.197Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f8/462ad2b5881d6b8ec8e5f7ed2ea1893faa02290d13870a1600fe72ad8efc/pandas-3.0.2-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1478075142e83a5571782ad007fb201ed074bdeac7ebcc8890c71442e96adf7", size = 10324154, upload-time = "2026-03-31T06:47:28.097Z" }, + { url = "https://files.pythonhosted.org/packages/0a/65/d1e69b649cbcddda23ad6e4c40ef935340f6f652a006e5cbc3555ac8adb3/pandas-3.0.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5880314e69e763d4c8b27937090de570f1fb8d027059a7ada3f7f8e98bdcb677", size = 10714449, upload-time = "2026-03-31T06:47:30.85Z" }, + { url = "https://files.pythonhosted.org/packages/47/a4/85b59bc65b8190ea3689882db6cdf32a5003c0ccd5a586c30fdcc3ffc4fc/pandas-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5329e26898896f06035241a626d7c335daa479b9bbc82be7c2742d048e41172", size = 11338475, upload-time = "2026-03-31T06:47:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c4/bc6966c6e38e5d9478b935272d124d80a589511ed1612a5d21d36f664c68/pandas-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:81526c4afd31971f8b62671442a4b2b51e0aa9acc3819c9f0f12a28b6fcf85f1", size = 11786568, upload-time = "2026-03-31T06:47:36.941Z" }, + { url = "https://files.pythonhosted.org/packages/e8/74/09298ca9740beed1d3504e073d67e128aa07e5ca5ca2824b0c674c0b8676/pandas-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:7cadd7e9a44ec13b621aec60f9150e744cfc7a3dd32924a7e2f45edff31823b0", size = 10488652, upload-time = "2026-03-31T06:47:40.612Z" }, ] [[package]] From ed0d6218b29a47c7286ac4f1da5005034fd6b163 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 13:20:31 +0200 Subject: [PATCH 05/27] build(deps): bump numpy from 2.4.3 to 2.4.4 (#1166) Bumps [numpy](https://github.com/numpy/numpy) from 2.4.3 to 2.4.4. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v2.4.3...v2.4.4) --- updated-dependencies: - dependency-name: numpy dependency-version: 2.4.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 52 +++++++++++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d1c3d9a0..f440631a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ # ML / data science "scikit-learn==1.8.0", "pandas==3.0.2", - "numpy==2.4.3", + "numpy==2.4.4", "joblib==1.5.3", "datasketch==1.9.0", # File Format Support diff --git a/uv.lock b/uv.lock index 10d31f12..dd9a4c08 100644 --- a/uv.lock +++ b/uv.lock @@ -531,7 +531,7 @@ requires-dist = [ { name = "feedparser", specifier = "==6.0.12" }, { name = "gunicorn", specifier = "==25.3.0" }, { name = "joblib", specifier = "==1.5.3" }, - { name = "numpy", specifier = "==2.4.3" }, + { name = "numpy", specifier = "==2.4.4" }, { name = "pandas", specifier = "==3.0.2" }, { name = "psycopg2-binary", specifier = "==2.9.11" }, { name = "requests", specifier = "==2.33.1" }, @@ -643,31 +643,31 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, - { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, - { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, - { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, - { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, - { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, - { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, - { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, - { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, - { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, ] [[package]] From 786ff60521d3011b32a942927195e31f87c4fbd8 Mon Sep 17 00:00:00 2001 From: Arnav Vinod Deshpande Date: Wed, 1 Apr 2026 14:37:01 +0200 Subject: [PATCH 06/27] All instances don't hit enrichment sources at the same time. Closes #1158 (#1169) * schedules rescheduled * cooment * weekly generalisation --- greedybear/cronjobs/schedules.py | 37 ++++++++++------ .../management/test_setup_schedules.py | 43 +++++++++++++++++++ 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/greedybear/cronjobs/schedules.py b/greedybear/cronjobs/schedules.py index b773d0fb..9ad3d41f 100644 --- a/greedybear/cronjobs/schedules.py +++ b/greedybear/cronjobs/schedules.py @@ -1,7 +1,20 @@ +import hashlib + from django.conf import settings from django_q.models import Schedule +def _external_weekly_cron(job_name: str) -> str: + """Return a deterministic Sunday cron for external jobs outside 00:00-02:00.""" + seed_value = f"{settings.SECRET_KEY}:{job_name}" + digest = hashlib.sha256(seed_value.encode()).digest() + seed_int = int.from_bytes(digest[:8], byteorder="big", signed=False) + + minute = seed_int % 60 + hour = 2 + ((seed_int // 60) % 22) + return f"{minute} {hour} * * 0" + + def setup_schedules(): """ Configure Django Q2 scheduled tasks for the GreedyBear application. @@ -47,29 +60,29 @@ def setup_schedules(): "func": "greedybear.tasks.clean_up_db", "cron": "7 1 * * *", }, - # Mass Scanners: Weekly (Sunday) at 01:07 + # Mass Scanners: Weekly (Sunday) at deterministic time outside 00:00-02:00 { "name": "get_mass_scanners", "func": "greedybear.tasks.get_mass_scanners", - "cron": "7 1 * * 0", + "cron": _external_weekly_cron("get_mass_scanners"), }, - # WhatsMyIP: Weekly (Sunday) at 01:07 + # WhatsMyIP: Weekly (Sunday) at deterministic time outside 00:00-02:00 { "name": "get_whatsmyip", "func": "greedybear.tasks.get_whatsmyip", - "cron": "7 1 * * 0", + "cron": _external_weekly_cron("get_whatsmyip"), }, - # Firehol Lists: Weekly (Sunday) at 01:07 + # Firehol Lists: Weekly (Sunday) at deterministic time outside 00:00-02:00 { "name": "extract_firehol_lists", "func": "greedybear.tasks.extract_firehol_lists", - "cron": "7 1 * * 0", + "cron": _external_weekly_cron("extract_firehol_lists"), }, - # Tor Exit Nodes: Weekly (Sunday) at 01:07 + # Tor Exit Nodes: Weekly (Sunday) at deterministic time outside 00:00-02:00 { "name": "get_tor_exit_nodes", "func": "greedybear.tasks.get_tor_exit_nodes", - "cron": "7 1 * * 0", + "cron": _external_weekly_cron("get_tor_exit_nodes"), }, # 10. Reverse DNS Scanner Check: Daily at 06:07 { @@ -77,17 +90,17 @@ def setup_schedules(): "func": "greedybear.tasks.check_reverse_dns", "cron": "7 6 * * *", }, - # 11. ThreatFox Enrichment: Weekly (Sunday) at 01:07 + # 11. ThreatFox Enrichment: Weekly (Sunday) at deterministic time outside 00:00-02:00 { "name": "enrich_threatfox", "func": "greedybear.tasks.enrich_threatfox", - "cron": "7 1 * * 0", + "cron": _external_weekly_cron("enrich_threatfox"), }, - # 12. AbuseIPDB Enrichment: Weekly (Sunday) at 01:07 + # 12. AbuseIPDB Enrichment: Weekly (Sunday) at deterministic time outside 00:00-02:00 { "name": "enrich_abuseipdb", "func": "greedybear.tasks.enrich_abuseipdb", - "cron": "7 1 * * 0", + "cron": _external_weekly_cron("enrich_abuseipdb"), }, ] diff --git a/tests/greedybear/management/test_setup_schedules.py b/tests/greedybear/management/test_setup_schedules.py index 7430d049..78b5384d 100644 --- a/tests/greedybear/management/test_setup_schedules.py +++ b/tests/greedybear/management/test_setup_schedules.py @@ -55,6 +55,49 @@ def test_extraction_interval_5(self, mock_schedule): extract_call = next(c for c in calls if c[1]["name"] == "extract_all") self.assertEqual(extract_call[1]["defaults"]["cron"], "*/5 * * * *") + @patch("greedybear.cronjobs.schedules.Schedule") + @override_settings(EXTRACTION_INTERVAL=10, SECRET_KEY="test-secret") + def test_external_weekly_jobs_cron_are_deterministic_and_outside_local_window(self, mock_schedule): + """External weekly jobs run on Sunday at deterministic times outside 00:00-02:00.""" + mock_schedule.CRON = Schedule.CRON + mock_schedule.objects.update_or_create = MagicMock() + mock_schedule.objects.exclude = MagicMock(return_value=MagicMock()) + + call_command("setup_schedules") + first_calls = mock_schedule.objects.update_or_create.call_args_list + + mock_schedule.objects.update_or_create.reset_mock() + call_command("setup_schedules") + second_calls = mock_schedule.objects.update_or_create.call_args_list + + def get_cron(calls, name): + return next(c for c in calls if c[1]["name"] == name)[1]["defaults"]["cron"] + + for job_name in ( + "get_mass_scanners", + "get_whatsmyip", + "extract_firehol_lists", + "get_tor_exit_nodes", + "enrich_threatfox", + "enrich_abuseipdb", + ): + first_cron = get_cron(first_calls, job_name) + second_cron = get_cron(second_calls, job_name) + + self.assertEqual(first_cron, second_cron) + + minute_str, hour_str, day_of_month, month, day_of_week = first_cron.split() + hour = int(hour_str) + minute = int(minute_str) + + self.assertGreaterEqual(hour, 2) + self.assertLessEqual(hour, 23) + self.assertGreaterEqual(minute, 0) + self.assertLessEqual(minute, 59) + self.assertEqual(day_of_month, "*") + self.assertEqual(month, "*") + self.assertEqual(day_of_week, "0") + def test_orphan_schedules_are_deleted(self): """Test that orphaned schedules not in active_schedules list are deleted.""" # Create an orphan schedule that's not in the active_schedules list From 037f16b7db74d8b23dba4dc85c57c345654a6364 Mon Sep 17 00:00:00 2001 From: Deepanshu <144600350+Deepanshu1230@users.noreply.github.com> Date: Wed, 1 Apr 2026 18:10:48 +0530 Subject: [PATCH 07/27] fix: reject promise on requestPasswordReset failure and add test. Closes #1095 (#1136) * fix: reject promise on requestPasswordReset failure and add test * Update frontend/tests/components/auth/utils/EmailForm.test.jsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fixing the real Call issue * Mock addToast to isolate test environment --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- frontend/src/components/auth/api.js | 2 +- .../components/auth/utils/EmailForm.test.jsx | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/auth/api.js b/frontend/src/components/auth/api.js index b348b22f..7dacae04 100644 --- a/frontend/src/components/auth/api.js +++ b/frontend/src/components/auth/api.js @@ -51,7 +51,7 @@ export async function requestPasswordReset(body) { return resp; } catch (err) { addToast("Failed to send email!", err.parsedMsg, "danger", true); - return null; + return Promise.reject(err); } } diff --git a/frontend/tests/components/auth/utils/EmailForm.test.jsx b/frontend/tests/components/auth/utils/EmailForm.test.jsx index e2960e0e..598b9d98 100644 --- a/frontend/tests/components/auth/utils/EmailForm.test.jsx +++ b/frontend/tests/components/auth/utils/EmailForm.test.jsx @@ -4,9 +4,15 @@ import { render, screen, waitFor } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; import userEvent from "@testing-library/user-event"; import EmailForm from "../../../../src/components/auth/utils/EmailForm"; +import { requestPasswordReset } from "../../../../src/components/auth/api"; +import axios from "axios"; vi.mock("axios"); +vi.mock("@certego/certego-ui", () => ({ + addToast: vi.fn(), +})); + describe("EmailForm component", () => { test("Submit email form", async () => { // mock user interaction: reccomanded to put this at the start of the test @@ -74,4 +80,34 @@ describe("EmailForm component", () => { expect(submitButtonElement).not.toBeDisabled(); }); }); + + test("Does not trigger onFormSubmit and re-enables button when API request fails", async () => { + const user = userEvent.setup(); + + vi.mocked(axios.post).mockRejectedValueOnce(new Error("Network Error")); + const mockOnFormSubmit = vi.fn(); + + render( + + + , + ); + + const emailInputElement = screen.getByLabelText("Email Address"); + const submitButtonElement = screen.getByRole("button", { name: /Send/i }); + + await user.type(emailInputElement, "test@test.com"); + await user.click(submitButtonElement); + + expect(axios.post).toHaveBeenCalledTimes(1); + + await waitFor(() => { + expect(submitButtonElement).not.toBeDisabled(); + }); + + expect(mockOnFormSubmit).not.toHaveBeenCalled(); + }); }); From 2bc036708f92ed76bf1814b4a91fd47b68ea1b4c Mon Sep 17 00:00:00 2001 From: Krishna Awasthi <140143710+opbot-xd@users.noreply.github.com> Date: Thu, 2 Apr 2026 01:06:41 +0530 Subject: [PATCH 08/27] perf: cache ASN aggregation results with version-based invalidation. Closes #852 (#1062) Cache the heavy GROUP BY aggregation in asn_aggregated_queryset() using Django's cache framework, so identical API requests between extraction runs are served from cache instead of re-querying the database. How it works: - Cache key = hash of all query params + a version counter - After each extraction run, the pipeline bumps the version counter, which effectively invalidates all stale ASN cache entries - 24-hour TTL as safety net for cache expiry - Different param combinations (max_age, feed_type, etc.) each get their own cache entry, so correctness is preserved for all queries Also: - Add preload_cache param to ASRepository for test flexibility - Add cache behavior test validating hit/invalidation/miss cycle - Clear cache in test setUp to prevent cross-test pollution --- api/views/utils.py | 34 +++++++++++++--- greedybear/cronjobs/extraction/pipeline.py | 13 +++++++ tests/api/views/test_feeds_asn_view.py | 45 ++++++++++++++++++++++ 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/api/views/utils.py b/api/views/utils.py index 349e60e8..68cdf751 100644 --- a/api/views/utils.py +++ b/api/views/utils.py @@ -1,14 +1,16 @@ # This file is a part of GreedyBear https://github.com/honeynet/GreedyBear # See the file 'LICENSE' for copying permission. import csv +import hashlib import logging +import urllib.parse from datetime import datetime, timedelta import feedparser import requests from django.conf import settings from django.contrib.postgres.aggregates import ArrayAgg -from django.core.cache import cache +from django.core.cache import cache, caches from django.db.models import Count, F, Max, Min, Q, Sum, Value from django.db.models.functions import JSONObject from django.http import HttpResponse, HttpResponseBadRequest, StreamingHttpResponse @@ -446,15 +448,33 @@ def feeds_response(request=None, iocs=None, feed_params=None, valid_feed_types=N def asn_aggregated_queryset(iocs_qs, request, feed_params): """ - Perform DB-level aggregation grouped by ASN. + Retrieve ASN aggregation data. Caches the heavy aggregation query + since the data only updates during the extraction cronjob. Args iocs_qs (QuerySet): Filtered IOC queryset from get_queryset; request (Request): The API request object; feed_params (FeedRequestParams): Validated parameter object - Returns: A values-grouped queryset with annotated metrics and honeypot arrays. + Returns: A list of dicts with aggregated metrics and honeypot arrays per ASN. """ + + # Build reliable cache key from query params + sorted_params = sorted(request.query_params.lists()) + params_string = urllib.parse.urlencode(sorted_params, doseq=True) + param_hash = hashlib.sha256(params_string.encode("utf-8")).hexdigest() + + # To prevent per-worker continuous RAM bloat, use the shared DB-backed cache + # instead of the default LocMemCache, since the JSON response size can be large. + # The extraction pipeline invalidates this cache by bumping the version counter. + shared_cache = caches["django-q"] + version = shared_cache.get("asn_feeds_version", 1) + cache_key = f"asn_feeds_v{version}_{param_hash}" + + cached_result = shared_cache.get(cache_key) + if cached_result is not None: + return cached_result + asn_filter = request.query_params.get("asn") if asn_filter: iocs_qs = iocs_qs.filter(autonomous_system__asn=asn_filter) @@ -480,9 +500,11 @@ def asn_aggregated_queryset(iocs_qs, request, feed_params): first_seen=Min("first_seen"), last_seen=Max("last_seen"), ) - .order_by(ordering) ) + numeric_agg = numeric_agg.order_by(ordering) + # Honeypot names still require a lightweight aggregation because + # they depend on the active flag which can change independently. honeypot_agg = ( iocs_qs.exclude(autonomous_system__isnull=True) .filter(general_honeypot__active=True) @@ -497,7 +519,6 @@ def asn_aggregated_queryset(iocs_qs, request, feed_params): hp_lookup = {row["asn"]: row["honeypots"] or [] for row in honeypot_agg} - # merging numeric aggregate with honeypot names for each asn result = [] for row in numeric_agg: asn = row["asn"] @@ -505,6 +526,9 @@ def asn_aggregated_queryset(iocs_qs, request, feed_params): row_dict["honeypots"] = sorted(hp_lookup.get(asn, [])) result.append(row_dict) + # Set cache with a 60-minute timeout (max extraction interval length) to prevent memory bloat + shared_cache.set(cache_key, result, timeout=3600) + return result diff --git a/greedybear/cronjobs/extraction/pipeline.py b/greedybear/cronjobs/extraction/pipeline.py index 8f50e4f3..f434bd83 100644 --- a/greedybear/cronjobs/extraction/pipeline.py +++ b/greedybear/cronjobs/extraction/pipeline.py @@ -1,6 +1,8 @@ import logging from collections import defaultdict +from django.core.cache import caches + from greedybear.cronjobs.extraction.strategies.factory import ExtractionStrategyFactory from greedybear.cronjobs.repositories import ( ElasticRepository, @@ -104,4 +106,15 @@ def execute(self) -> int: UpdateScores().score_only(ioc_records) ioc_record_count += len(ioc_records) + # 5. Invalidate API caches only if any IOC records were processed + if ioc_record_count > 0: + # Use the shared DB-backed cache so the version bump is visible to + # gunicorn API workers (LocMemCache is per-process). + self.log.info("Invalidating feeds ASN cache") + shared_cache = caches["django-q"] + try: + shared_cache.incr("asn_feeds_version") + except ValueError: + shared_cache.set("asn_feeds_version", 2, timeout=None) + return ioc_record_count diff --git a/tests/api/views/test_feeds_asn_view.py b/tests/api/views/test_feeds_asn_view.py index e6eace5c..c3624da5 100644 --- a/tests/api/views/test_feeds_asn_view.py +++ b/tests/api/views/test_feeds_asn_view.py @@ -1,3 +1,4 @@ +from django.core.cache import cache, caches from django.utils import timezone from rest_framework.test import APIClient @@ -63,6 +64,8 @@ def setUpClass(cls): def setUp(self): super().setUp() + cache.clear() + caches["django-q"].clear() self.client = APIClient() self.client.force_authenticate(user=self.superuser) self.url = "/api/feeds/asn/" @@ -221,3 +224,45 @@ def test_asn_feed_with_empty_as_name(self): # Restore original name self.as_low.name = original_name self.as_low.save(update_fields=["name"]) + + def test_asn_feed_caching_behavior(self): + """ + Verify that identical requests to the ASN feed return cached results, + and that invalidating the 'asn_feeds_version' forces a re-computation. + """ + # 1. First request computes and caches the result + response1 = self.client.get(self.url) + self.assertEqual(response1.status_code, 200) + results1 = self._get_results(response1) + high_item1 = next((item for item in results1 if str(item["asn"]) == self.high_asn), None) + self.assertIsNotNone(high_item1) + + # 2. Modify DB (e.g. change an IOC's attack count) + original_attack_count = self.ioc_high1.attack_count + self.ioc_high1.attack_count += 100 + self.ioc_high1.save(update_fields=["attack_count"]) + + # 3. Second request should hit the cache and return SAME old value + response2 = self.client.get(self.url) + results2 = self._get_results(response2) + high_item2 = next((item for item in results2 if str(item["asn"]) == self.high_asn), None) + + self.assertEqual(high_item1["total_attack_count"], high_item2["total_attack_count"]) + + # 4. Invalidate the cache (simulate extraction cronjob behavior) + shared_cache = caches["django-q"] + try: + shared_cache.incr("asn_feeds_version") + except ValueError: + shared_cache.set("asn_feeds_version", 2, timeout=None) + + # 5. Third request should re-compute and show UPDATED DB value + response3 = self.client.get(self.url) + results3 = self._get_results(response3) + high_item3 = next((item for item in results3 if str(item["asn"]) == self.high_asn), None) + + self.assertEqual(high_item3["total_attack_count"], high_item1["total_attack_count"] + 100) + + # Cleanup DB + self.ioc_high1.attack_count = original_attack_count + self.ioc_high1.save(update_fields=["attack_count"]) From a798c90cc413797eab945b7e26ac4da20e3bced7 Mon Sep 17 00:00:00 2001 From: Dorna Raj Gyawali Date: Thu, 2 Apr 2026 01:32:12 +0545 Subject: [PATCH 09/27] fix: replace hardcoded GID 82 with www-data group name . Closes #1162 (#1168) * fix: replace hardcoded GID 82 with www-data group name * removed usermod Signed-off-by: Drona Raj Gyawali --------- Signed-off-by: Drona Raj Gyawali --- docker/Dockerfile | 1 - docker/default.yml | 2 +- docker/entrypoint_gunicorn.sh | 5 +++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 36775739..54d202b0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -60,7 +60,6 @@ RUN mkdir -p ${LOG_PATH}/django ${LOG_PATH}/gunicorn \ && touch ${LOG_PATH}/django/django_errors.log ${LOG_PATH}/django/elasticsearch.log \ && touch ${LOG_PATH}/django/authentication.log ${LOG_PATH}/django/authentication_errors.log \ && mkdir -p ${APP_ROOT}/mlmodels \ - && usermod -u 2000 www-data \ && chown -R www-data:www-data ${LOG_PATH} /opt/deploy/ ${APP_ROOT}/mlmodels/ \ && rm -rf frontend/ diff --git a/docker/default.yml b/docker/default.yml index c7c2600d..7a4fd0d7 100644 --- a/docker/default.yml +++ b/docker/default.yml @@ -82,7 +82,7 @@ services: condition: service_healthy app: condition: service_healthy - user: "2000:82" + user: "www-data:www-data" healthcheck: disable: true diff --git a/docker/entrypoint_gunicorn.sh b/docker/entrypoint_gunicorn.sh index 1d71e4c4..3de924bd 100755 --- a/docker/entrypoint_gunicorn.sh +++ b/docker/entrypoint_gunicorn.sh @@ -20,8 +20,9 @@ python manage.py collectstatic --noinput --clear --verbosity 0 mkdir -p /var/log/greedybear/gunicorn mkdir -p /run/gunicorn -# Fix log file ownership (manage.py commands above run as root and may create new log files) -chown -R 2000:82 /var/log/greedybear /run/gunicorn +# Fix log file ownership (manage.py commands above run as root +# and may create new log files owned by root instead of www-data) +chown -R www-data:www-data /var/log/greedybear /run/gunicorn # Obtain the current GreedyBear version number GREEDYBEAR_VERSION=$(uv version --short) From 2489f79e2840ed6cb7ae28d63e8abb4bed1e88b2 Mon Sep 17 00:00:00 2001 From: SHUBHAM CHAUHAN <119105844+Demiserular@users.noreply.github.com> Date: Thu, 2 Apr 2026 01:32:08 +0530 Subject: [PATCH 10/27] Fix/search help text Closes #1161 (#1172) * fix: use string instead of list for search_help_text (#1161) * fix: correct misleading search_help_text values in MassScanner and IOC admins --- greedybear/admin.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/greedybear/admin.py b/greedybear/admin.py index 2736b1d8..a45f79ea 100644 --- a/greedybear/admin.py +++ b/greedybear/admin.py @@ -27,7 +27,7 @@ class TorExitNodeModelAdmin(admin.ModelAdmin): list_display = ["ip_address", "added", "reason"] search_fields = ["ip_address"] - search_help_text = ["search for the IP address"] + search_help_text = "search for the IP address" @admin.register(Sensor) @@ -35,7 +35,7 @@ class SensorsModelAdmin(admin.ModelAdmin): list_display = ["id", "address", "country", "label"] list_editable = ["label"] search_fields = ["address", "label"] - search_help_text = ["search for the sensor IP address or label"] + search_help_text = "search for the sensor IP address or label" @admin.register(Statistics) @@ -43,14 +43,14 @@ class StatisticsModelAdmin(admin.ModelAdmin): list_display = ["source", "view", "request_date"] list_filter = ["source"] search_fields = ["source"] - search_help_text = ["search for the IP address source"] + search_help_text = "search for the IP address source" @admin.register(WhatsMyIPDomain) class WhatsMyIPModelAdmin(admin.ModelAdmin): list_display = ["domain", "added"] search_fields = ["domain"] - search_help_text = ["search for the domain"] + search_help_text = "search for the domain" @admin.register(MassScanner) @@ -58,7 +58,7 @@ class MassScannersModelAdmin(admin.ModelAdmin): list_display = ["ip_address", "added", "reason"] list_filter = ["reason"] search_fields = ["ip_address"] - search_help_text = ["search for the IP address source"] + search_help_text = "search for the IP address" @admin.register(FireHolList) @@ -66,7 +66,7 @@ class FireHolListModelAdmin(admin.ModelAdmin): list_display = ["ip_address", "added", "source"] list_filter = ["source"] search_fields = ["ip_address"] - search_help_text = ["search for the IP address"] + search_help_text = "search for the IP address" class SessionInline(admin.TabularInline): @@ -101,7 +101,7 @@ class CowrieSessionModelAdmin(admin.ModelAdmin): "source", ] search_fields = ["source__name"] - search_help_text = ["search for the IP address source"] + search_help_text = "search for the IP address source" raw_id_fields = ["source", "commands"] list_filter = ["login_attempt", "command_execution"] @@ -113,7 +113,7 @@ def credential_list(self, session): class CredentialModelAdmin(admin.ModelAdmin): list_display = ["username", "password"] search_fields = ["username", "password"] - search_help_text = ["search for username or password"] + search_help_text = "search for username or password" @admin.register(CommandSequence) @@ -154,7 +154,7 @@ class IOCModelAdmin(admin.ModelAdmin): "autonomous_system", ] search_fields = ["name", "related_ioc__name"] - search_help_text = ["search for the IP address source"] + search_help_text = "search by IOC name or related IOC name" raw_id_fields = ["related_ioc"] filter_horizontal = ["general_honeypot", "sensors"] inlines = [SessionInline] From ede95fcac15aa7b26012937f98e2144e7703f442 Mon Sep 17 00:00:00 2001 From: Manik Date: Thu, 2 Apr 2026 11:17:30 +0530 Subject: [PATCH 11/27] feat: extract attacker country code in IOC model. Closes #1160 (#1173) * add attacker_country_code field to IOC model * extract country_iso_code from geoip data during IOC creation * update _merge_iocs to refresh attacker_country_code on re-extraction * add tests for attacker_country_code extraction and merge * validate country code length before saving to prevent DataError --- .../cronjobs/extraction/ioc_processor.py | 2 + greedybear/cronjobs/extraction/utils.py | 3 + .../0048_add_attacker_country_code.py | 18 ++++++ greedybear/models.py | 5 ++ tests/__init__.py | 4 ++ tests/test_extraction_utils.py | 56 +++++++++++++++++++ tests/test_ioc_processor.py | 24 ++++++++ 7 files changed, 112 insertions(+) create mode 100644 greedybear/migrations/0048_add_attacker_country_code.py diff --git a/greedybear/cronjobs/extraction/ioc_processor.py b/greedybear/cronjobs/extraction/ioc_processor.py index fd79c721..8919a870 100644 --- a/greedybear/cronjobs/extraction/ioc_processor.py +++ b/greedybear/cronjobs/extraction/ioc_processor.py @@ -110,6 +110,8 @@ def _merge_iocs(self, existing: IOC, new: IOC) -> IOC: # we will always update attacker_country if incoming value exists if new.attacker_country: existing.attacker_country = new.attacker_country + if new.attacker_country_code and len(new.attacker_country_code) == 2: + existing.attacker_country_code = new.attacker_country_code # Add sensors from new IOC (existing is already saved, so ManyToMany works). # We retrieve sensors from the temporary attribute of the input IOC object. diff --git a/greedybear/cronjobs/extraction/utils.py b/greedybear/cronjobs/extraction/utils.py index efaf16c9..16023f3d 100644 --- a/greedybear/cronjobs/extraction/utils.py +++ b/greedybear/cronjobs/extraction/utils.py @@ -176,6 +176,8 @@ def iocs_from_hits(hits: list[dict]) -> list[IOC]: geoip = hits[0].get("geoip", {}) if hits else {} attacker_country = geoip.get("country_name", "") + raw_country_code = geoip.get("country_iso_code", "") + attacker_country_code = raw_country_code if len(raw_country_code) == 2 else "" asn = geoip.get("asn") as_name = geoip.get("as_org", "") @@ -191,6 +193,7 @@ def iocs_from_hits(hits: list[dict]) -> list[IOC]: login_attempts=login_attempts, firehol_categories=firehol_categories, attacker_country=attacker_country, + attacker_country_code=attacker_country_code, ) # Attach sensors to temporary attribute for later processing. # We cannot use `ioc.sensors.add()` here because the IOC instance is not yet saved diff --git a/greedybear/migrations/0048_add_attacker_country_code.py b/greedybear/migrations/0048_add_attacker_country_code.py new file mode 100644 index 00000000..e9b5f899 --- /dev/null +++ b/greedybear/migrations/0048_add_attacker_country_code.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.12 on 2026-04-01 19:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("greedybear", "0047_credential_sources"), + ] + + operations = [ + migrations.AddField( + model_name="ioc", + name="attacker_country_code", + field=models.CharField(blank=True, default="", max_length=2), + ), + ] diff --git a/greedybear/models.py b/greedybear/models.py index 9644e700..4b33c99f 100644 --- a/greedybear/models.py +++ b/greedybear/models.py @@ -84,6 +84,11 @@ class IOC(models.Model): blank=True, default="", ) + attacker_country_code = models.CharField( + max_length=2, + blank=True, + default="", + ) autonomous_system = models.ForeignKey( AutonomousSystem, on_delete=models.SET_NULL, diff --git a/tests/__init__.py b/tests/__init__.py index 886a055e..684195b1 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -229,6 +229,8 @@ def _create_mock_ioc( ip_reputation="", asn=1234, firehol_categories=None, + attacker_country="", + attacker_country_code="", ): mock = Mock(spec=IOC) mock.name = name @@ -246,6 +248,8 @@ def _create_mock_ioc( mock.ip_reputation = ip_reputation mock.firehol_categories = firehol_categories if firehol_categories is not None else [] mock.number_of_days_seen = len(mock.days_seen) + mock.attacker_country = attacker_country + mock.attacker_country_code = attacker_country_code if asn is not None: mock.autonomous_system = Mock() diff --git a/tests/test_extraction_utils.py b/tests/test_extraction_utils.py index 8ca146e1..9b60eaa6 100644 --- a/tests/test_extraction_utils.py +++ b/tests/test_extraction_utils.py @@ -668,6 +668,62 @@ def test_ioc_attacker_country_set_correctly(self): self.assertEqual(ioc.interaction_count, 1) + def test_ioc_attacker_country_code_set_correctly(self): + """Verify that iocs_from_hits extracts country_iso_code from geoip.""" + hits = [ + self._create_hit( + src_ip="8.8.8.8", + dest_port=22, + hit_type="Cowrie", + ) + ] + + hits[0]["geoip"] = {"country_name": "Nepal", "country_iso_code": "NP"} + + iocs = iocs_from_hits(hits) + self.assertEqual(len(iocs), 1) + + ioc = iocs[0] + self.assertEqual(ioc.attacker_country, "Nepal") + self.assertEqual(ioc.attacker_country_code, "NP") + + def test_ioc_attacker_country_code_defaults_to_empty(self): + """Verify that attacker_country_code defaults to empty when geoip has no country_iso_code.""" + hits = [ + self._create_hit( + src_ip="8.8.8.8", + dest_port=22, + hit_type="Cowrie", + ) + ] + + hits[0]["geoip"] = {"country_name": "Nepal"} + + iocs = iocs_from_hits(hits) + self.assertEqual(len(iocs), 1) + + ioc = iocs[0] + self.assertEqual(ioc.attacker_country, "Nepal") + self.assertEqual(ioc.attacker_country_code, "") + + def test_ioc_attacker_country_code_rejects_invalid_length(self): + """Verify that country codes longer than 2 chars are discarded.""" + hits = [ + self._create_hit( + src_ip="8.8.8.8", + dest_port=22, + hit_type="Cowrie", + ) + ] + + hits[0]["geoip"] = {"country_name": "Nepal", "country_iso_code": "NPL"} + + iocs = iocs_from_hits(hits) + self.assertEqual(len(iocs), 1) + + ioc = iocs[0] + self.assertEqual(ioc.attacker_country_code, "") + def test_ioc_autonomous_system_set_correctly(self): """Verify that iocs_from_hits sets autonomous_system FK correctly from hits.""" diff --git a/tests/test_ioc_processor.py b/tests/test_ioc_processor.py index 2dbbe688..79e97218 100644 --- a/tests/test_ioc_processor.py +++ b/tests/test_ioc_processor.py @@ -322,6 +322,30 @@ def test_clears_stale_firehol_categories(self): self.assertEqual(result.firehol_categories, []) + def test_updates_attacker_country_code(self): + existing = self._create_mock_ioc(attacker_country_code="") + new = self._create_mock_ioc(attacker_country_code="NP") + + result = self.processor._merge_iocs(existing, new) + + self.assertEqual(result.attacker_country_code, "NP") + + def test_preserves_attacker_country_code_when_new_is_empty(self): + existing = self._create_mock_ioc(attacker_country_code="US") + new = self._create_mock_ioc(attacker_country_code="") + + result = self.processor._merge_iocs(existing, new) + + self.assertEqual(result.attacker_country_code, "US") + + def test_rejects_invalid_length_attacker_country_code(self): + existing = self._create_mock_ioc(attacker_country_code="US") + new = self._create_mock_ioc(attacker_country_code="NPL") + + result = self.processor._merge_iocs(existing, new) + + self.assertEqual(result.attacker_country_code, "US") + class TestUpdateDaysSeen(ExtractionTestCase): def setUp(self): From df13e8e37fdd3eab88bda7639fd88d9e1cedd229 Mon Sep 17 00:00:00 2001 From: tEmhItHoRpHe Date: Thu, 2 Apr 2026 07:34:04 +0100 Subject: [PATCH 12/27] Refactor: Rename usage of GeneralHoneypot to Honeypot across the codebase. Closes #774 (#1070) * chore: ignore uv files * Refactor: rename GeneralHoneypot model to Honeypot. * chore: added local dev dependencies * chore(migration): generalhoneypot to honeypot * fix(django field collision): Standardize honeypot name aggregation to `honeypot_names`. * fix(tests): revert changes to migration tests that reference old model names. * Update honeypot name handling in IOC Refactor honeypot name normalization and check logic. * chore(lint-fix): replace GeneralHoneypot usage to Honeypot. * Refactor: rename GeneralHoneypot model to Honeypot and update related field and parameter names across the codebase. * chore(build-fix): resolved conflicting migrations. * chore(build-fix): resolved conflicting migrations & rename stale references. * chore: remove unrelated changes. * chore: remove unrelated uv lockfile * chore: revert unrelated changes * chore(build-fix): add Sensor model to serializers and rename honeypot cache test for clarity. * rename migration --------- Co-authored-by: tim <46972822+regulartim@users.noreply.github.com> --- api/serializers.py | 8 +- api/views/__init__.py | 2 +- api/views/health.py | 6 +- .../{general_honeypot.py => honeypots.py} | 14 +- api/views/statistics.py | 14 +- api/views/utils.py | 30 +-- greedybear/admin.py | 16 +- .../cronjobs/extraction/ioc_processor.py | 8 +- .../cronjobs/extraction/strategies/cowrie.py | 6 +- .../cronjobs/extraction/strategies/generic.py | 2 +- .../extraction/strategies/heralding.py | 2 +- .../cronjobs/extraction/strategies/tanner.py | 4 +- greedybear/cronjobs/extraction/utils.py | 2 +- greedybear/cronjobs/repositories/ioc.py | 40 ++-- ...ename_generalhoneypot_honeypot_and_more.py | 30 +++ greedybear/models.py | 6 +- tests/__init__.py | 36 ++-- tests/api/test_feed_types.py | 12 +- tests/api/views/test_feeds_advanced_view.py | 2 +- tests/api/views/test_feeds_asn_view.py | 14 +- tests/api/views/test_feeds_view.py | 2 +- tests/api/views/test_general_honeypot_view.py | 10 +- tests/api/views/test_health.py | 12 +- tests/api/views/test_statistics_view.py | 8 +- tests/test_cowrie_extraction.py | 8 +- tests/test_extraction_strategies.py | 6 +- tests/test_extraction_utils.py | 6 +- tests/test_heralding_strategy.py | 2 +- tests/test_ioc_processor.py | 8 +- tests/test_ioc_repository.py | 198 +++++++++--------- tests/test_models.py | 10 +- tests/test_serializers.py | 10 +- tests/test_tanner_strategy.py | 4 +- 33 files changed, 284 insertions(+), 254 deletions(-) rename api/views/{general_honeypot.py => honeypots.py} (62%) create mode 100644 greedybear/migrations/0049_rename_generalhoneypot_honeypot_and_more.py diff --git a/api/serializers.py b/api/serializers.py index aba8d0b1..f11298b6 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -6,15 +6,15 @@ from rest_framework import serializers from greedybear.consts import REGEX_DOMAIN -from greedybear.models import IOC, GeneralHoneypot, Sensor, Tag +from greedybear.models import IOC, Honeypot, Sensor, Tag from greedybear.utils import is_ip_address logger = logging.getLogger(__name__) -class GeneralHoneypotSerializer(serializers.ModelSerializer): +class HoneypotSerializer(serializers.ModelSerializer): class Meta: - model = GeneralHoneypot + model = Honeypot def to_representation(self, value): return value.name @@ -33,7 +33,7 @@ class Meta: class IOCSerializer(serializers.ModelSerializer): - general_honeypot = GeneralHoneypotSerializer(many=True, read_only=True) + general_honeypot = HoneypotSerializer(many=True, read_only=True, source="honeypots") tags = TagSerializer(many=True, read_only=True) sensors = SensorSerializer(many=True, read_only=True) diff --git a/api/views/__init__.py b/api/views/__init__.py index ebc2b1f2..398982a0 100644 --- a/api/views/__init__.py +++ b/api/views/__init__.py @@ -2,7 +2,7 @@ from api.views.cowrie_session import * from api.views.enrichment import * from api.views.feeds import * -from api.views.general_honeypot import * from api.views.health import * +from api.views.honeypots import * from api.views.news import * from api.views.statistics import * diff --git a/api/views/health.py b/api/views/health.py index ac3f2533..e6ef70c0 100644 --- a/api/views/health.py +++ b/api/views/health.py @@ -15,7 +15,7 @@ IOC, CowrieSession, FireHolList, - GeneralHoneypot, + Honeypot, MassScanner, TorExitNode, ) @@ -71,8 +71,8 @@ def get_observables_overview(last_24h): ) honeypot_stats = { - "total": GeneralHoneypot.objects.count(), - "active": GeneralHoneypot.objects.filter(active=True).count(), + "total": Honeypot.objects.count(), + "active": Honeypot.objects.filter(active=True).count(), } threat_list_stats = { diff --git a/api/views/general_honeypot.py b/api/views/honeypots.py similarity index 62% rename from api/views/general_honeypot.py rename to api/views/honeypots.py index 7679eb04..ea2d5148 100644 --- a/api/views/general_honeypot.py +++ b/api/views/honeypots.py @@ -6,7 +6,7 @@ from rest_framework.response import Response from greedybear.consts import GET -from greedybear.models import GeneralHoneypot +from greedybear.models import Honeypot logger = logging.getLogger(__name__) @@ -23,14 +23,14 @@ def general_honeypot_list(request): Response: A JSON response containing the list of general honeypots. """ - logger.info(f"Requested general honeypots list from {request.user}.") + logger.info(f"Requested honeypots list from {request.user}.") active = request.query_params.get("onlyActive") honeypots = [] - general_honeypots = GeneralHoneypot.objects.all() + honeypot_objs = Honeypot.objects.all() if active == "true": - general_honeypots = general_honeypots.filter(active=True) - logger.info(f"Requested only active general honeypots from {request.user}") - honeypots.extend([hp.name for hp in general_honeypots]) + honeypot_objs = honeypot_objs.filter(active=True) + logger.info(f"Requested only active honeypots from {request.user}") + honeypots.extend([hp.name for hp in honeypot_objs]) - logger.info(f"General honeypots: {honeypots} given back to user {request.user}") + logger.info(f"Honeypots: {honeypots} given back to user {request.user}") return Response(honeypots) diff --git a/api/views/statistics.py b/api/views/statistics.py index adea971b..d4bd1f75 100644 --- a/api/views/statistics.py +++ b/api/views/statistics.py @@ -10,7 +10,7 @@ from rest_framework.decorators import action from rest_framework.response import Response -from greedybear.models import IOC, GeneralHoneypot, Statistics, ViewType +from greedybear.models import IOC, Honeypot, Statistics, ViewType logger = logging.getLogger(__name__) @@ -92,7 +92,7 @@ def countries(self, request): qs = ( IOC.objects.filter(last_seen__gte=delta) .exclude(attacker_country="") - .filter(general_honeypot__active=True) + .filter(honeypots__active=True) .values("attacker_country") .annotate(count=Count("id", distinct=True)) .order_by("-count") @@ -103,7 +103,7 @@ def countries(self, request): @action(detail=False, methods=["get"]) def feeds_types(self, request): """ - Retrieve statistics for different types of feeds using GeneralHoneypot M2M relationship. + Retrieve statistics for different types of feeds using Honeypot M2M relationship. Args: request: The incoming request object. @@ -113,10 +113,10 @@ def feeds_types(self, request): """ # Build annotations for each active general honeypot annotations = {} - general_honeypots = GeneralHoneypot.objects.all().filter(active=True) - for hp in general_honeypots: + honeypots = Honeypot.objects.all().filter(active=True) + for hp in honeypots: # Use M2M relationship instead of boolean fields - annotations[hp.name] = Count("name", distinct=True, filter=Q(general_honeypot__name__iexact=hp.name)) + annotations[hp.name] = Count("name", distinct=True, filter=Q(honeypots__name__iexact=hp.name)) return self.__aggregation_response_static_ioc(annotations) def __aggregation_response_static_statistics(self, annotations: dict) -> Response: @@ -147,7 +147,7 @@ def __aggregation_response_static_ioc(self, annotations: dict) -> Response: qs = ( IOC.objects.filter(last_seen__gte=delta) - .exclude(general_honeypot__active=False) + .exclude(honeypots__active=False) .annotate(date=Trunc("last_seen", basis)) .values("date") .annotate(**annotations) diff --git a/api/views/utils.py b/api/views/utils.py index 68cdf751..109d515c 100644 --- a/api/views/utils.py +++ b/api/views/utils.py @@ -21,7 +21,7 @@ from api.serializers import FeedsRequestSerializer, parse_feed_types from greedybear.consts import CACHE_KEY_GREEDYBEAR_NEWS, CACHE_TIMEOUT_SECONDS, RSS_FEED_URL from greedybear.enums import IpReputation -from greedybear.models import IOC, GeneralHoneypot, Statistics +from greedybear.models import IOC, Honeypot, Statistics from greedybear.utils import is_ip_address, is_valid_domain logger = logging.getLogger(__name__) @@ -146,8 +146,8 @@ def get_valid_feed_types() -> frozenset[str]: Returns: frozenset[str]: An immutable set of valid feed type strings """ - general_honeypots = GeneralHoneypot.objects.filter(active=True) - feed_types = ["all"] + [hp.name.lower() for hp in general_honeypots] + honeypots = Honeypot.objects.filter(active=True) + feed_types = ["all"] + [hp.name.lower() for hp in honeypots] return frozenset(feed_types) @@ -228,13 +228,13 @@ def get_queryset(request, feed_params, valid_feed_types, is_aggregated=False, se if "all" not in feed_params.feed_types: type_filter = Q() for ft in feed_params.feed_types: - type_filter |= Q(general_honeypot__name__iexact=ft) + type_filter |= Q(honeypots__name__iexact=ft) iocs = iocs.filter(type_filter) # aggregated feeds calculate metrics differently and need all rows to be accurate. if not is_aggregated: - iocs = iocs.filter(general_honeypot__active=True) - iocs = iocs.annotate(honeypots=ArrayAgg("general_honeypot__name", distinct=True)) + iocs = iocs.filter(honeypots__active=True) + iocs = iocs.annotate(honeypot_names=ArrayAgg("honeypots__name", distinct=True)) # Only annotate tags metadata when the response format needs it (e.g. JSON), # to avoid unnecessary joins and aggregation work for txt/csv feeds. if getattr(feed_params, "format", "").lower() == "json": @@ -317,7 +317,7 @@ def feeds_response(request=None, iocs=None, feed_params=None, valid_feed_types=N "login_attempts", "recurrence_probability", "expected_interactions", - "honeypots", # used to build feed_type; removed from response + "honeypot_names", # used to build feed_type; removed from response "destination_ports", # used to calculate destination_port_count "attacker_country", "autonomous_system", @@ -346,7 +346,7 @@ def feeds_response(request=None, iocs=None, feed_params=None, valid_feed_types=N else: iocs_iter = iocs.values(*required_fields).iterator(chunk_size=2000) for ioc in iocs_iter: - ioc_feed_type = [hp.lower() for hp in ioc.get("honeypots", []) if hp] + ioc_feed_type = [hp.lower() for hp in ioc.get("honeypot_names", []) if hp] data_ = ioc | { "first_seen": ioc["first_seen"].strftime("%Y-%m-%d"), @@ -360,7 +360,7 @@ def feeds_response(request=None, iocs=None, feed_params=None, valid_feed_types=N if not verbose: data_.pop("destination_ports", None) data_.pop("autonomous_system", None) - data_.pop("honeypots", None) + data_.pop("honeypot_names", None) data_.pop("id", None) json_list.append(data_) @@ -388,7 +388,7 @@ def feeds_response(request=None, iocs=None, feed_params=None, valid_feed_types=N "first_seen", "last_seen", "recurrence_probability", - "honeypots", + "honeypot_names", "ip_reputation", } # Fetch fields from database @@ -418,7 +418,7 @@ def feeds_response(request=None, iocs=None, feed_params=None, valid_feed_types=N confidence = 90 # Labels - labels = [hp.lower() for hp in ioc.get("honeypots", []) if hp] + labels = [hp.lower() for hp in ioc.get("honeypot_names", []) if hp] if ioc.get("ip_reputation"): labels.append(ioc["ip_reputation"]) @@ -507,17 +507,17 @@ def asn_aggregated_queryset(iocs_qs, request, feed_params): # they depend on the active flag which can change independently. honeypot_agg = ( iocs_qs.exclude(autonomous_system__isnull=True) - .filter(general_honeypot__active=True) + .filter(honeypots__active=True) .values(asn=F("autonomous_system__asn")) .annotate( - honeypots=ArrayAgg( - "general_honeypot__name", + honeypot_names=ArrayAgg( + "honeypots__name", distinct=True, ) ) ) - hp_lookup = {row["asn"]: row["honeypots"] or [] for row in honeypot_agg} + hp_lookup = {row["asn"]: row["honeypot_names"] or [] for row in honeypot_agg} result = [] for row in numeric_agg: diff --git a/greedybear/admin.py b/greedybear/admin.py index a45f79ea..3372630f 100644 --- a/greedybear/admin.py +++ b/greedybear/admin.py @@ -12,7 +12,7 @@ CowrieSession, Credential, FireHolList, - GeneralHoneypot, + Honeypot, MassScanner, Sensor, Statistics, @@ -138,7 +138,7 @@ class IOCModelAdmin(admin.ModelAdmin): "related_urls", "scanner", "payload_request", - "general_honeypots", + "honeypots_list", "sensor_list", "ip_reputation", "firehol_categories", @@ -156,11 +156,11 @@ class IOCModelAdmin(admin.ModelAdmin): search_fields = ["name", "related_ioc__name"] search_help_text = "search by IOC name or related IOC name" raw_id_fields = ["related_ioc"] - filter_horizontal = ["general_honeypot", "sensors"] + filter_horizontal = ["honeypots", "sensors"] inlines = [SessionInline] - def general_honeypots(self, ioc): - return ", ".join([str(element) for element in ioc.general_honeypot.all()]) + def honeypots_list(self, ioc): + return ", ".join([str(element) for element in ioc.honeypots.all()]) def sensor_list(self, ioc): return ", ".join([str(sensor.address) for sensor in ioc.sensors.all()]) @@ -180,11 +180,11 @@ def autonomous_system_display(self, ioc): def get_queryset(self, request): """Override to optimize queries and avoid N+1 problems.""" - return super().get_queryset(request).select_related("autonomous_system").prefetch_related("sensors", "general_honeypot") + return super().get_queryset(request).select_related("autonomous_system").prefetch_related("sensors", "honeypots") -@admin.register(GeneralHoneypot) -class GeneralHoneypotAdmin(admin.ModelAdmin): +@admin.register(Honeypot) +class HoneypotAdmin(admin.ModelAdmin): list_display = [ "name", "active", diff --git a/greedybear/cronjobs/extraction/ioc_processor.py b/greedybear/cronjobs/extraction/ioc_processor.py index 8919a870..c2d80d9f 100644 --- a/greedybear/cronjobs/extraction/ioc_processor.py +++ b/greedybear/cronjobs/extraction/ioc_processor.py @@ -31,7 +31,7 @@ def add_ioc( self, ioc: IOC, attack_type: str, - general_honeypot_name: str = None, + honeypot_name: str = None, ) -> IOC | None: """ Process an IOC record. @@ -42,7 +42,7 @@ def add_ioc( Args: ioc: IOC instance to process. attack_type: Type of attack (SCANNER or PAYLOAD_REQUEST). - general_honeypot_name: Optional honeypot name to associate with the IOC. + honeypot_name: Optional honeypot name to associate with the IOC. Returns: The persisted IOC record, or None if filtered out. @@ -69,8 +69,8 @@ def add_ioc( self.log.debug(f"{ioc} is already known - updating record") ioc_record = self._merge_iocs(ioc_record, ioc) - if general_honeypot_name is not None: - ioc_record = self.ioc_repo.add_honeypot_to_ioc(general_honeypot_name, ioc_record) + if honeypot_name is not None: + ioc_record = self.ioc_repo.add_honeypot_to_ioc(honeypot_name, ioc_record) ioc_record = self._update_days_seen(ioc_record) ioc_record.scanner = ioc_record.scanner or (attack_type == SCANNER) diff --git a/greedybear/cronjobs/extraction/strategies/cowrie.py b/greedybear/cronjobs/extraction/strategies/cowrie.py index aa7b043b..e168e04f 100644 --- a/greedybear/cronjobs/extraction/strategies/cowrie.py +++ b/greedybear/cronjobs/extraction/strategies/cowrie.py @@ -95,7 +95,7 @@ def _get_scanners(self, hits: list[dict]) -> None: """Extract scanner IPs and sessions.""" for ioc in iocs_from_hits(hits): self.log.info(f"found IP {ioc.name} by honeypot cowrie") - ioc_record = self.ioc_processor.add_ioc(ioc, attack_type=SCANNER, general_honeypot_name="Cowrie") + ioc_record = self.ioc_processor.add_ioc(ioc, attack_type=SCANNER, honeypot_name="Cowrie") if ioc_record: self.ioc_records.append(ioc_record) threatfox_submission(ioc_record, ioc.related_urls, self.log) @@ -142,7 +142,7 @@ def _extract_possible_payload_in_messages(self, hits: list[dict]) -> None: sensor = hit.get("_sensor") if sensor: ioc._sensors_to_add = [sensor] - self.ioc_processor.add_ioc(ioc, attack_type=PAYLOAD_REQUEST, general_honeypot_name="Cowrie") + self.ioc_processor.add_ioc(ioc, attack_type=PAYLOAD_REQUEST, honeypot_name="Cowrie") self._add_fks(scanner_ip, payload_hostname) self.payloads_in_message += 1 @@ -182,7 +182,7 @@ def _get_url_downloads(self, hits: list[dict]) -> None: sensor = hit.get("_sensor") if sensor: ioc._sensors_to_add = [sensor] - ioc_record = self.ioc_processor.add_ioc(ioc, attack_type=PAYLOAD_REQUEST, general_honeypot_name="Cowrie") + ioc_record = self.ioc_processor.add_ioc(ioc, attack_type=PAYLOAD_REQUEST, honeypot_name="Cowrie") if ioc_record: self.added_url_downloads += 1 threatfox_submission(ioc_record, ioc.related_urls, self.log) diff --git a/greedybear/cronjobs/extraction/strategies/generic.py b/greedybear/cronjobs/extraction/strategies/generic.py index f4653e1f..81048317 100644 --- a/greedybear/cronjobs/extraction/strategies/generic.py +++ b/greedybear/cronjobs/extraction/strategies/generic.py @@ -23,7 +23,7 @@ def extract_from_hits(self, hits: list[dict]) -> None: """ for ioc in iocs_from_hits(hits): self.log.info(f"IoC {ioc.name} found by honeypot {self.honeypot}") - ioc_record = self.ioc_processor.add_ioc(ioc, attack_type=SCANNER, general_honeypot_name=self.honeypot) + ioc_record = self.ioc_processor.add_ioc(ioc, attack_type=SCANNER, honeypot_name=self.honeypot) if ioc_record: self.ioc_records.append(ioc_record) threatfox_submission(ioc_record, ioc.related_urls, self.log) diff --git a/greedybear/cronjobs/extraction/strategies/heralding.py b/greedybear/cronjobs/extraction/strategies/heralding.py index 07848a10..4508bf9c 100644 --- a/greedybear/cronjobs/extraction/strategies/heralding.py +++ b/greedybear/cronjobs/extraction/strategies/heralding.py @@ -73,7 +73,7 @@ def _get_scanners(self, hits: list[dict]) -> None: ioc_record = self.ioc_processor.add_ioc( ioc, attack_type=SCANNER, - general_honeypot_name=HERALDING_HONEYPOT, + honeypot_name=HERALDING_HONEYPOT, ) if ioc_record: self.ioc_records.append(ioc_record) diff --git a/greedybear/cronjobs/extraction/strategies/tanner.py b/greedybear/cronjobs/extraction/strategies/tanner.py index 73411548..fe952b74 100644 --- a/greedybear/cronjobs/extraction/strategies/tanner.py +++ b/greedybear/cronjobs/extraction/strategies/tanner.py @@ -114,7 +114,7 @@ def _get_scanners(self, hits: list[dict]) -> None: """Extract scanner IPs from hits.""" for ioc in iocs_from_hits(hits): self.log.info(f"found IP {ioc.name} by honeypot {self.honeypot}") - ioc_record = self.ioc_processor.add_ioc(ioc, attack_type=SCANNER, general_honeypot_name=TANNER_HONEYPOT) + ioc_record = self.ioc_processor.add_ioc(ioc, attack_type=SCANNER, honeypot_name=TANNER_HONEYPOT) if ioc_record: self.ioc_records.append(ioc_record) threatfox_submission(ioc_record, ioc.related_urls, self.log) @@ -272,7 +272,7 @@ def _extract_rfi_hostnames(self, hit: dict, scanner_ip: str, request_text: str) if sensor: ioc._sensors_to_add = [sensor] - ioc_record = self.ioc_processor.add_ioc(ioc, attack_type=PAYLOAD_REQUEST, general_honeypot_name=TANNER_HONEYPOT) + ioc_record = self.ioc_processor.add_ioc(ioc, attack_type=PAYLOAD_REQUEST, honeypot_name=TANNER_HONEYPOT) if ioc_record: self.rfi_hostnames_added += 1 threatfox_submission(ioc_record, ioc.related_urls, self.log) diff --git a/greedybear/cronjobs/extraction/utils.py b/greedybear/cronjobs/extraction/utils.py index 16023f3d..49f5cdeb 100644 --- a/greedybear/cronjobs/extraction/utils.py +++ b/greedybear/cronjobs/extraction/utils.py @@ -294,7 +294,7 @@ def threatfox_submission(ioc_record: IOC, related_urls: list, log: Logger) -> No if hasattr(ioc_record, "_seen_honeypots"): seen_honeypots = ioc_record._seen_honeypots else: - seen_honeypots = [hp.name for hp in ioc_record.general_honeypot.all()] + seen_honeypots = [hp.name for hp in ioc_record.honeypots.all()] seen_honeypots_str = ", ".join(seen_honeypots) diff --git a/greedybear/cronjobs/repositories/ioc.py b/greedybear/cronjobs/repositories/ioc.py index 4cc04701..00b13561 100644 --- a/greedybear/cronjobs/repositories/ioc.py +++ b/greedybear/cronjobs/repositories/ioc.py @@ -4,7 +4,7 @@ from django.db import IntegrityError from django.db.models import F -from greedybear.models import IOC, GeneralHoneypot +from greedybear.models import IOC, Honeypot class IocRepository: @@ -18,7 +18,7 @@ class IocRepository: def __init__(self): """Initialize the repository and populate the honeypot cache from the database.""" self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") - self._honeypot_cache = {self._normalize_name(hp.name): hp for hp in GeneralHoneypot.objects.all()} + self._honeypot_cache = {self._normalize_name(hp.name): hp for hp in Honeypot.objects.all()} def _normalize_name(self, name: str) -> str: """Normalize honeypot names for consistent cache and DB usage.""" @@ -40,13 +40,13 @@ def add_honeypot_to_ioc(self, honeypot_name: str, ioc: IOC) -> IOC: if hasattr(ioc, "_seen_honeypots"): honeypot_set = set(ioc._seen_honeypots) else: - honeypot_set = {self._normalize_name(hp.name) for hp in ioc.general_honeypot.all()} + honeypot_set = {self._normalize_name(hp.name) for hp in ioc.honeypots.all()} if normalized_name not in honeypot_set: self.log.debug(f"adding honeypot {honeypot_name} to IoC {ioc}") honeypot = self._honeypot_cache.get(normalized_name) if honeypot is not None: - ioc.general_honeypot.add(honeypot) + ioc.honeypots.add(honeypot) honeypot_set.add(normalized_name) else: self.log.error(f"Honeypot '{honeypot_name}' not found in cache; skipping association for IOC {ioc}") @@ -56,7 +56,7 @@ def add_honeypot_to_ioc(self, honeypot_name: str, ioc: IOC) -> IOC: ioc._seen_honeypots = list(honeypot_set) return ioc - def create_honeypot(self, honeypot_name: str) -> GeneralHoneypot: + def create_honeypot(self, honeypot_name: str) -> Honeypot: """ Create a new honeypot or return an existing one. @@ -68,12 +68,12 @@ def create_honeypot(self, honeypot_name: str) -> GeneralHoneypot: honeypot_name: Name for the new honeypot. Returns: - A GeneralHoneypot instance (newly created or existing). + A Honeypot instance (newly created or existing). """ normalized = self._normalize_name(honeypot_name) try: - honeypot = GeneralHoneypot.objects.create( + honeypot = Honeypot.objects.create( name=honeypot_name, active=True, ) @@ -86,14 +86,14 @@ def create_honeypot(self, honeypot_name: str) -> GeneralHoneypot: self._honeypot_cache[normalized] = honeypot return honeypot - def get_active_honeypots(self) -> list[GeneralHoneypot]: + def get_active_honeypots(self) -> list[Honeypot]: """ Retrieve a list of all active honeypots. Returns: A list of all active honeypots in the database. """ - return list(GeneralHoneypot.objects.filter(active=True)) + return list(Honeypot.objects.filter(active=True)) def get_ioc_by_name(self, name: str) -> IOC | None: """ @@ -106,11 +106,11 @@ def get_ioc_by_name(self, name: str) -> IOC | None: The matching IOC, or None if not found. """ try: - return IOC.objects.prefetch_related("general_honeypot").get(name=name) + return IOC.objects.prefetch_related("honeypots").get(name=name) except IOC.DoesNotExist: return None - def get_hp_by_name(self, name: str) -> GeneralHoneypot | None: + def get_hp_by_name(self, name: str) -> Honeypot | None: """ Retrieve a honeypot by its name. @@ -118,9 +118,9 @@ def get_hp_by_name(self, name: str) -> GeneralHoneypot | None: name: The honeypot name to look up. Returns: - The matching GeneralHoneypot, or None if not found. + The matching Honeypot, or None if not found. """ - return GeneralHoneypot.objects.filter(name__iexact=name).first() + return Honeypot.objects.filter(name__iexact=name).first() def is_empty(self) -> bool: """ @@ -188,7 +188,7 @@ def get_scanners_for_scoring(self, score_fields: list[str]) -> list[IOC]: Returns: QuerySet of IOC objects with only name and score fields loaded. """ - return IOC.objects.filter(general_honeypot__active=True).filter(scanner=True).distinct().only("name", *score_fields) + return IOC.objects.filter(honeypots__active=True).filter(scanner=True).distinct().only("name", *score_fields) def get_scanners_by_pks(self, primary_keys: set[int]): """ @@ -198,14 +198,14 @@ def get_scanners_by_pks(self, primary_keys: set[int]): primary_keys: Set of IOC primary keys to retrieve. Returns: - QuerySet of IOC objects with prefetched general_honeypot relationships + QuerySet of IOC objects with prefetched honeypots relationships and annotated with value and honeypots fields. """ return ( IOC.objects.filter(pk__in=primary_keys) - .prefetch_related("general_honeypot") + .prefetch_related("honeypots") .annotate(value=F("name")) - .annotate(honeypots=ArrayAgg("general_honeypot__name", distinct=True)) + .annotate(honeypot_names=ArrayAgg("honeypots__name", distinct=True)) .values() ) @@ -224,11 +224,11 @@ def get_recent_scanners(self, cutoff_date, days_lookback: int = 30): QuerySet of IOC objects with prefetched relationships and annotations. """ return ( - IOC.objects.filter(general_honeypot__active=True) + IOC.objects.filter(honeypots__active=True) .filter(last_seen__gte=cutoff_date, scanner=True) - .prefetch_related("general_honeypot") + .prefetch_related("honeypots") .annotate(value=F("name")) - .annotate(honeypots=ArrayAgg("general_honeypot__name", distinct=True)) + .annotate(honeypot_names=ArrayAgg("honeypots__name", distinct=True)) .values() ) diff --git a/greedybear/migrations/0049_rename_generalhoneypot_honeypot_and_more.py b/greedybear/migrations/0049_rename_generalhoneypot_honeypot_and_more.py new file mode 100644 index 00000000..9b46c280 --- /dev/null +++ b/greedybear/migrations/0049_rename_generalhoneypot_honeypot_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.12 on 2026-03-17 00:08 + +import django.db.models.functions.text +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("greedybear", "0048_add_attacker_country_code"), + ] + + operations = [ + migrations.RenameModel( + old_name="GeneralHoneypot", + new_name="Honeypot", + ), + migrations.RemoveConstraint( + model_name="honeypot", + name="unique_generalhoneypot_name_ci", + ), + migrations.RenameField( + model_name="ioc", + old_name="general_honeypot", + new_name="honeypots", + ), + migrations.AddConstraint( + model_name="honeypot", + constraint=models.UniqueConstraint(django.db.models.functions.text.Lower("name"), name="unique_honeypot_name_ci"), + ), + ] diff --git a/greedybear/models.py b/greedybear/models.py index 4b33c99f..4156634c 100644 --- a/greedybear/models.py +++ b/greedybear/models.py @@ -37,12 +37,12 @@ def __str__(self): return f"{self.address} ({self.label})" if self.label else self.address -class GeneralHoneypot(models.Model): +class Honeypot(models.Model): name = models.CharField(max_length=15) active = models.BooleanField(default=True) class Meta: - constraints = [models.UniqueConstraint(Lower("name"), name="unique_generalhoneypot_name_ci")] + constraints = [models.UniqueConstraint(Lower("name"), name="unique_honeypot_name_ci")] def __str__(self): return self.name @@ -97,7 +97,7 @@ class IOC(models.Model): related_name="iocs", ) # FEEDS - list of honeypots from general list, from which the IOC was detected - general_honeypot = models.ManyToManyField(GeneralHoneypot, blank=True) + honeypots = models.ManyToManyField(Honeypot, blank=True) # SENSORS - list of T-Pot sensors that detected this IOC sensors = models.ManyToManyField(Sensor, blank=True) scanner = models.BooleanField(default=False) diff --git a/tests/__init__.py b/tests/__init__.py index 684195b1..02134026 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -13,7 +13,7 @@ CommandSequence, CowrieSession, Credential, - GeneralHoneypot, + Honeypot, IocType, ) @@ -24,15 +24,15 @@ def setUpTestData(cls): super().setUpTestData() cls.as_obj, _ = AutonomousSystem.objects.get_or_create(asn="12345", defaults={"name": "greedybear"}) - cls.heralding = GeneralHoneypot.objects.get_or_create(name="Heralding", defaults={"active": True})[0] - cls.ciscoasa = GeneralHoneypot.objects.get_or_create(name="Ciscoasa", defaults={"active": True})[0] - cls.ddospot = GeneralHoneypot.objects.get_or_create(name="Ddospot", defaults={"active": False})[0] + cls.heralding = Honeypot.objects.get_or_create(name="Heralding", defaults={"active": True})[0] + cls.ciscoasa = Honeypot.objects.get_or_create(name="Ciscoasa", defaults={"active": True})[0] + cls.ddospot = Honeypot.objects.get_or_create(name="Ddospot", defaults={"active": False})[0] cls.current_time = datetime.now() # Create honeypots for Cowrie and Log4pot (replacing boolean fields) - cls.cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] - cls.log4pot_hp = GeneralHoneypot.objects.get_or_create(name="Log4pot", defaults={"active": True})[0] + cls.cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + cls.log4pot_hp = Honeypot.objects.get_or_create(name="Log4pot", defaults={"active": True})[0] cls.ioc = IOC.objects.create( name="140.246.171.141", @@ -117,20 +117,20 @@ def setUpTestData(cls): expected_interactions=5.5, ) - cls.ioc.general_honeypot.add(cls.heralding) # FEEDS - cls.ioc.general_honeypot.add(cls.ciscoasa) # FEEDS - cls.ioc.general_honeypot.add(cls.cowrie_hp) # Cowrie honeypot - cls.ioc.general_honeypot.add(cls.log4pot_hp) # Log4pot honeypot + cls.ioc.honeypots.add(cls.heralding) # FEEDS + cls.ioc.honeypots.add(cls.ciscoasa) # FEEDS + cls.ioc.honeypots.add(cls.cowrie_hp) # Cowrie honeypot + cls.ioc.honeypots.add(cls.log4pot_hp) # Log4pot honeypot cls.ioc.save() - cls.ioc_2.general_honeypot.add(cls.heralding) # FEEDS - cls.ioc_2.general_honeypot.add(cls.ciscoasa) # FEEDS - cls.ioc_2.general_honeypot.add(cls.cowrie_hp) # Cowrie honeypot - cls.ioc_2.general_honeypot.add(cls.log4pot_hp) # Log4pot honeypot + cls.ioc_2.honeypots.add(cls.heralding) # FEEDS + cls.ioc_2.honeypots.add(cls.ciscoasa) # FEEDS + cls.ioc_2.honeypots.add(cls.cowrie_hp) # Cowrie honeypot + cls.ioc_2.honeypots.add(cls.log4pot_hp) # Log4pot honeypot cls.ioc_2.save() - cls.ioc_3.general_honeypot.add(cls.cowrie_hp) # Cowrie honeypot + cls.ioc_3.honeypots.add(cls.cowrie_hp) # Cowrie honeypot cls.ioc_3.save() - cls.ioc_domain.general_honeypot.add(cls.heralding) # FEEDS - cls.ioc_domain.general_honeypot.add(cls.log4pot_hp) # Log4pot honeypot + cls.ioc_domain.honeypots.add(cls.heralding) # FEEDS + cls.ioc_domain.honeypots.add(cls.log4pot_hp) # Log4pot honeypot cls.ioc_domain.save() # IOC with an inactive-only honeypot @@ -145,7 +145,7 @@ def setUpTestData(cls): interaction_count=1, attacker_country="Russia", ) - cls.ioc_inactive_country.general_honeypot.add(cls.ddospot) + cls.ioc_inactive_country.honeypots.add(cls.ddospot) cls.ioc_inactive_country.save() cls.cmd_seq = ["cd foo", "ls -la"] diff --git a/tests/api/test_feed_types.py b/tests/api/test_feed_types.py index 35118fc9..4402bddd 100644 --- a/tests/api/test_feed_types.py +++ b/tests/api/test_feed_types.py @@ -5,23 +5,23 @@ from django.test import override_settings from rest_framework.test import APIClient -from greedybear.models import IOC, GeneralHoneypot, IocType +from greedybear.models import IOC, Honeypot, IocType from tests import CustomTestCase class FeedTypeAPITestCase(CustomTestCase): - """Test API feed handling with GeneralHoneypot M2M instead of boolean fields.""" + """Test API feed handling with Honeypot M2M instead of boolean fields.""" def setUp(self): self.client = APIClient() self.client.force_authenticate(user=self.superuser) # Ensure Cowrie and Log4pot honeypots exist - self.cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] - self.log4pot_hp = GeneralHoneypot.objects.get_or_create(name="Log4pot", defaults={"active": True})[0] + self.cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + self.log4pot_hp = Honeypot.objects.get_or_create(name="Log4pot", defaults={"active": True})[0] def test_feed_type_derived_from_m2m(self): - """Verify feed_type is derived from general_honeypot M2M.""" + """Verify feed_type is derived from honeypot M2M.""" response = self.client.get("/api/feeds/all/all/recent.json") self.assertEqual(response.status_code, 200) @@ -100,7 +100,7 @@ def test_feed_type_no_normalization_log4pot(self): type=IocType.IP.value, scanner=True, ) - ioc.general_honeypot.add(self.log4pot_hp) + ioc.honeypots.add(self.log4pot_hp) response = self.client.get("/api/feeds/all/all/recent.json") self.assertEqual(response.status_code, 200) diff --git a/tests/api/views/test_feeds_advanced_view.py b/tests/api/views/test_feeds_advanced_view.py index 7d0b50f8..027bd59e 100644 --- a/tests/api/views/test_feeds_advanced_view.py +++ b/tests/api/views/test_feeds_advanced_view.py @@ -133,7 +133,7 @@ def setUp(self): interaction_count=1, login_attempts=0, ) - self.ioc2.general_honeypot.add(self.cowrie_hp) + self.ioc2.honeypots.add(self.cowrie_hp) self.ioc2.save() # ── Advanced filtering ──────────────────────────────────────────────────── diff --git a/tests/api/views/test_feeds_asn_view.py b/tests/api/views/test_feeds_asn_view.py index c3624da5..fbe62a29 100644 --- a/tests/api/views/test_feeds_asn_view.py +++ b/tests/api/views/test_feeds_asn_view.py @@ -2,7 +2,7 @@ from django.utils import timezone from rest_framework.test import APIClient -from greedybear.models import IOC, AutonomousSystem, GeneralHoneypot +from greedybear.models import IOC, AutonomousSystem, Honeypot from tests import CustomTestCase @@ -13,8 +13,8 @@ class FeedsASNViewTestCase(CustomTestCase): def setUpClass(cls): super().setUpClass() IOC.objects.all().delete() - cls.testpot1, _ = GeneralHoneypot.objects.get_or_create(name="testpot1", active=True) - cls.testpot2, _ = GeneralHoneypot.objects.get_or_create(name="testpot2", active=True) + cls.testpot1, _ = Honeypot.objects.get_or_create(name="testpot1", active=True) + cls.testpot2, _ = Honeypot.objects.get_or_create(name="testpot2", active=True) cls.high_asn = "13335" cls.low_asn = "16276" @@ -34,7 +34,7 @@ def setUpClass(cls): recurrence_probability=0.8, expected_interactions=20.0, ) - cls.ioc_high1.general_honeypot.add(cls.testpot1, cls.testpot2) + cls.ioc_high1.honeypots.add(cls.testpot1, cls.testpot2) cls.ioc_high2 = IOC.objects.create( name="high2.example.com", @@ -47,7 +47,7 @@ def setUpClass(cls): recurrence_probability=0.3, expected_interactions=8.0, ) - cls.ioc_high2.general_honeypot.add(cls.testpot1, cls.testpot2) + cls.ioc_high2.honeypots.add(cls.testpot1, cls.testpot2) cls.ioc_low = IOC.objects.create( name="low.example.com", @@ -60,7 +60,7 @@ def setUpClass(cls): recurrence_probability=0.1, expected_interactions=3.0, ) - cls.ioc_low.general_honeypot.add(cls.testpot1, cls.testpot2) + cls.ioc_low.honeypots.add(cls.testpot1, cls.testpot2) def setUp(self): super().setUp() @@ -100,7 +100,7 @@ def test_200_asn_feed_aggregated_fields(self): self.assertEqual(high_item["last_seen"], max(i.last_seen for i in high_iocs).isoformat()) # validating honeypots dynamically - expected_honeypots = sorted({hp.name for i in high_iocs for hp in i.general_honeypot.all()}) + expected_honeypots = sorted({hp.name for i in high_iocs for hp in i.honeypots.all()}) self.assertEqual(sorted(high_item["honeypots"]), expected_honeypots) def test_200_asn_feed_default_ordering(self): diff --git a/tests/api/views/test_feeds_view.py b/tests/api/views/test_feeds_view.py index 7188f4f2..16346fd7 100644 --- a/tests/api/views/test_feeds_view.py +++ b/tests/api/views/test_feeds_view.py @@ -17,7 +17,7 @@ def test_200_log4pot_feeds(self): target_ioc = next((i for i in iocs if i["value"] == self.ioc.name), None) self.assertIsNotNone(target_ioc) - # feed_type now derived from general_honeypot M2M + # feed_type now derived from honeypot M2M self.assertIn("log4pot", target_ioc["feed_type"]) self.assertIn("cowrie", target_ioc["feed_type"]) self.assertIn("heralding", target_ioc["feed_type"]) diff --git a/tests/api/views/test_general_honeypot_view.py b/tests/api/views/test_general_honeypot_view.py index bdc5a5e4..a592812a 100644 --- a/tests/api/views/test_general_honeypot_view.py +++ b/tests/api/views/test_general_honeypot_view.py @@ -1,13 +1,13 @@ -from greedybear.models import GeneralHoneypot +from greedybear.models import Honeypot from tests import CustomTestCase -class GeneralHoneypotViewTestCase(CustomTestCase): +class HoneypotViewTestCase(CustomTestCase): def test_200_all_general_honeypots(self): - initial_count = GeneralHoneypot.objects.count() + initial_count = Honeypot.objects.count() # add a general honeypot not active - GeneralHoneypot(name="Adbhoney", active=False).save() - self.assertEqual(GeneralHoneypot.objects.count(), initial_count + 1) + Honeypot(name="Adbhoney", active=False).save() + self.assertEqual(Honeypot.objects.count(), initial_count + 1) response = self.client.get("/api/general_honeypot") self.assertEqual(response.status_code, 200) diff --git a/tests/api/views/test_health.py b/tests/api/views/test_health.py index e1bac040..27481c37 100644 --- a/tests/api/views/test_health.py +++ b/tests/api/views/test_health.py @@ -5,7 +5,7 @@ from django.test import override_settings from rest_framework.test import APIClient -from greedybear.models import IOC, GeneralHoneypot +from greedybear.models import IOC, Honeypot from tests import CustomTestCase User = get_user_model() @@ -17,7 +17,7 @@ class HealthViewTestCase(CustomTestCase): @classmethod def setUpTestData(cls): # deleting all existing objects to have predictable test counts - GeneralHoneypot.objects.all().delete() + Honeypot.objects.all().delete() IOC.objects.all().delete() cls.superuser = User.objects.create_superuser( @@ -26,8 +26,8 @@ def setUpTestData(cls): password="adminpass", ) - cls.testpot1 = GeneralHoneypot.objects.create(name="testpot1", active=True) - cls.testpot2 = GeneralHoneypot.objects.create(name="testpot2", active=True) + cls.testpot1 = Honeypot.objects.create(name="testpot1", active=True) + cls.testpot2 = Honeypot.objects.create(name="testpot2", active=True) cls.ioc1 = IOC.objects.create( name="ioc1.example.com", @@ -37,7 +37,7 @@ def setUpTestData(cls): login_attempts=2, first_seen=datetime.now() - timedelta(days=2), ) - cls.ioc1.general_honeypot.add(cls.testpot1, cls.testpot2) + cls.ioc1.honeypots.add(cls.testpot1, cls.testpot2) cls.ioc2 = IOC.objects.create( name="ioc2.example.com", @@ -47,7 +47,7 @@ def setUpTestData(cls): login_attempts=1, first_seen=datetime.now() - timedelta(hours=5), ) - cls.ioc2.general_honeypot.add(cls.testpot1) + cls.ioc2.honeypots.add(cls.testpot1) def setUp(self): self.client = APIClient() diff --git a/tests/api/views/test_statistics_view.py b/tests/api/views/test_statistics_view.py index c7171039..59a7b4df 100644 --- a/tests/api/views/test_statistics_view.py +++ b/tests/api/views/test_statistics_view.py @@ -1,4 +1,4 @@ -from greedybear.models import GeneralHoneypot, Statistics, ViewType +from greedybear.models import Honeypot, Statistics, ViewType from tests import CustomTestCase @@ -37,10 +37,10 @@ def test_200_enrichment_requests(self): def test_200_feed_types(self): # Count honeypots before adding new one - initial_count = GeneralHoneypot.objects.count() + initial_count = Honeypot.objects.count() # add a general honeypot without associated ioc - GeneralHoneypot(name="Tanner", active=True).save() - self.assertEqual(GeneralHoneypot.objects.count(), initial_count + 1) + Honeypot(name="Tanner", active=True).save() + self.assertEqual(Honeypot.objects.count(), initial_count + 1) response = self.client.get("/api/statistics/feeds_types") self.assertEqual(response.status_code, 200) diff --git a/tests/test_cowrie_extraction.py b/tests/test_cowrie_extraction.py index c1d66d5b..3f1c7ed6 100644 --- a/tests/test_cowrie_extraction.py +++ b/tests/test_cowrie_extraction.py @@ -132,8 +132,8 @@ def test_extract_payload_in_messages_with_url(self): self.assertEqual(ioc_arg.name, "evil.com") self.assertIn("http://evil.com/malware.exe", ioc_arg.related_urls) - # Verify honeypot is set via general_honeypot_name argument - self.assertEqual(call_args.kwargs.get("general_honeypot_name"), "Cowrie") + # Verify honeypot is set via honeypot_name argument + self.assertEqual(call_args.kwargs.get("honeypot_name"), "Cowrie") def test_extract_payload_in_messages_no_url(self): """Test extraction when message has no URL.""" @@ -195,7 +195,7 @@ def test_get_url_downloads(self): self.mock_ioc_repo.get_ioc_by_name.side_effect = [scanner_mock, payload_mock] mock_payload_record = Mock() - mock_payload_record.general_honeypot.all.return_value = [] + mock_payload_record.honeypots.all.return_value = [] self.strategy.ioc_processor.add_ioc.return_value = mock_payload_record self.strategy._get_url_downloads(hits) @@ -434,4 +434,4 @@ def test_extract_from_hits_integration(self, mock_iocs_from_hits): # Verify scanner was processed with Cowrie as honeypot self.strategy.ioc_processor.add_ioc.assert_called() call_args = self.strategy.ioc_processor.add_ioc.call_args - self.assertEqual(call_args.kwargs.get("general_honeypot_name"), "Cowrie") + self.assertEqual(call_args.kwargs.get("honeypot_name"), "Cowrie") diff --git a/tests/test_extraction_strategies.py b/tests/test_extraction_strategies.py index 6aaec4f8..c729331e 100644 --- a/tests/test_extraction_strategies.py +++ b/tests/test_extraction_strategies.py @@ -30,7 +30,7 @@ def test_processes_enabled_honeypot(self, mock_threatfox, mock_iocs_from_hits): self.strategy.extract_from_hits(hits) mock_iocs_from_hits.assert_called_once_with(hits) - self.strategy.ioc_processor.add_ioc.assert_called_once_with(mock_ioc, attack_type=SCANNER, general_honeypot_name="TestHoneypot") + self.strategy.ioc_processor.add_ioc.assert_called_once_with(mock_ioc, attack_type=SCANNER, honeypot_name="TestHoneypot") self.assertEqual(len(self.strategy.ioc_records), 1) mock_threatfox.assert_called_once() @@ -84,7 +84,7 @@ def test_logs_correct_honeypot_name(self, mock_iocs_from_hits): self.strategy.extract_from_hits(hits) call_kwargs = self.strategy.ioc_processor.add_ioc.call_args[1] - self.assertEqual(call_kwargs["general_honeypot_name"], "TestHoneypot") + self.assertEqual(call_kwargs["honeypot_name"], "TestHoneypot") @patch("greedybear.cronjobs.extraction.strategies.generic.iocs_from_hits") @patch("greedybear.cronjobs.extraction.strategies.generic.threatfox_submission") @@ -108,4 +108,4 @@ def test_processes_ioc_with_sensors(self, mock_threatfox, mock_iocs_from_hits): self.strategy.extract_from_hits(hits) # Should call add_ioc once with IOC object (sensors are attached to it) - self.strategy.ioc_processor.add_ioc.assert_called_once_with(mock_ioc, attack_type=SCANNER, general_honeypot_name="TestHoneypot") + self.strategy.ioc_processor.add_ioc.assert_called_once_with(mock_ioc, attack_type=SCANNER, honeypot_name="TestHoneypot") diff --git a/tests/test_extraction_utils.py b/tests/test_extraction_utils.py index 9b60eaa6..42387b83 100644 --- a/tests/test_extraction_utils.py +++ b/tests/test_extraction_utils.py @@ -759,7 +759,7 @@ def setUp(self): def _create_mock_payload_request(self): mock = self._create_mock_ioc() mock.payload_request = True - mock.general_honeypot.all.return_value = [] + mock.honeypots.all.return_value = [] return mock def test_skips_non_payload_request_iocs(self): @@ -789,7 +789,7 @@ def test_submits_urls_with_path(self, mock_settings, mock_post): mock_honeypot_cowrie = Mock() mock_honeypot_cowrie.name = "Cowrie" ioc_record = self._create_mock_payload_request() - ioc_record.general_honeypot.all.return_value = [mock_honeypot_cowrie] + ioc_record.honeypots.all.return_value = [mock_honeypot_cowrie] threatfox_submission(ioc_record, ["http://malicious.com/payload.sh"], self.mock_log) mock_post.assert_called_once() call_kwargs = mock_post.call_args[1] @@ -808,7 +808,7 @@ def test_includes_honeypot_names_in_comment(self, mock_settings, mock_post): mock_honeypot_log4pot.name = "Log4pot" mock_honeypot_dionaea = Mock() mock_honeypot_dionaea.name = "Dionaea" - ioc_record.general_honeypot.all.return_value = [mock_honeypot_cowrie, mock_honeypot_log4pot, mock_honeypot_dionaea] + ioc_record.honeypots.all.return_value = [mock_honeypot_cowrie, mock_honeypot_log4pot, mock_honeypot_dionaea] threatfox_submission(ioc_record, ["http://malicious.com/payload.sh"], self.mock_log) call_kwargs = mock_post.call_args[1] comment = call_kwargs["json"]["comment"] diff --git a/tests/test_heralding_strategy.py b/tests/test_heralding_strategy.py index cf6a6707..02cbc1d7 100644 --- a/tests/test_heralding_strategy.py +++ b/tests/test_heralding_strategy.py @@ -43,7 +43,7 @@ def test_extract_scanner_ips(self, mock_threatfox, mock_credential_objects, mock self.strategy.ioc_processor.add_ioc.assert_any_call( mock_ioc, attack_type=SCANNER, - general_honeypot_name=HERALDING_HONEYPOT, + honeypot_name=HERALDING_HONEYPOT, ) self.assertEqual(len(self.strategy.ioc_records), 1) mock_threatfox.assert_called_once() diff --git a/tests/test_ioc_processor.py b/tests/test_ioc_processor.py index 79e97218..1ae5ad61 100644 --- a/tests/test_ioc_processor.py +++ b/tests/test_ioc_processor.py @@ -78,24 +78,24 @@ def test_sets_payload_request_flag_for_payload_attack_type(self): self.assertFalse(result.scanner) self.assertTrue(result.payload_request) - def test_adds_general_honeypot_when_provided(self): + def test_adds_honeypot_when_provided(self): self.mock_sensor_repo.cache = {} self.mock_ioc_repo.get_ioc_by_name.return_value = None ioc = self._create_mock_ioc() self.mock_ioc_repo.save.return_value = ioc self.mock_ioc_repo.add_honeypot_to_ioc.return_value = ioc - self.processor.add_ioc(ioc, attack_type=SCANNER, general_honeypot_name="TestHoneypot") + self.processor.add_ioc(ioc, attack_type=SCANNER, honeypot_name="TestHoneypot") self.mock_ioc_repo.add_honeypot_to_ioc.assert_called_once_with("TestHoneypot", ioc) - def test_skips_general_honeypot_when_not_provided(self): + def test_skips_honeypot_when_not_provided(self): self.mock_sensor_repo.cache = {} self.mock_ioc_repo.get_ioc_by_name.return_value = None ioc = self._create_mock_ioc() self.mock_ioc_repo.save.return_value = ioc - self.processor.add_ioc(ioc, attack_type=SCANNER, general_honeypot_name=None) + self.processor.add_ioc(ioc, attack_type=SCANNER, honeypot_name=None) self.mock_ioc_repo.add_honeypot_to_ioc.assert_not_called() diff --git a/tests/test_ioc_repository.py b/tests/test_ioc_repository.py index a3260898..d088a6e2 100644 --- a/tests/test_ioc_repository.py +++ b/tests/test_ioc_repository.py @@ -5,7 +5,7 @@ from greedybear.cronjobs.repositories import IocRepository from greedybear.enums import IpReputation -from greedybear.models import IOC, GeneralHoneypot +from greedybear.models import IOC, Honeypot from . import CustomTestCase @@ -45,14 +45,14 @@ def test_save_updates_existing_ioc(self): def test_create_honeypot(self): self.repo.create_honeypot("NewHoneypot") - self.assertTrue(GeneralHoneypot.objects.filter(name="NewHoneypot").exists()) - hp = GeneralHoneypot.objects.get(name="NewHoneypot") + self.assertTrue(Honeypot.objects.filter(name="NewHoneypot").exists()) + hp = Honeypot.objects.get(name="NewHoneypot") self.assertTrue(hp.active) def test_get_active_honeypots_returns_only_active(self): - GeneralHoneypot.objects.create(name="TestActivePot1", active=True) - GeneralHoneypot.objects.create(name="TestActivePot2", active=True) - GeneralHoneypot.objects.create(name="TestInactivePot", active=False) + Honeypot.objects.create(name="TestActivePot1", active=True) + Honeypot.objects.create(name="TestActivePot2", active=True) + Honeypot.objects.create(name="TestInactivePot", active=False) result = self.repo.get_active_honeypots() names = [hp.name for hp in result] @@ -62,15 +62,15 @@ def test_get_active_honeypots_returns_only_active(self): self.assertNotIn("TestInactivePot", names) def test_get_active_honeypots_returns_empty_if_none_active(self): - GeneralHoneypot.objects.update(active=False) + Honeypot.objects.update(active=False) result = self.repo.get_active_honeypots() self.assertEqual(len(result), 0) - GeneralHoneypot.objects.update(active=True) + Honeypot.objects.update(active=True) def test_get_hp_by_name_returns_existing(self): - GeneralHoneypot.objects.create(name="TestPot", active=True) + Honeypot.objects.create(name="TestPot", active=True) result = self.repo.get_hp_by_name("TestPot") self.assertIsNotNone(result) self.assertEqual(result.name, "TestPot") @@ -101,53 +101,53 @@ def test_is_enabled_returns_false_for_inactive_honeypot(self): def test_add_honeypot_to_ioc_adds_new_honeypot(self): ioc = IOC.objects.create(name="1.2.3.4", type="ip") - honeypot = GeneralHoneypot.objects.get(name="Cowrie") + honeypot = Honeypot.objects.get(name="Cowrie") result = self.repo.add_honeypot_to_ioc("Cowrie", ioc) - self.assertIn(honeypot, result.general_honeypot.all()) + self.assertIn(honeypot, result.honeypots.all()) def test_add_honeypot_to_ioc_cache_miss_logs_error(self): """Honeypot created after repo init is not in cache; association is skipped and error is logged.""" ioc = IOC.objects.create(name="1.2.3.4", type="ip") # Force cache initialization before creating the new honeypot _ = self.repo._honeypot_cache - GeneralHoneypot.objects.create(name="NewPot", active=True) + Honeypot.objects.create(name="NewPot", active=True) with self.assertLogs("greedybear.cronjobs.repositories.ioc", level="ERROR") as cm: result = self.repo.add_honeypot_to_ioc("NewPot", ioc) - self.assertEqual(result.general_honeypot.count(), 0) + self.assertEqual(result.honeypots.count(), 0) self.assertTrue(any("NewPot" in msg for msg in cm.output)) def test_add_honeypot_to_ioc_idempotent(self): ioc = IOC.objects.create(name="1.2.3.4", type="ip") - honeypot = GeneralHoneypot.objects.get(name="Cowrie") - ioc.general_honeypot.add(honeypot) - initial_count = ioc.general_honeypot.count() + honeypot = Honeypot.objects.get(name="Cowrie") + ioc.honeypots.add(honeypot) + initial_count = ioc.honeypots.count() result = self.repo.add_honeypot_to_ioc("Cowrie", ioc) - self.assertEqual(result.general_honeypot.count(), initial_count) - self.assertEqual(ioc.general_honeypot.count(), 1) + self.assertEqual(result.honeypots.count(), initial_count) + self.assertEqual(ioc.honeypots.count(), 1) def test_add_honeypot_to_ioc_idempotent_case_insensitive(self): ioc = IOC.objects.create(name="1.2.3.5", type="ip") - honeypot = GeneralHoneypot.objects.get(name="Log4pot") - ioc.general_honeypot.add(honeypot) + honeypot = Honeypot.objects.get(name="Log4pot") + ioc.honeypots.add(honeypot) - # Fetch through repository so general_honeypot is prefetched; with normalized + # Fetch through repository so honeypots is prefetched; with normalized # membership check this should not attempt another add query. ioc_fetched = self.repo.get_ioc_by_name("1.2.3.5") with self.assertNumQueries(0): result = self.repo.add_honeypot_to_ioc("Log4Pot", ioc_fetched) - self.assertEqual(result.general_honeypot.count(), 1) - self.assertIn(honeypot, result.general_honeypot.all()) + self.assertEqual(result.honeypots.count(), 1) + self.assertIn(honeypot, result.honeypots.all()) def test_add_honeypot_to_ioc_multiple_honeypots(self): ioc = IOC.objects.create(name="1.2.3.4", type="ip") - hp1 = GeneralHoneypot.objects.get(name="Cowrie") - hp2 = GeneralHoneypot.objects.get(name="Log4pot") + hp1 = Honeypot.objects.get(name="Cowrie") + hp2 = Honeypot.objects.get(name="Log4pot") self.repo.add_honeypot_to_ioc("Cowrie", ioc) self.repo.add_honeypot_to_ioc("Log4pot", ioc) - self.assertEqual(ioc.general_honeypot.count(), 2) - self.assertIn(hp1, ioc.general_honeypot.all()) - self.assertIn(hp2, ioc.general_honeypot.all()) + self.assertEqual(ioc.honeypots.count(), 2) + self.assertIn(hp1, ioc.honeypots.all()) + self.assertIn(hp2, ioc.honeypots.all()) def test_existing_honeypots(self): expected_honeypots = ["Cowrie", "Log4pot", "Heralding", "Ciscoasa", "Ddospot"] @@ -157,21 +157,21 @@ def test_existing_honeypots(self): def test_is_ready_for_extraction_creates_and_enables(self): result = self.repo.is_ready_for_extraction("FooPot") self.assertTrue(result) - self.assertTrue(GeneralHoneypot.objects.filter(name="FooPot").exists()) + self.assertTrue(Honeypot.objects.filter(name="FooPot").exists()) def test_is_ready_for_extraction_case_insensitive(self): - GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True}) + Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True}) result = self.repo.is_ready_for_extraction("cowrie") self.assertTrue(result) - self.assertEqual(GeneralHoneypot.objects.filter(name__iexact="cowrie").count(), 1) + self.assertEqual(Honeypot.objects.filter(name__iexact="cowrie").count(), 1) def test_get_hp_by_name_insensitive(self): - GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True}) + Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True}) result = self.repo.get_hp_by_name("cowrie") self.assertIsNotNone(result) def test_disabled_honeypot_case_insensitive(self): - GeneralHoneypot.objects.create(name="Testpot69", active=False) + Honeypot.objects.create(name="Testpot69", active=False) # reiniting repo after DB change to refresh the cache repo = IocRepository() @@ -179,7 +179,7 @@ def test_disabled_honeypot_case_insensitive(self): self.assertFalse(result) def test_special_and_normal_honeypots(self): - GeneralHoneypot.objects.create(name="NormalPot", active=False) + Honeypot.objects.create(name="NormalPot", active=False) repo = IocRepository() @@ -189,29 +189,29 @@ def test_special_and_normal_honeypots(self): self.assertFalse(repo.is_ready_for_extraction("normalpot")) def test_create_honeypot_case_insensitive_uniqueness(self): - initial_count = GeneralHoneypot.objects.count() - GeneralHoneypot.objects.create(name="TestPot123", active=True) - self.assertEqual(GeneralHoneypot.objects.count(), initial_count + 1) + initial_count = Honeypot.objects.count() + Honeypot.objects.create(name="TestPot123", active=True) + self.assertEqual(Honeypot.objects.count(), initial_count + 1) with self.assertRaises(IntegrityError): with transaction.atomic(): - GeneralHoneypot.objects.create(name="testpot123", active=True) + Honeypot.objects.create(name="testpot123", active=True) - self.assertEqual(GeneralHoneypot.objects.count(), initial_count + 1) - self.assertEqual(GeneralHoneypot.objects.get(name__iexact="testpot123").name, "TestPot123") + self.assertEqual(Honeypot.objects.count(), initial_count + 1) + self.assertEqual(Honeypot.objects.get(name__iexact="testpot123").name, "TestPot123") def test_create_honeypot_integrity_error_handling(self): - initial_count = GeneralHoneypot.objects.count() - GeneralHoneypot.objects.create(name="Log4PotTest123", active=True) + initial_count = Honeypot.objects.count() + Honeypot.objects.create(name="Log4PotTest123", active=True) try: with transaction.atomic(): - GeneralHoneypot.objects.create(name="log4pottest123", active=True) + Honeypot.objects.create(name="log4pottest123", active=True) except IntegrityError: - hp = GeneralHoneypot.objects.filter(name__iexact="log4pottest123").first() + hp = Honeypot.objects.filter(name__iexact="log4pottest123").first() self.assertEqual(hp.name, "Log4PotTest123") - self.assertEqual(GeneralHoneypot.objects.count(), initial_count + 1) + self.assertEqual(Honeypot.objects.count(), initial_count + 1) def test_create_new_honeypot_creates_and_updates_cache(self): self.repo._honeypot_cache.clear() @@ -220,30 +220,30 @@ def test_create_new_honeypot_creates_and_updates_cache(self): self.assertIn("uniquenewpot123", self.repo._honeypot_cache) self.assertTrue(hp.active) - db_hp = GeneralHoneypot.objects.get(name="UniqueNewPot123") + db_hp = Honeypot.objects.get(name="UniqueNewPot123") self.assertEqual(db_hp.name, "UniqueNewPot123") self.assertTrue(db_hp.active) def test_honeypot_unique_constraint_case_insensitive(self): - initial_count = GeneralHoneypot.objects.count() + initial_count = Honeypot.objects.count() hp1 = self.repo.create_honeypot("TestPot456") self.assertIsNotNone(hp1) with self.assertRaises(IntegrityError): with transaction.atomic(): - GeneralHoneypot.objects.create(name="testpot456", active=True) + Honeypot.objects.create(name="testpot456", active=True) - self.assertEqual(GeneralHoneypot.objects.filter(name__iexact="testpot456").count(), 1) - self.assertEqual(GeneralHoneypot.objects.count(), initial_count + 1) + self.assertEqual(Honeypot.objects.filter(name__iexact="testpot456").count(), 1) + self.assertEqual(Honeypot.objects.count(), initial_count + 1) def test_get_scanners_for_scoring_returns_scanners(self): # Create scanners - cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] - log4pot_hp = GeneralHoneypot.objects.get_or_create(name="Log4pot", defaults={"active": True})[0] + cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + log4pot_hp = Honeypot.objects.get_or_create(name="Log4pot", defaults={"active": True})[0] ioc1 = IOC.objects.create(name="1.2.3.4", type="ip", scanner=True) - ioc1.general_honeypot.add(cowrie_hp) + ioc1.honeypots.add(cowrie_hp) ioc2 = IOC.objects.create(name="5.6.7.8", type="ip", scanner=True) - ioc2.general_honeypot.add(log4pot_hp) + ioc2.honeypots.add(log4pot_hp) result = self.repo.get_scanners_for_scoring(["recurrence_probability", "expected_interactions"]) @@ -252,9 +252,9 @@ def test_get_scanners_for_scoring_returns_scanners(self): self.assertIn("5.6.7.8", names) def test_get_scanners_for_scoring_excludes_non_scanners(self): - cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] ioc = IOC.objects.create(name="1.2.3.4", type="ip", scanner=False) - ioc.general_honeypot.add(cowrie_hp) + ioc.honeypots.add(cowrie_hp) result = self.repo.get_scanners_for_scoring(["recurrence_probability"]) @@ -262,9 +262,9 @@ def test_get_scanners_for_scoring_excludes_non_scanners(self): self.assertNotIn("1.2.3.4", names) def test_get_scanners_for_scoring_only_loads_specified_fields(self): - cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] ioc = IOC.objects.create(name="1.2.3.4", type="ip", scanner=True, attack_count=100) - ioc.general_honeypot.add(cowrie_hp) + ioc.honeypots.add(cowrie_hp) result = list(self.repo.get_scanners_for_scoring(["recurrence_probability"])) @@ -289,24 +289,24 @@ def test_get_scanners_by_pks_returns_correct_iocs(self): self.assertNotIn("9.10.11.12", values) def test_get_scanners_by_pks_includes_honeypot_annotation(self): - hp = GeneralHoneypot.objects.create(name="TestPot", active=True) + hp = Honeypot.objects.create(name="TestPot", active=True) ioc = IOC.objects.create(name="1.2.3.4", type="ip") - ioc.general_honeypot.add(hp) + ioc.honeypots.add(hp) result = list(self.repo.get_scanners_by_pks({ioc.pk})) self.assertEqual(len(result), 1) - self.assertIn("honeypots", result[0]) + self.assertIn("honeypot_names", result[0]) def test_get_recent_scanners_returns_recent_only(self): recent_date = datetime.now() - timedelta(days=5) old_date = datetime.now() - timedelta(days=40) - cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] ioc1 = IOC.objects.create(name="1.2.3.4", type="ip", scanner=True, last_seen=recent_date) - ioc1.general_honeypot.add(cowrie_hp) + ioc1.honeypots.add(cowrie_hp) ioc2 = IOC.objects.create(name="5.6.7.8", type="ip", scanner=True, last_seen=old_date) - ioc2.general_honeypot.add(cowrie_hp) + ioc2.honeypots.add(cowrie_hp) cutoff = datetime.now() - timedelta(days=30) result = list(self.repo.get_recent_scanners(cutoff, days_lookback=30)) @@ -317,9 +317,9 @@ def test_get_recent_scanners_returns_recent_only(self): def test_get_recent_scanners_excludes_non_scanners(self): recent_date = datetime.now() - timedelta(days=5) - cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] ioc = IOC.objects.create(name="1.2.3.4", type="ip", scanner=False, last_seen=recent_date) - ioc.general_honeypot.add(cowrie_hp) + ioc.honeypots.add(cowrie_hp) cutoff = datetime.now() - timedelta(days=30) result = list(self.repo.get_recent_scanners(cutoff)) @@ -369,9 +369,9 @@ def test_get_scanners_for_scoring_returns_empty_when_no_scanners(self): self.assertEqual(len(result), 0) def test_get_scanners_for_scoring_excludes_inactive_honeypots(self): - hp = GeneralHoneypot.objects.create(name="InactivePot", active=False) + hp = Honeypot.objects.create(name="InactivePot", active=False) ioc = IOC.objects.create(name="1.2.3.4", type="ip", scanner=True) - ioc.general_honeypot.add(hp) + ioc.honeypots.add(hp) result = list(self.repo.get_scanners_for_scoring(["recurrence_probability"])) @@ -379,10 +379,10 @@ def test_get_scanners_for_scoring_excludes_inactive_honeypots(self): self.assertNotIn("1.2.3.4", names) def test_get_scanners_for_scoring_with_multiple_honeypots(self): - hp1 = GeneralHoneypot.objects.create(name="Pot1", active=True) - hp2 = GeneralHoneypot.objects.create(name="Pot2", active=True) + hp1 = Honeypot.objects.create(name="Pot1", active=True) + hp2 = Honeypot.objects.create(name="Pot2", active=True) ioc = IOC.objects.create(name="1.2.3.4", type="ip", scanner=True) - ioc.general_honeypot.add(hp1, hp2) + ioc.honeypots.add(hp1, hp2) result = list(self.repo.get_scanners_for_scoring(["recurrence_probability"])) @@ -406,13 +406,13 @@ def test_get_scanners_by_pks_ioc_with_no_honeypots(self): result = list(self.repo.get_scanners_by_pks({ioc.pk})) self.assertEqual(len(result), 1) - self.assertIn("honeypots", result[0]) + self.assertIn("honeypot_names", result[0]) def test_get_recent_scanners_all_iocs_older_than_cutoff(self): old_date = datetime.now() - timedelta(days=40) - cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] ioc = IOC.objects.create(name="1.2.3.4", type="ip", scanner=True, last_seen=old_date) - ioc.general_honeypot.add(cowrie_hp) + ioc.honeypots.add(cowrie_hp) cutoff = datetime.now() - timedelta(days=30) result = list(self.repo.get_recent_scanners(cutoff)) @@ -421,10 +421,10 @@ def test_get_recent_scanners_all_iocs_older_than_cutoff(self): self.assertNotIn("1.2.3.4", values) def test_get_recent_scanners_with_inactive_honeypot(self): - hp = GeneralHoneypot.objects.create(name="InactivePot", active=False) + hp = Honeypot.objects.create(name="InactivePot", active=False) recent_date = datetime.now() - timedelta(days=5) ioc = IOC.objects.create(name="1.2.3.4", type="ip", scanner=True, last_seen=recent_date) - ioc.general_honeypot.add(hp) + ioc.honeypots.add(hp) cutoff = datetime.now() - timedelta(days=30) result = list(self.repo.get_recent_scanners(cutoff)) @@ -449,8 +449,8 @@ def test_bulk_update_scores_with_custom_batch_size(self): # --- Tests for N+1 fix --- - def test_honeypot_cache_stores_generalhoneypot_objects(self): - """honeypot_cache must store GeneralHoneypot instances, not booleans.""" + def test_honeypot_cache_stores_honeypot_objects(self): + """_honeypot_cache must store Honeypot instances, not booleans.""" self.assertGreater( len(self.repo._honeypot_cache), 0, @@ -459,29 +459,29 @@ def test_honeypot_cache_stores_generalhoneypot_objects(self): for key, value in self.repo._honeypot_cache.items(): self.assertIsInstance( value, - GeneralHoneypot, - f"Cache value for '{key}' should be a GeneralHoneypot instance, got {type(value)}", + Honeypot, + f"Cache value for '{key}' should be a Honeypot instance, got {type(value)}", ) - def test_get_ioc_by_name_prefetches_general_honeypot(self): - """Accessing general_honeypot on an IOC fetched via get_ioc_by_name must not trigger extra DB queries.""" + def test_get_ioc_by_name_prefetches_honeypots(self): + """Accessing honeypots on an IOC fetched via get_ioc_by_name must not trigger extra DB queries.""" ioc = self.repo.get_ioc_by_name("140.246.171.141") self.assertIsNotNone(ioc) with self.assertNumQueries(0): - list(ioc.general_honeypot.all()) + list(ioc.honeypots.all()) def test_add_honeypot_to_ioc_uses_cache_not_db(self): """When honeypot is in cache and the IOC was fetched with prefetch, no extra queries are needed.""" - cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] # Case 1: IOC already associated with Cowrie - membership check uses prefetch (0 queries), # and skips the add entirely (already associated), producing zero DB queries ioc = IOC.objects.create(name="5.5.5.5", type="ip") - ioc.general_honeypot.add(cowrie_hp) + ioc.honeypots.add(cowrie_hp) ioc_fetched = self.repo.get_ioc_by_name("5.5.5.5") with self.assertNumQueries(0): result = self.repo.add_honeypot_to_ioc("Cowrie", ioc_fetched) - self.assertIn(cowrie_hp, result.general_honeypot.all()) + self.assertIn(cowrie_hp, result.honeypots.all()) # Case 2: IOC not yet associated - membership check uses prefetch (0 queries), # honeypot lookup uses in-memory cache (0 queries), only the M2M INSERT fires @@ -490,13 +490,13 @@ def test_add_honeypot_to_ioc_uses_cache_not_db(self): _ = self.repo._honeypot_cache # Force cache load to isolate M2M INSERT queries with self.assertNumQueries(1): # only M2M INSERT result2 = self.repo.add_honeypot_to_ioc("Cowrie", ioc2_fetched) - self.assertIn(cowrie_hp, result2.general_honeypot.all()) + self.assertIn(cowrie_hp, result2.honeypots.all()) def test_create_honeypot_stores_object_in_cache(self): - """create_honeypot must store the GeneralHoneypot object in cache, not a boolean.""" + """create_honeypot must store the Honeypot object in cache, not a boolean.""" hp = self.repo.create_honeypot("CacheTestPot") cached = self.repo._honeypot_cache.get("cachetestpot") - self.assertIsInstance(cached, GeneralHoneypot) + self.assertIsInstance(cached, Honeypot) self.assertEqual(cached.pk, hp.pk) @@ -513,12 +513,12 @@ def test_update_scores_with_repository(self): from greedybear.cronjobs.scoring.scoring_jobs import UpdateScores # Create test data - cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] - log4pot_hp = GeneralHoneypot.objects.get_or_create(name="Log4pot", defaults={"active": True})[0] + cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + log4pot_hp = Honeypot.objects.get_or_create(name="Log4pot", defaults={"active": True})[0] ioc1 = IOC.objects.create(name="10.1.2.3", type="ip", scanner=True, recurrence_probability=0.0) - ioc1.general_honeypot.add(cowrie_hp) + ioc1.honeypots.add(cowrie_hp) ioc2 = IOC.objects.create(name="10.5.6.7", type="ip", scanner=True, recurrence_probability=0.0) - ioc2.general_honeypot.add(log4pot_hp) + ioc2.honeypots.add(log4pot_hp) # Create score dataframe df = pd.DataFrame( @@ -547,12 +547,12 @@ def test_update_scores_resets_missing_iocs(self): from greedybear.cronjobs.scoring.scoring_jobs import UpdateScores # Create test data - one IOC will be missing from df - cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] - log4pot_hp = GeneralHoneypot.objects.get_or_create(name="Log4pot", defaults={"active": True})[0] + cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + log4pot_hp = Honeypot.objects.get_or_create(name="Log4pot", defaults={"active": True})[0] ioc1 = IOC.objects.create(name="10.2.3.4", type="ip", scanner=True, recurrence_probability=0.9) - ioc1.general_honeypot.add(cowrie_hp) + ioc1.honeypots.add(cowrie_hp) ioc2 = IOC.objects.create(name="10.6.7.8", type="ip", scanner=True, recurrence_probability=0.8) - ioc2.general_honeypot.add(log4pot_hp) + ioc2.honeypots.add(log4pot_hp) # DataFrame only has one IOC df = pd.DataFrame({"value": ["10.2.3.4"], "recurrence_probability": [0.75], "expected_interactions": [10.0]}) @@ -571,9 +571,9 @@ def test_get_current_data_with_repository(self): from greedybear.cronjobs.scoring.utils import get_current_data recent_date = datetime.now() - timedelta(days=5) - cowrie_hp = GeneralHoneypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] + cowrie_hp = Honeypot.objects.get_or_create(name="Cowrie", defaults={"active": True})[0] ioc = IOC.objects.create(name="1.2.3.4", type="ip", scanner=True, last_seen=recent_date) - ioc.general_honeypot.add(cowrie_hp) + ioc.honeypots.add(cowrie_hp) result = get_current_data(days_lookback=30, ioc_repo=self.repo) diff --git a/tests/test_models.py b/tests/test_models.py index 18be2556..bbfbc78f 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -17,8 +17,8 @@ def test_ioc_model(self): self.assertEqual(self.ioc.attack_count, 1) self.assertEqual(self.ioc.interaction_count, 1) # Honeypots are now via M2M relationship - self.assertIn(self.cowrie_hp, self.ioc.general_honeypot.all()) - self.assertIn(self.log4pot_hp, self.ioc.general_honeypot.all()) + self.assertIn(self.cowrie_hp, self.ioc.honeypots.all()) + self.assertIn(self.log4pot_hp, self.ioc.honeypots.all()) self.assertEqual(self.ioc.scanner, True) self.assertEqual(self.ioc.payload_request, True) self.assertEqual(self.ioc.related_urls, []) @@ -31,8 +31,8 @@ def test_ioc_model(self): self.assertEqual(self.ioc_2.ip_reputation, IpReputation.MASS_SCANNER) - self.assertIn(self.heralding, self.ioc.general_honeypot.all()) - self.assertIn(self.ciscoasa, self.ioc.general_honeypot.all()) + self.assertIn(self.heralding, self.ioc.honeypots.all()) + self.assertIn(self.ciscoasa, self.ioc.honeypots.all()) def test_command_sequence_model(self): self.assertEqual(self.command_sequence.first_seen, self.current_time) @@ -71,7 +71,7 @@ def test_statistics_model(self): self.assertEqual(self.statistic.view, ViewType.ENRICHMENT_VIEW.value) self.assertEqual(self.statistic.request_date, self.current_time) - def test_general_honeypot_model(self): + def test_honeypot_model(self): self.assertEqual(self.heralding.name, "Heralding") self.assertEqual(self.heralding.active, True) diff --git a/tests/test_serializers.py b/tests/test_serializers.py index 6e78cdc5..e367cd58 100644 --- a/tests/test_serializers.py +++ b/tests/test_serializers.py @@ -11,7 +11,7 @@ ) from greedybear.consts import PAYLOAD_REQUEST, SCANNER from greedybear.enums import IpReputation -from greedybear.models import IOC, GeneralHoneypot, Sensor +from greedybear.models import IOC, Honeypot, Sensor from tests import CustomTestCase @@ -39,9 +39,9 @@ class FeedsRequestSerializersTestCase(CustomTestCase): @classmethod def setUpTestData(cls): super().setUpTestData() - cls.adbhoney = GeneralHoneypot.objects.filter(name__iexact="adbhoney").first() + cls.adbhoney = Honeypot.objects.filter(name__iexact="adbhoney").first() if not cls.adbhoney: - cls.adbhoney = GeneralHoneypot.objects.create(name="Adbhoney", active=True) + cls.adbhoney = Honeypot.objects.create(name="Adbhoney", active=True) def test_valid_fields(self): choices = { @@ -218,9 +218,9 @@ class FeedsResponseSerializersTestCase(CustomTestCase): @classmethod def setUpTestData(cls): super().setUpTestData() - cls.adbhoney = GeneralHoneypot.objects.filter(name__iexact="adbhoney").first() + cls.adbhoney = Honeypot.objects.filter(name__iexact="adbhoney").first() if not cls.adbhoney: - cls.adbhoney = GeneralHoneypot.objects.create(name="Adbhoney", active=True) + cls.adbhoney = Honeypot.objects.create(name="Adbhoney", active=True) def test_valid_fields(self): scanner_choices = [True, False] diff --git a/tests/test_tanner_strategy.py b/tests/test_tanner_strategy.py index 7e31f95c..729fd37c 100644 --- a/tests/test_tanner_strategy.py +++ b/tests/test_tanner_strategy.py @@ -30,7 +30,7 @@ def test_extract_scanner_ips(self, mock_threatfox, mock_iocs_from_hits): self.strategy.extract_from_hits(hits) mock_iocs_from_hits.assert_called_once_with(hits) - self.strategy.ioc_processor.add_ioc.assert_any_call(mock_ioc, attack_type=SCANNER, general_honeypot_name=TANNER_HONEYPOT) + self.strategy.ioc_processor.add_ioc.assert_any_call(mock_ioc, attack_type=SCANNER, honeypot_name=TANNER_HONEYPOT) self.assertEqual(len(self.strategy.ioc_records), 1) mock_threatfox.assert_called_once() @@ -373,7 +373,7 @@ def test_rfi_hostname_as_payload_request(self, mock_add_tags, mock_threatfox, mo payload_calls = [call for call in self.strategy.ioc_processor.add_ioc.call_args_list if call[1].get("attack_type") == PAYLOAD_REQUEST] self.assertEqual(len(payload_calls), 1) self.assertEqual(payload_calls[0][0][0].name, "evil.com") - self.assertEqual(payload_calls[0][1]["general_honeypot_name"], TANNER_HONEYPOT) + self.assertEqual(payload_calls[0][1]["honeypot_name"], TANNER_HONEYPOT) @patch("greedybear.cronjobs.extraction.strategies.tanner.iocs_from_hits") @patch("greedybear.cronjobs.extraction.strategies.tanner.threatfox_submission") From cc94c280e58fb2571bdccbd97ca0a26080d63f13 Mon Sep 17 00:00:00 2001 From: Tanmay Joddar <152395649+tanmayjoddar@users.noreply.github.com> Date: Thu, 2 Apr 2026 17:47:08 +0530 Subject: [PATCH 13/27] fix: scan all hits for GeoIP enrichment in iocs_from_hits(). Closes #1174 (#1178) --- greedybear/cronjobs/extraction/utils.py | 4 ++-- tests/test_extraction_utils.py | 26 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/greedybear/cronjobs/extraction/utils.py b/greedybear/cronjobs/extraction/utils.py index 49f5cdeb..94cb6158 100644 --- a/greedybear/cronjobs/extraction/utils.py +++ b/greedybear/cronjobs/extraction/utils.py @@ -174,7 +174,7 @@ def iocs_from_hits(hits: list[dict]) -> list[IOC]: # Sort sensors by ID for consistent processing order sensors = sorted(sensors_map.values(), key=lambda s: s.id) - geoip = hits[0].get("geoip", {}) if hits else {} + geoip = next((h.get("geoip") for h in hits if h.get("geoip")), {}) attacker_country = geoip.get("country_name", "") raw_country_code = geoip.get("country_iso_code", "") attacker_country_code = raw_country_code if len(raw_country_code) == 2 else "" @@ -187,7 +187,7 @@ def iocs_from_hits(hits: list[dict]) -> list[IOC]: name=ip, type=get_ioc_type(ip), interaction_count=len(hits), - ip_reputation=correct_ip_reputation(ip, hits[0].get("ip_rep", ""), mass_scanner_ips), + ip_reputation=correct_ip_reputation(ip, next((h.get("ip_rep", "") for h in hits if h.get("ip_rep")), ""), mass_scanner_ips), autonomous_system=autonomous_system, destination_ports=sorted(set(dest_ports)), login_attempts=login_attempts, diff --git a/tests/test_extraction_utils.py b/tests/test_extraction_utils.py index 42387b83..fa703e42 100644 --- a/tests/test_extraction_utils.py +++ b/tests/test_extraction_utils.py @@ -413,6 +413,32 @@ def test_handles_missing_geoip(self): ioc = iocs[0] self.assertIsNone(ioc.autonomous_system) + def test_geoip_from_later_hit_when_first_has_none(self): + """ASN and country must be taken from any geoip-enriched hit, not only hits[0].""" + hits = [ + self._create_hit(src_ip="1.2.3.4"), + self._create_hit(src_ip="1.2.3.4", asn=13335), + ] + hits[1]["geoip"]["as_org"] = "CLOUDFLARE" + hits[1]["geoip"]["country_name"] = "United States" + + iocs = iocs_from_hits(hits) + ioc = iocs[0] + + self.assertIsNotNone(ioc.autonomous_system) + self.assertEqual(ioc.autonomous_system.asn, 13335) + self.assertEqual(ioc.attacker_country, "United States") + + def test_ip_rep_from_later_hit_when_first_has_none(self): + """ip_rep should be read from first hit that carries it, not blindly hits[0].""" + hits = [ + self._create_hit(src_ip="1.2.3.4", ip_rep=""), + self._create_hit(src_ip="1.2.3.4", ip_rep="known_attacker"), + ] + iocs = iocs_from_hits(hits) + ioc = iocs[0] + self.assertEqual(ioc.ip_reputation, "known_attacker") + def test_extracts_timestamps(self): hits = [ self._create_hit(src_ip="8.8.8.8", timestamp="2025-01-01T10:00:00.000Z"), From 010f3e991c2f5683e50553340ff0230cfe1c6f4a Mon Sep 17 00:00:00 2001 From: Matteo Lodi <30625432+mlodic@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:12:56 +0200 Subject: [PATCH 14/27] updated readme --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b1452bf9..dbdd1822 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,12 @@ To install it locally, Please refer to our [installation guide](https://intelowl Thanks to [The Honeynet Project](https://www.honeynet.org) we are providing free public feeds available [here](https://greedybear.honeynet.org). -#### DigitalOcean +#### Google Summer of Code + GSoC logo -In 2022 we joined the official [DigitalOcean Open Source Program](https://www.digitalocean.com/open-source?utm_medium=opensource&utm_source=IntelOwl). +In 2026 we started participating to the [Google Summer of Code](https://summerofcode.withgoogle.com/) (GSoC)! +If you are interested in participating in the next Google Summer of Code, check all the info available in the [dedicated repository](https://github.com/intelowlproject/gsoc)! ## Maintainers and Key Contributors From 1cdc0d44897e1ac0efd4b2df84ca75b5814aafb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:02:31 +0200 Subject: [PATCH 15/27] build(deps-dev): bump ruff from 0.15.8 to 0.15.9 (#1187) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.8 to 0.15.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.8...0.15.9) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f440631a..77bd5b73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ dev = [ "django-watchfiles==1.4.0", ] lint = [ - "ruff==0.15.8", + "ruff==0.15.9", ] [tool.uv] diff --git a/uv.lock b/uv.lock index dd9a4c08..8800fed4 100644 --- a/uv.lock +++ b/uv.lock @@ -546,7 +546,7 @@ dev = [ { name = "django-test-migrations", specifier = "==1.5.0" }, { name = "django-watchfiles", specifier = "==1.4.0" }, ] -lint = [{ name = "ruff", specifier = "==0.15.8" }] +lint = [{ name = "ruff", specifier = "==0.15.9" }] [[package]] name = "gunicorn" @@ -877,27 +877,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, - { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, - { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, - { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, - { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, - { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, - { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, - { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, - { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, - { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, - { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, +version = "0.15.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" }, + { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" }, + { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" }, + { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" }, + { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, ] [[package]] From b34b83f4821047826300aba1edd8af46e38d7edc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:24:59 +0200 Subject: [PATCH 16/27] build(deps): bump sass from 1.98.0 to 1.99.0 in /frontend (#1186) Bumps [sass](https://github.com/sass/dart-sass) from 1.98.0 to 1.99.0. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.98.0...1.99.0) --- updated-dependencies: - dependency-name: sass dependency-version: 1.99.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9bfe1abb..90a9754c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,7 +23,7 @@ "react-use": "^17.6.0", "reactstrap": "^9.2.3", "recharts": "^2.15.4", - "sass": "^1.98.0", + "sass": "^1.99.0", "zustand": "^4.5.7" }, "devDependencies": { @@ -7438,9 +7438,9 @@ } }, "node_modules/sass": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", - "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz", + "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", @@ -14205,9 +14205,9 @@ } }, "sass": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", - "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz", + "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", "requires": { "@parcel/watcher": "^2.4.1", "chokidar": "^4.0.0", diff --git a/frontend/package.json b/frontend/package.json index 67a24135..70fbf106 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,7 +24,7 @@ "react-use": "^17.6.0", "reactstrap": "^9.2.3", "recharts": "^2.15.4", - "sass": "^1.98.0", + "sass": "^1.99.0", "zustand": "^4.5.7" }, "scripts": { From 84374a27e77e1bc1b76e333d86d2e4de66d21eaf Mon Sep 17 00:00:00 2001 From: Varun chauhan <115783538+chauhan-varun@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:04:32 +0530 Subject: [PATCH 17/27] Refactor: Dashboard Country Normalization. Closes #1175 (#1180) * refactor: move country normalization logic to utility module and apply during data fetch * refactor: rename rawData to normalizedData in attacker countries store and update type annotations * chore: remove redundant whitespace in useAttackerCountriesStore tests --- .../src/components/dashboard/utils/charts.jsx | 2 +- .../src/stores/useAttackerCountriesStore.jsx | 49 +++++++------------ frontend/src/utils/country.js | 33 +++++++++++++ .../stores/useAttackerCountriesStore.test.jsx | 19 +++++-- frontend/tests/utils/country.test.js | 25 ++++++++++ 5 files changed, 91 insertions(+), 37 deletions(-) create mode 100644 frontend/src/utils/country.js create mode 100644 frontend/tests/utils/country.test.js diff --git a/frontend/src/components/dashboard/utils/charts.jsx b/frontend/src/components/dashboard/utils/charts.jsx index 6091cddd..ed49d705 100644 --- a/frontend/src/components/dashboard/utils/charts.jsx +++ b/frontend/src/components/dashboard/utils/charts.jsx @@ -139,7 +139,7 @@ export const AttackOriginCountriesChart = React.memo(() => { const { range } = useTimePickerStore(); const { - rawData: data, + normalizedData: data, loading, error, fetchData, diff --git a/frontend/src/stores/useAttackerCountriesStore.jsx b/frontend/src/stores/useAttackerCountriesStore.jsx index bf128847..2240c839 100644 --- a/frontend/src/stores/useAttackerCountriesStore.jsx +++ b/frontend/src/stores/useAttackerCountriesStore.jsx @@ -1,34 +1,10 @@ import axios from "axios"; import { create } from "zustand"; import { IOC_ATTACKER_COUNTRIES_URI } from "../constants/api"; - -// Normalise country names from T-Pot geoip to match Natural Earth names used by world-atlas@2. (https://github.com/topojson/world-atlas) -const NAME_FIXES = { - "United States": "United States of America", - "Czech Republic": "Czechia", - "Ivory Coast": "Côte d'Ivoire", - "Democratic Republic of the Congo": "Dem. Rep. Congo", - "Republic of the Congo": "Congo", - "Bosnia and Herzegovina": "Bosnia and Herz.", - "Central African Republic": "Central African Rep.", - "Dominican Republic": "Dominican Rep.", - "Equatorial Guinea": "Eq. Guinea", - "South Sudan": "S. Sudan", - "North Macedonia": "Macedonia", - Eswatini: "eSwatini", - "State of Palestine": "Palestine", - "Western Sahara": "W. Sahara", - "Solomon Islands": "Solomon Is.", - "Falkland Islands": "Falkland Is.", - "French Southern Territories": "Fr. S. Antarctic Lands", -}; - -function normalise(name) { - return NAME_FIXES[name] ?? name; -} +import { normalizeCountryName } from "../utils/country"; const useAttackerCountriesStore = create((set, get) => ({ - rawData: [], + normalizedData: [], countryDataMap: {}, maxCount: 0, loading: false, @@ -58,17 +34,28 @@ const useAttackerCountriesStore = create((set, get) => ({ signal: controller.signal, }); - const rawData = Array.isArray(resp?.data) ? resp.data : []; + const normalizedData = (Array.isArray(resp?.data) ? resp.data : []).map( + (item) => { + if ( + item && + typeof item === "object" && + typeof item.country === "string" + ) { + return { ...item, country: normalizeCountryName(item.country) }; + } + return item; + }, + ); + const countryDataMap = {}; let maxCount = 0; - rawData.forEach((item) => { + normalizedData.forEach((item) => { if (item && typeof item === "object") { const { country, count } = item; if (typeof country === "string") { - const key = normalise(country); const countNum = Number(count) || 0; - countryDataMap[key] = countNum; + countryDataMap[country] = countNum; if (countNum > maxCount) maxCount = countNum; } } @@ -76,7 +63,7 @@ const useAttackerCountriesStore = create((set, get) => ({ if (get().currentController === controller) { set({ - rawData, + normalizedData, countryDataMap, maxCount, loading: false, diff --git a/frontend/src/utils/country.js b/frontend/src/utils/country.js new file mode 100644 index 00000000..0ef67ac4 --- /dev/null +++ b/frontend/src/utils/country.js @@ -0,0 +1,33 @@ +/** + * Normalise country names from T-Pot geoip to match Natural Earth names used by world-atlas@2. + * (https://github.com/topojson/world-atlas) + */ +export const COUNTRY_NAME_FIXES = { + "United States": "United States of America", + "Czech Republic": "Czechia", + "Ivory Coast": "Côte d'Ivoire", + "Democratic Republic of the Congo": "Dem. Rep. Congo", + "Republic of the Congo": "Congo", + "Bosnia and Herzegovina": "Bosnia and Herz.", + "Central African Republic": "Central African Rep.", + "Dominican Republic": "Dominican Rep.", + "Equatorial Guinea": "Eq. Guinea", + "South Sudan": "S. Sudan", + "North Macedonia": "Macedonia", + Eswatini: "eSwatini", + "State of Palestine": "Palestine", + "Western Sahara": "W. Sahara", + "Solomon Islands": "Solomon Is.", + "Falkland Islands": "Falkland Is.", + "French Southern Territories": "Fr. S. Antarctic Lands", +}; + +/** + * Returns a normalised country name if a fix is available, otherwise returns the original name. + * + * @param {string|null|undefined} name - Raw country name (e.g., from T-Pot GeoIP) + * @returns {string|null|undefined} - Normalised country name (matching Natural Earth standards) + */ +export function normalizeCountryName(name) { + return COUNTRY_NAME_FIXES[name] ?? name; +} diff --git a/frontend/tests/stores/useAttackerCountriesStore.test.jsx b/frontend/tests/stores/useAttackerCountriesStore.test.jsx index 37da99bd..e0293269 100644 --- a/frontend/tests/stores/useAttackerCountriesStore.test.jsx +++ b/frontend/tests/stores/useAttackerCountriesStore.test.jsx @@ -19,7 +19,7 @@ const createDeferred = () => { describe("useAttackerCountriesStore", () => { beforeEach(() => { useAttackerCountriesStore.setState({ - rawData: [], + normalizedData: [], countryDataMap: {}, maxCount: 0, loading: false, @@ -33,7 +33,7 @@ describe("useAttackerCountriesStore", () => { describe("Initial State", () => { test("initial state is correct", () => { const state = useAttackerCountriesStore.getState(); - expect(state.rawData).toEqual([]); + expect(state.normalizedData).toEqual([]); expect(state.countryDataMap).toEqual({}); expect(state.maxCount).toBe(0); expect(state.loading).toBe(false); @@ -55,7 +55,10 @@ describe("useAttackerCountriesStore", () => { await useAttackerCountriesStore.getState().fetchData(mockRange); const state = useAttackerCountriesStore.getState(); - expect(state.rawData).toEqual(mockData); + expect(state.normalizedData).toEqual([ + { country: "United States of America", count: 100 }, + { country: "Italy", count: 50 }, + ]); expect(state.countryDataMap).toEqual({ "United States of America": 100, Italy: 50, @@ -140,7 +143,10 @@ describe("useAttackerCountriesStore", () => { await Promise.all([fetch1, fetch2]); expect(axios.get).toHaveBeenCalledTimes(2); - expect(useAttackerCountriesStore.getState().rawData).toEqual(mockData); + expect(useAttackerCountriesStore.getState().normalizedData).toEqual([ + { country: "United States of America", count: 100 }, + { country: "Italy", count: 50 }, + ]); }); test("sets error state on failure", async () => { @@ -184,7 +190,10 @@ describe("useAttackerCountriesStore", () => { // Now loading should be false expect(useAttackerCountriesStore.getState().loading).toBe(false); - expect(useAttackerCountriesStore.getState().rawData).toEqual(mockData); + expect(useAttackerCountriesStore.getState().normalizedData).toEqual([ + { country: "United States of America", count: 100 }, + { country: "Italy", count: 50 }, + ]); }); }); }); diff --git a/frontend/tests/utils/country.test.js b/frontend/tests/utils/country.test.js new file mode 100644 index 00000000..8457b830 --- /dev/null +++ b/frontend/tests/utils/country.test.js @@ -0,0 +1,25 @@ +import { describe, it, expect } from "vitest"; +import { normalizeCountryName } from "../../src/utils/country"; + +describe("normalizeCountryName", () => { + it("should normalize known mismatched names", () => { + expect(normalizeCountryName("United States")).toBe( + "United States of America", + ); + expect(normalizeCountryName("Czech Republic")).toBe("Czechia"); + expect(normalizeCountryName("Ivory Coast")).toBe("Côte d'Ivoire"); + expect(normalizeCountryName("South Sudan")).toBe("S. Sudan"); + }); + + it("should return the same name if no mismatch is known", () => { + expect(normalizeCountryName("Italy")).toBe("Italy"); + expect(normalizeCountryName("Brazil")).toBe("Brazil"); + expect(normalizeCountryName("France")).toBe("France"); + }); + + it("should handle edge cases like empty strings or nulls", () => { + expect(normalizeCountryName("")).toBe(""); + expect(normalizeCountryName(null)).toBe(null); + expect(normalizeCountryName(undefined)).toBe(undefined); + }); +}); From 3badb0f26b0b9daaa876b3c5b8c729b58c1e865e Mon Sep 17 00:00:00 2001 From: armoredvortex <66690593+armoredvortex@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:09:43 +0530 Subject: [PATCH 18/27] Accessibility improvements using pa11y. Closes #1179 (#1182) * enhance accessibility by eliminating pa11y reported errors * use variables for new css props Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- frontend/src/components/auth/Login.jsx | 2 +- frontend/src/components/feeds/tableColumns.jsx | 2 +- frontend/src/components/home/Home.jsx | 16 +++++++++------- .../src/components/me/sessions/APIaccess.jsx | 3 +++ .../src/components/me/sessions/SessionList.jsx | 4 +++- frontend/src/layouts/AppFooter.jsx | 3 +++ frontend/src/styles/App.scss | 17 +++++++++++++++++ 7 files changed, 37 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/auth/Login.jsx b/frontend/src/components/auth/Login.jsx index 6fd44ac1..b63922fc 100644 --- a/frontend/src/components/auth/Login.jsx +++ b/frontend/src/components/auth/Login.jsx @@ -78,7 +78,7 @@ function Login() { id="access-info" className="col-12 px-1 text-center" > -
+
 New users
diff --git a/frontend/src/components/feeds/tableColumns.jsx b/frontend/src/components/feeds/tableColumns.jsx index a7de0078..3eb7ce21 100644 --- a/frontend/src/components/feeds/tableColumns.jsx +++ b/frontend/src/components/feeds/tableColumns.jsx @@ -40,7 +40,7 @@ const feedsTableColumns = [ Array.isArray(value) ? (
    {value?.map((val, index) => ( -
  • +
  • {val}
  • ))} diff --git a/frontend/src/components/home/Home.jsx b/frontend/src/components/home/Home.jsx index 9354f214..d3a4017e 100644 --- a/frontend/src/components/home/Home.jsx +++ b/frontend/src/components/home/Home.jsx @@ -19,13 +19,15 @@ function Home() { <> {/* BG Image */} -

    - {versionText} -

    + {versionText && ( +

    + {versionText} +

    + )}
    {/* Content */} diff --git a/frontend/src/components/me/sessions/APIaccess.jsx b/frontend/src/components/me/sessions/APIaccess.jsx index d3363756..6b192979 100644 --- a/frontend/src/components/me/sessions/APIaccess.jsx +++ b/frontend/src/components/me/sessions/APIaccess.jsx @@ -122,6 +122,7 @@ export default function APIAccess() { id="toggle-show-apikey-btn" color="dark" title={tokenVisible ? "Hide API key" : "Show API Key"} + aria-label={tokenVisible ? "Hide API key" : "Show API Key"} className="ms-2 border border-dark" Icon={tokenVisible ? MdVisibility : MdVisibilityOff} onClick={() => setTokenVisible((s) => !s)} @@ -129,6 +130,7 @@ export default function APIAccess() { revokeSessionCb(id, client)} /> ) : ( - current + current )} diff --git a/frontend/src/layouts/AppFooter.jsx b/frontend/src/layouts/AppFooter.jsx index 3f4dfd96..67bd06c9 100644 --- a/frontend/src/layouts/AppFooter.jsx +++ b/frontend/src/layouts/AppFooter.jsx @@ -36,6 +36,7 @@ function AppFooter() { target="_blank" rel="noopener noreferrer" className="ms-3 text-white text-decoration-none" + aria-label="IntelOwl on X" > @@ -44,6 +45,7 @@ function AppFooter() { target="_blank" rel="noopener noreferrer" className="ms-3 text-white text-decoration-none" + aria-label="IntelOwl on GitHub" > @@ -52,6 +54,7 @@ function AppFooter() { target="_blank" rel="noopener noreferrer" className="ms-3 text-white text-decoration-none" + aria-label="IntelOwl on LinkedIn" > diff --git a/frontend/src/styles/App.scss b/frontend/src/styles/App.scss index bb1c1c8d..a4a4a49e 100644 --- a/frontend/src/styles/App.scss +++ b/frontend/src/styles/App.scss @@ -157,3 +157,20 @@ section.fixed-bottom { border-color: $darker; } } + +$dashboard-btn-secondary-bg: #3f7d95; +$dashboard-btn-secondary-bg-hover: #376d82; + +#Dashboard { + .btn.btn-secondary { + background-color: $dashboard-btn-secondary-bg; + border-color: $dashboard-btn-secondary-bg; + } + + .btn.btn-secondary:hover, + .btn.btn-secondary:focus, + .btn.btn-secondary:active { + background-color: $dashboard-btn-secondary-bg-hover; + border-color: $dashboard-btn-secondary-bg-hover; + } +} From b0cf56189b9e00e7b79bef5102ccac4e781c5a19 Mon Sep 17 00:00:00 2001 From: Manik Date: Tue, 7 Apr 2026 19:15:15 +0530 Subject: [PATCH 19/27] fix(docker): fix qcluster permission error on mlmodels volume (#1177) --- docker/default.yml | 3 ++- docker/entrypoint_qcluster.sh | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100755 docker/entrypoint_qcluster.sh diff --git a/docker/default.yml b/docker/default.yml index 7a4fd0d7..699d4abf 100644 --- a/docker/default.yml +++ b/docker/default.yml @@ -71,6 +71,8 @@ services: container_name: greedybear_qcluster restart: unless-stopped stop_grace_period: 3m + entrypoint: + - ./docker/entrypoint_qcluster.sh command: sh -c "python manage.py setup_schedules && exec python manage.py qcluster" volumes: - generic_logs:/var/log/greedybear @@ -82,7 +84,6 @@ services: condition: service_healthy app: condition: service_healthy - user: "www-data:www-data" healthcheck: disable: true diff --git a/docker/entrypoint_qcluster.sh b/docker/entrypoint_qcluster.sh new file mode 100755 index 00000000..965a9421 --- /dev/null +++ b/docker/entrypoint_qcluster.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Fix mlmodels ownership (volumes may retain files owned by a previous UID) +chown -R www-data:www-data /opt/deploy/greedybear/mlmodels + +if [ "$DJANGO_TEST_SERVER" = "True" ]; then + # Dev mode: run as root (needed for hot-reload on volume-mounted source) + exec "$@" +else + # Production mode: drop privileges to www-data before starting qcluster + exec gosu www-data "$@" +fi From 8ec367ce0cbc574fe69f3a952b044ccfd999b1a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:51:14 +0200 Subject: [PATCH 20/27] build(deps-dev): bump vite from 8.0.3 to 8.0.7 in /frontend (#1200) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.3 to 8.0.7. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v8.0.7/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 8.0.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 322 ++++++++++++++++++------------------- frontend/package.json | 2 +- 2 files changed, 161 insertions(+), 163 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 90a9754c..1b00d5d6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -41,7 +41,7 @@ "jsdom": "^29.0.1", "prettier": "^3.8.1", "stylelint": "^17.6.0", - "vite": "^8.0.3", + "vite": "^8.0.7", "vitest": "^4.0.18" } }, @@ -722,7 +722,6 @@ "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", "dev": true, "optional": true, - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" @@ -734,7 +733,6 @@ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", "dev": true, "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -745,7 +743,6 @@ "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -1109,9 +1106,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.122.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", - "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.123.0.tgz", + "integrity": "sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==", "dev": true, "funding": { "url": "https://github.com/sponsors/Boshen" @@ -1430,9 +1427,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.13.tgz", + "integrity": "sha512-5ZiiecKH2DXAVJTNN13gNMUcCDg4Jy8ZjbXEsPnqa248wgOVeYRX0iqXXD5Jz4bI9BFHgKsI2qmyJynstbmr+g==", "cpu": [ "arm64" ], @@ -1446,9 +1443,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.13.tgz", + "integrity": "sha512-tz/v/8G77seu8zAB3A5sK3UFoOl06zcshEzhUO62sAEtrEuW/H1CcyoupOrD+NbQJytYgA4CppXPzlrmp4JZKA==", "cpu": [ "arm64" ], @@ -1462,9 +1459,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.13.tgz", + "integrity": "sha512-8DakphqOz8JrMYWTJmWA+vDJxut6LijZ8Xcdc4flOlAhU7PNVwo2MaWBF9iXjJAPo5rC/IxEFZDhJ3GC7NHvug==", "cpu": [ "x64" ], @@ -1478,9 +1475,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.13.tgz", + "integrity": "sha512-4wBQFfjDuXYN/SVI8inBF3Aa+isq40rc6VMFbk5jcpolUBTe5cYnMsHZ51nFWsx3PVyyNN3vgoESki0Hmr/4BA==", "cpu": [ "x64" ], @@ -1494,9 +1491,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", - "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.13.tgz", + "integrity": "sha512-JW/e4yPIXLms+jmnbwwy5LA/LxVwZUWLN8xug+V200wzaVi5TEGIWQlh8o91gWYFxW609euI98OCCemmWGuPrw==", "cpu": [ "arm" ], @@ -1510,9 +1507,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-ZfKWpXiUymDnavepCaM6KG/uGydJ4l2nBmMxg60Ci4CbeefpqjPWpfaZM7PThOhk2dssqBAcwLc6rAyr0uTdXg==", "cpu": [ "arm64" ], @@ -1526,9 +1523,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.13.tgz", + "integrity": "sha512-bmRg3O6Z0gq9yodKKWCIpnlH051sEfdVwt+6m5UDffAQMUUqU0xjnQqqAUm+Gu7ofAAly9DqiQDtKu2nPDEABA==", "cpu": [ "arm64" ], @@ -1542,9 +1539,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-8Wtnbw4k7pMYN9B/mOEAsQ8HOiq7AZ31Ig4M9BKn2So4xRaFEhtCSa4ZJaOutOWq50zpgR4N5+L/opnlaCx8wQ==", "cpu": [ "ppc64" ], @@ -1558,9 +1555,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-D/0Nlo8mQuxSMohNJUF2lDXWRsFDsHldfRRgD9bRgktj+EndGPj4DOV37LqDKPYS+osdyhZEH7fTakTAEcW7qg==", "cpu": [ "s390x" ], @@ -1574,9 +1571,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-eRrPvat2YaVQcwwKi/JzOP6MKf1WRnOCr+VaI3cTWz3ZoLcP/654z90lVCJ4dAuMEpPdke0n+qyAqXDZdIC4rA==", "cpu": [ "x64" ], @@ -1590,9 +1587,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.13.tgz", + "integrity": "sha512-PsdONiFRp8hR8KgVjTWjZ9s7uA3uueWL0t74/cKHfM4dR5zXYv4AjB8BvA+QDToqxAFg4ZkcVEqeu5F7inoz5w==", "cpu": [ "x64" ], @@ -1606,9 +1603,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.13.tgz", + "integrity": "sha512-hCNXgC5dI3TVOLrPT++PKFNZ+1EtS0mLQwfXXXSUD/+rGlB65gZDwN/IDuxLpQP4x8RYYHqGomlUXzpO8aVI2w==", "cpu": [ "arm64" ], @@ -1622,25 +1619,27 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", - "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.13.tgz", + "integrity": "sha512-viLS5C5et8NFtLWw9Sw3M/w4vvnVkbWkO7wSNh3C+7G1+uCkGpr6PcjNDSFcNtmXY/4trjPBqUfcOL+P3sWy/g==", "cpu": [ "wasm32" ], "dev": true, "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.9.1", + "@emnapi/runtime": "1.9.1", + "@napi-rs/wasm-runtime": "^1.1.2" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.13.tgz", + "integrity": "sha512-Fqa3Tlt1xL4wzmAYxGNFV36Hb+VfPc9PYU+E25DAnswXv3ODDu/yyWjQDbXMo5AGWkQVjLgQExuVu8I/UaZhPQ==", "cpu": [ "arm64" ], @@ -1654,9 +1653,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.13.tgz", + "integrity": "sha512-/pLI5kPkGEi44TDlnbio3St/5gUFeN51YWNAk/Gnv6mEQBOahRBh52qVFVBpmrnU01n2yysvBML9Ynu7K4kGAQ==", "cpu": [ "x64" ], @@ -7309,13 +7308,13 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", - "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.13.tgz", + "integrity": "sha512-bvVj8YJmf0rq4pSFmH7laLa6pYrhghv3PRzrCdRAr23g66zOKVJ4wkvFtgohtPLWmthgg8/rkaqRHrpUEh0Zbw==", "dev": true, "dependencies": { - "@oxc-project/types": "=0.122.0", - "@rolldown/pluginutils": "1.0.0-rc.12" + "@oxc-project/types": "=0.123.0", + "@rolldown/pluginutils": "1.0.0-rc.13" }, "bin": { "rolldown": "bin/cli.mjs" @@ -7324,27 +7323,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-x64": "1.0.0-rc.12", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" + "@rolldown/binding-android-arm64": "1.0.0-rc.13", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.13", + "@rolldown/binding-darwin-x64": "1.0.0-rc.13", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.13", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.13", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.13", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.13", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.13", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.13", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.13", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.13", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.13", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.13", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.13" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", - "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz", + "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==", "dev": true }, "node_modules/rtl-css-js": { @@ -8756,15 +8755,15 @@ } }, "node_modules/vite": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", - "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.7.tgz", + "integrity": "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==", "dev": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.12", + "rolldown": "1.0.0-rc.13", "tinyglobby": "^0.2.15" }, "bin": { @@ -8782,7 +8781,7 @@ "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", @@ -9614,7 +9613,6 @@ "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", "dev": true, "optional": true, - "peer": true, "requires": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" @@ -9626,7 +9624,6 @@ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", "dev": true, "optional": true, - "peer": true, "requires": { "tslib": "^2.4.0" } @@ -9637,7 +9634,6 @@ "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, "optional": true, - "peer": true, "requires": { "tslib": "^2.4.0" } @@ -9917,9 +9913,9 @@ } }, "@oxc-project/types": { - "version": "0.122.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", - "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "version": "0.123.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.123.0.tgz", + "integrity": "sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==", "dev": true }, "@parcel/watcher": { @@ -10044,110 +10040,112 @@ "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==" }, "@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.13.tgz", + "integrity": "sha512-5ZiiecKH2DXAVJTNN13gNMUcCDg4Jy8ZjbXEsPnqa248wgOVeYRX0iqXXD5Jz4bI9BFHgKsI2qmyJynstbmr+g==", "dev": true, "optional": true }, "@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.13.tgz", + "integrity": "sha512-tz/v/8G77seu8zAB3A5sK3UFoOl06zcshEzhUO62sAEtrEuW/H1CcyoupOrD+NbQJytYgA4CppXPzlrmp4JZKA==", "dev": true, "optional": true }, "@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.13.tgz", + "integrity": "sha512-8DakphqOz8JrMYWTJmWA+vDJxut6LijZ8Xcdc4flOlAhU7PNVwo2MaWBF9iXjJAPo5rC/IxEFZDhJ3GC7NHvug==", "dev": true, "optional": true }, "@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.13.tgz", + "integrity": "sha512-4wBQFfjDuXYN/SVI8inBF3Aa+isq40rc6VMFbk5jcpolUBTe5cYnMsHZ51nFWsx3PVyyNN3vgoESki0Hmr/4BA==", "dev": true, "optional": true }, "@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", - "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.13.tgz", + "integrity": "sha512-JW/e4yPIXLms+jmnbwwy5LA/LxVwZUWLN8xug+V200wzaVi5TEGIWQlh8o91gWYFxW609euI98OCCemmWGuPrw==", "dev": true, "optional": true }, "@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-ZfKWpXiUymDnavepCaM6KG/uGydJ4l2nBmMxg60Ci4CbeefpqjPWpfaZM7PThOhk2dssqBAcwLc6rAyr0uTdXg==", "dev": true, "optional": true }, "@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.13.tgz", + "integrity": "sha512-bmRg3O6Z0gq9yodKKWCIpnlH051sEfdVwt+6m5UDffAQMUUqU0xjnQqqAUm+Gu7ofAAly9DqiQDtKu2nPDEABA==", "dev": true, "optional": true }, "@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-8Wtnbw4k7pMYN9B/mOEAsQ8HOiq7AZ31Ig4M9BKn2So4xRaFEhtCSa4ZJaOutOWq50zpgR4N5+L/opnlaCx8wQ==", "dev": true, "optional": true }, "@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-D/0Nlo8mQuxSMohNJUF2lDXWRsFDsHldfRRgD9bRgktj+EndGPj4DOV37LqDKPYS+osdyhZEH7fTakTAEcW7qg==", "dev": true, "optional": true }, "@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.13.tgz", + "integrity": "sha512-eRrPvat2YaVQcwwKi/JzOP6MKf1WRnOCr+VaI3cTWz3ZoLcP/654z90lVCJ4dAuMEpPdke0n+qyAqXDZdIC4rA==", "dev": true, "optional": true }, "@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.13.tgz", + "integrity": "sha512-PsdONiFRp8hR8KgVjTWjZ9s7uA3uueWL0t74/cKHfM4dR5zXYv4AjB8BvA+QDToqxAFg4ZkcVEqeu5F7inoz5w==", "dev": true, "optional": true }, "@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.13.tgz", + "integrity": "sha512-hCNXgC5dI3TVOLrPT++PKFNZ+1EtS0mLQwfXXXSUD/+rGlB65gZDwN/IDuxLpQP4x8RYYHqGomlUXzpO8aVI2w==", "dev": true, "optional": true }, "@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", - "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.13.tgz", + "integrity": "sha512-viLS5C5et8NFtLWw9Sw3M/w4vvnVkbWkO7wSNh3C+7G1+uCkGpr6PcjNDSFcNtmXY/4trjPBqUfcOL+P3sWy/g==", "dev": true, "optional": true, "requires": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.9.1", + "@emnapi/runtime": "1.9.1", + "@napi-rs/wasm-runtime": "^1.1.2" } }, "@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.13.tgz", + "integrity": "sha512-Fqa3Tlt1xL4wzmAYxGNFV36Hb+VfPc9PYU+E25DAnswXv3ODDu/yyWjQDbXMo5AGWkQVjLgQExuVu8I/UaZhPQ==", "dev": true, "optional": true }, "@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.13.tgz", + "integrity": "sha512-/pLI5kPkGEi44TDlnbio3St/5gUFeN51YWNAk/Gnv6mEQBOahRBh52qVFVBpmrnU01n2yysvBML9Ynu7K4kGAQ==", "dev": true, "optional": true }, @@ -14116,34 +14114,34 @@ } }, "rolldown": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", - "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", - "dev": true, - "requires": { - "@oxc-project/types": "=0.122.0", - "@rolldown/binding-android-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-x64": "1.0.0-rc.12", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12", - "@rolldown/pluginutils": "1.0.0-rc.12" + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.13.tgz", + "integrity": "sha512-bvVj8YJmf0rq4pSFmH7laLa6pYrhghv3PRzrCdRAr23g66zOKVJ4wkvFtgohtPLWmthgg8/rkaqRHrpUEh0Zbw==", + "dev": true, + "requires": { + "@oxc-project/types": "=0.123.0", + "@rolldown/binding-android-arm64": "1.0.0-rc.13", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.13", + "@rolldown/binding-darwin-x64": "1.0.0-rc.13", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.13", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.13", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.13", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.13", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.13", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.13", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.13", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.13", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.13", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.13", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.13", + "@rolldown/pluginutils": "1.0.0-rc.13" }, "dependencies": { "@rolldown/pluginutils": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", - "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz", + "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==", "dev": true } } @@ -15126,16 +15124,16 @@ } }, "vite": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", - "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.7.tgz", + "integrity": "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==", "dev": true, "requires": { "fsevents": "~2.3.3", "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.12", + "rolldown": "1.0.0-rc.13", "tinyglobby": "^0.2.15" }, "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 70fbf106..d340606c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -60,7 +60,7 @@ "jsdom": "^29.0.1", "prettier": "^3.8.1", "stylelint": "^17.6.0", - "vite": "^8.0.3", + "vite": "^8.0.7", "vitest": "^4.0.18" } } From 75ad85e7afe2689749102bae7a7363214e3132b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:51:32 +0200 Subject: [PATCH 21/27] build(deps-dev): bump @vitest/coverage-v8 in /frontend (#1199) Bumps [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) from 4.1.2 to 4.1.3. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.3/packages/coverage-v8) --- updated-dependencies: - dependency-name: "@vitest/coverage-v8" dependency-version: 4.1.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 198 +++++++++++++++++++------------------ frontend/package.json | 2 +- 2 files changed, 104 insertions(+), 96 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1b00d5d6..0153bd84 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,7 +31,7 @@ "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^14.6.1", "@vitejs/plugin-react": "^6.0.1", - "@vitest/coverage-v8": "^4.1.2", + "@vitest/coverage-v8": "^4.1.3", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", @@ -1961,13 +1961,13 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.2.tgz", - "integrity": "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.3.tgz", + "integrity": "sha512-/MBdrkA8t6hbdCWFKs09dPik774xvs4Z6L4bycdCxYNLHM8oZuRyosumQMG19LUlBsB6GeVpL1q4kFFazvyKGA==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.2", + "@vitest/utils": "4.1.3", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -1981,8 +1981,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.2", - "vitest": "4.1.2" + "@vitest/browser": "4.1.3", + "vitest": "4.1.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1991,15 +1991,15 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", - "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.3.tgz", + "integrity": "sha512-CW8Q9KMtXDGHj0vCsqui0M5KqRsu0zm0GNDW7Gd3U7nZ2RFpPKSCpeCXoT+/+5zr1TNlsoQRDEz+LzZUyq6gnQ==", "dev": true, "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/spy": "4.1.3", + "@vitest/utils": "4.1.3", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -2008,12 +2008,12 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", - "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.3.tgz", + "integrity": "sha512-XN3TrycitDQSzGRnec/YWgoofkYRhouyVQj4YNsJ5r/STCUFqMrP4+oxEv3e7ZbLi4og5kIHrZwekDJgw6hcjw==", "dev": true, "dependencies": { - "@vitest/spy": "4.1.2", + "@vitest/spy": "4.1.3", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2034,9 +2034,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.3.tgz", + "integrity": "sha512-hYqqwuMbpkkBodpRh4k4cQSOELxXky1NfMmQvOfKvV8zQHz8x8Dla+2wzElkMkBvSAJX5TRGHJAQvK0TcOafwg==", "dev": true, "dependencies": { "tinyrainbow": "^3.1.0" @@ -2046,12 +2046,12 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", - "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.3.tgz", + "integrity": "sha512-VwgOz5MmT0KhlUj40h02LWDpUBVpflZ/b7xZFA25F29AJzIrE+SMuwzFf0b7t4EXdwRNX61C3B6auIXQTR3ttA==", "dev": true, "dependencies": { - "@vitest/utils": "4.1.2", + "@vitest/utils": "4.1.3", "pathe": "^2.0.3" }, "funding": { @@ -2059,13 +2059,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", - "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.3.tgz", + "integrity": "sha512-9l+k/J9KG5wPJDX9BcFFzhhwNjwkRb8RsnYhaT1vPY7OufxmQFc9sZzScRCPTiETzl37mrIWVY9zxzmdVeJwDQ==", "dev": true, "dependencies": { - "@vitest/pretty-format": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/pretty-format": "4.1.3", + "@vitest/utils": "4.1.3", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2074,21 +2074,21 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", - "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.3.tgz", + "integrity": "sha512-ujj5Uwxagg4XUIfAUyRQxAg631BP6e9joRiN99mr48Bg9fRs+5mdUElhOoZ6rP5mBr8Bs3lmrREnkrQWkrsTCw==", "dev": true, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", - "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.3.tgz", + "integrity": "sha512-Pc/Oexse/khOWsGB+w3q4yzA4te7W4gpZZAvk+fr8qXfTURZUMj5i7kuxsNK5mP/dEB6ao3jfr0rs17fHhbHdw==", "dev": true, "dependencies": { - "@vitest/pretty-format": "4.1.2", + "@vitest/pretty-format": "4.1.3", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -8844,18 +8844,18 @@ } }, "node_modules/vitest": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", - "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", - "dev": true, - "dependencies": { - "@vitest/expect": "4.1.2", - "@vitest/mocker": "4.1.2", - "@vitest/pretty-format": "4.1.2", - "@vitest/runner": "4.1.2", - "@vitest/snapshot": "4.1.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.3.tgz", + "integrity": "sha512-DBc4Tx0MPNsqb9isoyOq00lHftVx/KIU44QOm2q59npZyLUkENn8TMFsuzuO+4U2FUa9rgbbPt3udrP25GcjXw==", + "dev": true, + "dependencies": { + "@vitest/expect": "4.1.3", + "@vitest/mocker": "4.1.3", + "@vitest/pretty-format": "4.1.3", + "@vitest/runner": "4.1.3", + "@vitest/snapshot": "4.1.3", + "@vitest/spy": "4.1.3", + "@vitest/utils": "4.1.3", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -8883,10 +8883,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.2", - "@vitest/browser-preview": "4.1.2", - "@vitest/browser-webdriverio": "4.1.2", - "@vitest/ui": "4.1.2", + "@vitest/browser-playwright": "4.1.3", + "@vitest/browser-preview": "4.1.3", + "@vitest/browser-webdriverio": "4.1.3", + "@vitest/coverage-istanbul": "4.1.3", + "@vitest/coverage-v8": "4.1.3", + "@vitest/ui": "4.1.3", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -8910,6 +8912,12 @@ "@vitest/browser-webdriverio": { "optional": true }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, "@vitest/ui": { "optional": true }, @@ -10392,13 +10400,13 @@ } }, "@vitest/coverage-v8": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.2.tgz", - "integrity": "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.3.tgz", + "integrity": "sha512-/MBdrkA8t6hbdCWFKs09dPik774xvs4Z6L4bycdCxYNLHM8oZuRyosumQMG19LUlBsB6GeVpL1q4kFFazvyKGA==", "dev": true, "requires": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.2", + "@vitest/utils": "4.1.3", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -10410,74 +10418,74 @@ } }, "@vitest/expect": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", - "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.3.tgz", + "integrity": "sha512-CW8Q9KMtXDGHj0vCsqui0M5KqRsu0zm0GNDW7Gd3U7nZ2RFpPKSCpeCXoT+/+5zr1TNlsoQRDEz+LzZUyq6gnQ==", "dev": true, "requires": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/spy": "4.1.3", + "@vitest/utils": "4.1.3", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "@vitest/mocker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", - "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.3.tgz", + "integrity": "sha512-XN3TrycitDQSzGRnec/YWgoofkYRhouyVQj4YNsJ5r/STCUFqMrP4+oxEv3e7ZbLi4og5kIHrZwekDJgw6hcjw==", "dev": true, "requires": { - "@vitest/spy": "4.1.2", + "@vitest/spy": "4.1.3", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" } }, "@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.3.tgz", + "integrity": "sha512-hYqqwuMbpkkBodpRh4k4cQSOELxXky1NfMmQvOfKvV8zQHz8x8Dla+2wzElkMkBvSAJX5TRGHJAQvK0TcOafwg==", "dev": true, "requires": { "tinyrainbow": "^3.1.0" } }, "@vitest/runner": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", - "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.3.tgz", + "integrity": "sha512-VwgOz5MmT0KhlUj40h02LWDpUBVpflZ/b7xZFA25F29AJzIrE+SMuwzFf0b7t4EXdwRNX61C3B6auIXQTR3ttA==", "dev": true, "requires": { - "@vitest/utils": "4.1.2", + "@vitest/utils": "4.1.3", "pathe": "^2.0.3" } }, "@vitest/snapshot": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", - "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.3.tgz", + "integrity": "sha512-9l+k/J9KG5wPJDX9BcFFzhhwNjwkRb8RsnYhaT1vPY7OufxmQFc9sZzScRCPTiETzl37mrIWVY9zxzmdVeJwDQ==", "dev": true, "requires": { - "@vitest/pretty-format": "4.1.2", - "@vitest/utils": "4.1.2", + "@vitest/pretty-format": "4.1.3", + "@vitest/utils": "4.1.3", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "@vitest/spy": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", - "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.3.tgz", + "integrity": "sha512-ujj5Uwxagg4XUIfAUyRQxAg631BP6e9joRiN99mr48Bg9fRs+5mdUElhOoZ6rP5mBr8Bs3lmrREnkrQWkrsTCw==", "dev": true }, "@vitest/utils": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", - "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.3.tgz", + "integrity": "sha512-Pc/Oexse/khOWsGB+w3q4yzA4te7W4gpZZAvk+fr8qXfTURZUMj5i7kuxsNK5mP/dEB6ao3jfr0rs17fHhbHdw==", "dev": true, "requires": { - "@vitest/pretty-format": "4.1.2", + "@vitest/pretty-format": "4.1.3", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -15146,18 +15154,18 @@ } }, "vitest": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", - "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", - "dev": true, - "requires": { - "@vitest/expect": "4.1.2", - "@vitest/mocker": "4.1.2", - "@vitest/pretty-format": "4.1.2", - "@vitest/runner": "4.1.2", - "@vitest/snapshot": "4.1.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.3.tgz", + "integrity": "sha512-DBc4Tx0MPNsqb9isoyOq00lHftVx/KIU44QOm2q59npZyLUkENn8TMFsuzuO+4U2FUa9rgbbPt3udrP25GcjXw==", + "dev": true, + "requires": { + "@vitest/expect": "4.1.3", + "@vitest/mocker": "4.1.3", + "@vitest/pretty-format": "4.1.3", + "@vitest/runner": "4.1.3", + "@vitest/snapshot": "4.1.3", + "@vitest/spy": "4.1.3", + "@vitest/utils": "4.1.3", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", diff --git a/frontend/package.json b/frontend/package.json index d340606c..c6fccec3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,7 +50,7 @@ "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^14.6.1", "@vitejs/plugin-react": "^6.0.1", - "@vitest/coverage-v8": "^4.1.2", + "@vitest/coverage-v8": "^4.1.3", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", From e366ade4e59e7f6288a481ab9ba44d0f608d3284 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:53:12 +0200 Subject: [PATCH 22/27] build(deps-dev): bump jsdom from 29.0.1 to 29.0.2 in /frontend (#1198) Bumps [jsdom](https://github.com/jsdom/jsdom) from 29.0.1 to 29.0.2. - [Release notes](https://github.com/jsdom/jsdom/releases) - [Commits](https://github.com/jsdom/jsdom/compare/v29.0.1...v29.0.2) --- updated-dependencies: - dependency-name: jsdom dependency-version: 29.0.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 90 ++++++++++++-------------------------- frontend/package.json | 2 +- 2 files changed, 28 insertions(+), 64 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0153bd84..7977d67a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -38,7 +38,7 @@ "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^4.6.0", - "jsdom": "^29.0.1", + "jsdom": "^29.0.2", "prettier": "^3.8.1", "stylelint": "^17.6.0", "vite": "^8.0.7", @@ -52,41 +52,30 @@ "dev": true }, "node_modules/@asamuzakjp/css-color": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", - "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.8.tgz", + "integrity": "sha512-OISPR9c2uPo23rUdvfEQiLPjoMLOpEeLNnP5iGkxr6tDDxJd3NjD+6fxY0mdaMbIPUjFGL4HFOJqLvow5q4aqQ==", "dev": true, "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0", - "lru-cache": "^11.2.6" + "@csstools/css-tokenizer": "^4.0.0" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "dev": true, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@asamuzakjp/dom-selector": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.4.tgz", - "integrity": "sha512-jXR6x4AcT3eIrS2fSNAwJpwirOkGcd+E7F7CP3zjdTqz9B/2huHOL8YJZBgekKwLML+u7qB/6P1LXQuMScsx0w==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.8.tgz", + "integrity": "sha512-erMO6FgtM02dC24NGm0xufMzWz5OF0wXKR7BpvGD973bq/GbmR8/DbxNZbj0YevQ5hlToJaWSVK/G9/NDgGEVw==", "dev": true, "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.7" + "is-potential-custom-element-name": "^1.0.1" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" @@ -105,15 +94,6 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "dev": true, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@asamuzakjp/dom-selector/node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -5435,13 +5415,13 @@ } }, "node_modules/jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", - "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", + "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", "dev": true, "dependencies": { - "@asamuzakjp/css-color": "^5.0.1", - "@asamuzakjp/dom-selector": "^7.0.3", + "@asamuzakjp/css-color": "^5.1.5", + "@asamuzakjp/dom-selector": "^7.0.6", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.1", "@exodus/bytes": "^1.15.0", @@ -9206,37 +9186,27 @@ "dev": true }, "@asamuzakjp/css-color": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", - "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.8.tgz", + "integrity": "sha512-OISPR9c2uPo23rUdvfEQiLPjoMLOpEeLNnP5iGkxr6tDDxJd3NjD+6fxY0mdaMbIPUjFGL4HFOJqLvow5q4aqQ==", "dev": true, "requires": { "@csstools/css-calc": "^3.1.1", "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0", - "lru-cache": "^11.2.6" - }, - "dependencies": { - "lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "dev": true - } + "@csstools/css-tokenizer": "^4.0.0" } }, "@asamuzakjp/dom-selector": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.4.tgz", - "integrity": "sha512-jXR6x4AcT3eIrS2fSNAwJpwirOkGcd+E7F7CP3zjdTqz9B/2huHOL8YJZBgekKwLML+u7qB/6P1LXQuMScsx0w==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.8.tgz", + "integrity": "sha512-erMO6FgtM02dC24NGm0xufMzWz5OF0wXKR7BpvGD973bq/GbmR8/DbxNZbj0YevQ5hlToJaWSVK/G9/NDgGEVw==", "dev": true, "requires": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.7" + "is-potential-custom-element-name": "^1.0.1" }, "dependencies": { "css-tree": { @@ -9249,12 +9219,6 @@ "source-map-js": "^1.2.1" } }, - "lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "dev": true - }, "mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -12889,13 +12853,13 @@ } }, "jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", - "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", + "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", "dev": true, "requires": { - "@asamuzakjp/css-color": "^5.0.1", - "@asamuzakjp/dom-selector": "^7.0.3", + "@asamuzakjp/css-color": "^5.1.5", + "@asamuzakjp/dom-selector": "^7.0.6", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.1", "@exodus/bytes": "^1.15.0", diff --git a/frontend/package.json b/frontend/package.json index c6fccec3..aa660b07 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,7 +57,7 @@ "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^4.6.0", - "jsdom": "^29.0.1", + "jsdom": "^29.0.2", "prettier": "^3.8.1", "stylelint": "^17.6.0", "vite": "^8.0.7", From c535c6e5c8da60c89cac0785a3107483c251de0b Mon Sep 17 00:00:00 2001 From: tim <46972822+regulartim@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:38:43 +0200 Subject: [PATCH 23/27] Revert "build(deps-dev): bump vite from 8.0.3 to 8.0.7 in /frontend (#1200)" (#1204) This reverts commit 8ec367ce0cbc574fe69f3a952b044ccfd999b1a7. --- frontend/package-lock.json | 322 +++++++++++++++++++------------------ frontend/package.json | 2 +- 2 files changed, 163 insertions(+), 161 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7977d67a..f6a5744a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -41,7 +41,7 @@ "jsdom": "^29.0.2", "prettier": "^3.8.1", "stylelint": "^17.6.0", - "vite": "^8.0.7", + "vite": "^8.0.3", "vitest": "^4.0.18" } }, @@ -702,6 +702,7 @@ "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", "dev": true, "optional": true, + "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" @@ -713,6 +714,7 @@ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", "dev": true, "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -723,6 +725,7 @@ "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -1086,9 +1089,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.123.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.123.0.tgz", - "integrity": "sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==", + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", "dev": true, "funding": { "url": "https://github.com/sponsors/Boshen" @@ -1407,9 +1410,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-5ZiiecKH2DXAVJTNN13gNMUcCDg4Jy8ZjbXEsPnqa248wgOVeYRX0iqXXD5Jz4bI9BFHgKsI2qmyJynstbmr+g==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", "cpu": [ "arm64" ], @@ -1423,9 +1426,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-tz/v/8G77seu8zAB3A5sK3UFoOl06zcshEzhUO62sAEtrEuW/H1CcyoupOrD+NbQJytYgA4CppXPzlrmp4JZKA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", "cpu": [ "arm64" ], @@ -1439,9 +1442,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.13.tgz", - "integrity": "sha512-8DakphqOz8JrMYWTJmWA+vDJxut6LijZ8Xcdc4flOlAhU7PNVwo2MaWBF9iXjJAPo5rC/IxEFZDhJ3GC7NHvug==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", "cpu": [ "x64" ], @@ -1455,9 +1458,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.13.tgz", - "integrity": "sha512-4wBQFfjDuXYN/SVI8inBF3Aa+isq40rc6VMFbk5jcpolUBTe5cYnMsHZ51nFWsx3PVyyNN3vgoESki0Hmr/4BA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", "cpu": [ "x64" ], @@ -1471,9 +1474,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.13.tgz", - "integrity": "sha512-JW/e4yPIXLms+jmnbwwy5LA/LxVwZUWLN8xug+V200wzaVi5TEGIWQlh8o91gWYFxW609euI98OCCemmWGuPrw==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", "cpu": [ "arm" ], @@ -1487,9 +1490,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-ZfKWpXiUymDnavepCaM6KG/uGydJ4l2nBmMxg60Ci4CbeefpqjPWpfaZM7PThOhk2dssqBAcwLc6rAyr0uTdXg==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", "cpu": [ "arm64" ], @@ -1503,9 +1506,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.13.tgz", - "integrity": "sha512-bmRg3O6Z0gq9yodKKWCIpnlH051sEfdVwt+6m5UDffAQMUUqU0xjnQqqAUm+Gu7ofAAly9DqiQDtKu2nPDEABA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", "cpu": [ "arm64" ], @@ -1519,9 +1522,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-8Wtnbw4k7pMYN9B/mOEAsQ8HOiq7AZ31Ig4M9BKn2So4xRaFEhtCSa4ZJaOutOWq50zpgR4N5+L/opnlaCx8wQ==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", "cpu": [ "ppc64" ], @@ -1535,9 +1538,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-D/0Nlo8mQuxSMohNJUF2lDXWRsFDsHldfRRgD9bRgktj+EndGPj4DOV37LqDKPYS+osdyhZEH7fTakTAEcW7qg==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", "cpu": [ "s390x" ], @@ -1551,9 +1554,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-eRrPvat2YaVQcwwKi/JzOP6MKf1WRnOCr+VaI3cTWz3ZoLcP/654z90lVCJ4dAuMEpPdke0n+qyAqXDZdIC4rA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", "cpu": [ "x64" ], @@ -1567,9 +1570,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.13.tgz", - "integrity": "sha512-PsdONiFRp8hR8KgVjTWjZ9s7uA3uueWL0t74/cKHfM4dR5zXYv4AjB8BvA+QDToqxAFg4ZkcVEqeu5F7inoz5w==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", "cpu": [ "x64" ], @@ -1583,9 +1586,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-hCNXgC5dI3TVOLrPT++PKFNZ+1EtS0mLQwfXXXSUD/+rGlB65gZDwN/IDuxLpQP4x8RYYHqGomlUXzpO8aVI2w==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", "cpu": [ "arm64" ], @@ -1599,27 +1602,25 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.13.tgz", - "integrity": "sha512-viLS5C5et8NFtLWw9Sw3M/w4vvnVkbWkO7wSNh3C+7G1+uCkGpr6PcjNDSFcNtmXY/4trjPBqUfcOL+P3sWy/g==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", "cpu": [ "wasm32" ], "dev": true, "optional": true, "dependencies": { - "@emnapi/core": "1.9.1", - "@emnapi/runtime": "1.9.1", - "@napi-rs/wasm-runtime": "^1.1.2" + "@napi-rs/wasm-runtime": "^1.1.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.13.tgz", - "integrity": "sha512-Fqa3Tlt1xL4wzmAYxGNFV36Hb+VfPc9PYU+E25DAnswXv3ODDu/yyWjQDbXMo5AGWkQVjLgQExuVu8I/UaZhPQ==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", "cpu": [ "arm64" ], @@ -1633,9 +1634,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.13.tgz", - "integrity": "sha512-/pLI5kPkGEi44TDlnbio3St/5gUFeN51YWNAk/Gnv6mEQBOahRBh52qVFVBpmrnU01n2yysvBML9Ynu7K4kGAQ==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", "cpu": [ "x64" ], @@ -7288,13 +7289,13 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.13.tgz", - "integrity": "sha512-bvVj8YJmf0rq4pSFmH7laLa6pYrhghv3PRzrCdRAr23g66zOKVJ4wkvFtgohtPLWmthgg8/rkaqRHrpUEh0Zbw==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", "dev": true, "dependencies": { - "@oxc-project/types": "=0.123.0", - "@rolldown/pluginutils": "1.0.0-rc.13" + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.12" }, "bin": { "rolldown": "bin/cli.mjs" @@ -7303,27 +7304,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.13", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.13", - "@rolldown/binding-darwin-x64": "1.0.0-rc.13", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.13", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.13", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.13", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.13", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.13", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.13", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.13", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.13" + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz", - "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", "dev": true }, "node_modules/rtl-css-js": { @@ -8735,15 +8736,15 @@ } }, "node_modules/vite": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.7.tgz", - "integrity": "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", + "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", "dev": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.13", + "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "bin": { @@ -8761,7 +8762,7 @@ "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", + "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", @@ -9585,6 +9586,7 @@ "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", "dev": true, "optional": true, + "peer": true, "requires": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" @@ -9596,6 +9598,7 @@ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", "dev": true, "optional": true, + "peer": true, "requires": { "tslib": "^2.4.0" } @@ -9606,6 +9609,7 @@ "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, "optional": true, + "peer": true, "requires": { "tslib": "^2.4.0" } @@ -9885,9 +9889,9 @@ } }, "@oxc-project/types": { - "version": "0.123.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.123.0.tgz", - "integrity": "sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==", + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", "dev": true }, "@parcel/watcher": { @@ -10012,112 +10016,110 @@ "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==" }, "@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-5ZiiecKH2DXAVJTNN13gNMUcCDg4Jy8ZjbXEsPnqa248wgOVeYRX0iqXXD5Jz4bI9BFHgKsI2qmyJynstbmr+g==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", "dev": true, "optional": true }, "@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-tz/v/8G77seu8zAB3A5sK3UFoOl06zcshEzhUO62sAEtrEuW/H1CcyoupOrD+NbQJytYgA4CppXPzlrmp4JZKA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", "dev": true, "optional": true }, "@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.13.tgz", - "integrity": "sha512-8DakphqOz8JrMYWTJmWA+vDJxut6LijZ8Xcdc4flOlAhU7PNVwo2MaWBF9iXjJAPo5rC/IxEFZDhJ3GC7NHvug==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", "dev": true, "optional": true }, "@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.13.tgz", - "integrity": "sha512-4wBQFfjDuXYN/SVI8inBF3Aa+isq40rc6VMFbk5jcpolUBTe5cYnMsHZ51nFWsx3PVyyNN3vgoESki0Hmr/4BA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", "dev": true, "optional": true }, "@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.13.tgz", - "integrity": "sha512-JW/e4yPIXLms+jmnbwwy5LA/LxVwZUWLN8xug+V200wzaVi5TEGIWQlh8o91gWYFxW609euI98OCCemmWGuPrw==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", "dev": true, "optional": true }, "@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-ZfKWpXiUymDnavepCaM6KG/uGydJ4l2nBmMxg60Ci4CbeefpqjPWpfaZM7PThOhk2dssqBAcwLc6rAyr0uTdXg==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", "dev": true, "optional": true }, "@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.13.tgz", - "integrity": "sha512-bmRg3O6Z0gq9yodKKWCIpnlH051sEfdVwt+6m5UDffAQMUUqU0xjnQqqAUm+Gu7ofAAly9DqiQDtKu2nPDEABA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", "dev": true, "optional": true }, "@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-8Wtnbw4k7pMYN9B/mOEAsQ8HOiq7AZ31Ig4M9BKn2So4xRaFEhtCSa4ZJaOutOWq50zpgR4N5+L/opnlaCx8wQ==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", "dev": true, "optional": true }, "@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-D/0Nlo8mQuxSMohNJUF2lDXWRsFDsHldfRRgD9bRgktj+EndGPj4DOV37LqDKPYS+osdyhZEH7fTakTAEcW7qg==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", "dev": true, "optional": true }, "@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.13.tgz", - "integrity": "sha512-eRrPvat2YaVQcwwKi/JzOP6MKf1WRnOCr+VaI3cTWz3ZoLcP/654z90lVCJ4dAuMEpPdke0n+qyAqXDZdIC4rA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", "dev": true, "optional": true }, "@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.13.tgz", - "integrity": "sha512-PsdONiFRp8hR8KgVjTWjZ9s7uA3uueWL0t74/cKHfM4dR5zXYv4AjB8BvA+QDToqxAFg4ZkcVEqeu5F7inoz5w==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", "dev": true, "optional": true }, "@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.13.tgz", - "integrity": "sha512-hCNXgC5dI3TVOLrPT++PKFNZ+1EtS0mLQwfXXXSUD/+rGlB65gZDwN/IDuxLpQP4x8RYYHqGomlUXzpO8aVI2w==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", "dev": true, "optional": true }, "@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.13.tgz", - "integrity": "sha512-viLS5C5et8NFtLWw9Sw3M/w4vvnVkbWkO7wSNh3C+7G1+uCkGpr6PcjNDSFcNtmXY/4trjPBqUfcOL+P3sWy/g==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", "dev": true, "optional": true, "requires": { - "@emnapi/core": "1.9.1", - "@emnapi/runtime": "1.9.1", - "@napi-rs/wasm-runtime": "^1.1.2" + "@napi-rs/wasm-runtime": "^1.1.1" } }, "@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.13.tgz", - "integrity": "sha512-Fqa3Tlt1xL4wzmAYxGNFV36Hb+VfPc9PYU+E25DAnswXv3ODDu/yyWjQDbXMo5AGWkQVjLgQExuVu8I/UaZhPQ==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", "dev": true, "optional": true }, "@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.13.tgz", - "integrity": "sha512-/pLI5kPkGEi44TDlnbio3St/5gUFeN51YWNAk/Gnv6mEQBOahRBh52qVFVBpmrnU01n2yysvBML9Ynu7K4kGAQ==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", "dev": true, "optional": true }, @@ -14086,34 +14088,34 @@ } }, "rolldown": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.13.tgz", - "integrity": "sha512-bvVj8YJmf0rq4pSFmH7laLa6pYrhghv3PRzrCdRAr23g66zOKVJ4wkvFtgohtPLWmthgg8/rkaqRHrpUEh0Zbw==", - "dev": true, - "requires": { - "@oxc-project/types": "=0.123.0", - "@rolldown/binding-android-arm64": "1.0.0-rc.13", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.13", - "@rolldown/binding-darwin-x64": "1.0.0-rc.13", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.13", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.13", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.13", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.13", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.13", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.13", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.13", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.13", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.13", - "@rolldown/pluginutils": "1.0.0-rc.13" + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", + "dev": true, + "requires": { + "@oxc-project/types": "=0.122.0", + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12", + "@rolldown/pluginutils": "1.0.0-rc.12" }, "dependencies": { "@rolldown/pluginutils": { - "version": "1.0.0-rc.13", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz", - "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==", + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", "dev": true } } @@ -15096,16 +15098,16 @@ } }, "vite": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.7.tgz", - "integrity": "sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", + "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", "dev": true, "requires": { "fsevents": "~2.3.3", "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.13", + "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index aa660b07..8d1f84d2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -60,7 +60,7 @@ "jsdom": "^29.0.2", "prettier": "^3.8.1", "stylelint": "^17.6.0", - "vite": "^8.0.7", + "vite": "^8.0.3", "vitest": "^4.0.18" } } From a3d5bffd4121a8efba11bbd310e53fc237430638 Mon Sep 17 00:00:00 2001 From: Krishna Awasthi <140143710+opbot-xd@users.noreply.github.com> Date: Wed, 8 Apr 2026 13:30:08 +0530 Subject: [PATCH 24/27] replace psycopg2-binary with psycopg[c] (psycopg v3) (#1202) - swap psycopg2-binary for psycopg[c]==3.3.3 in pyproject.toml - add gcc, python3-dev, libpq-dev to Dockerfile as build-only deps to compile the C extension; purge them after uv sync to keep the image lean; only libpq5 (runtime lib) stays in the image - regenerate uv.lock - fix pre-existing test bug in test_get_url_downloads: mock's payload_request was a truthy Mock causing threatfox_submission to run and fail on a non-iterable; set it to False explicitly Closes #1191 --- docker/Dockerfile | 24 ++++++++++++++++-------- pyproject.toml | 2 +- uv.lock | 36 ++++++++++++++++++++---------------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 54d202b0..8e5839d3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -37,22 +37,30 @@ ENV UV_PROJECT_ENVIRONMENT=/usr/local WORKDIR $APP_ROOT -# Install runtime dependencies -# - libgomp1 is required for model training -# - curl is used for healthcheck -RUN apt-get update && apt-get install -y --no-install-recommends \ - libgomp1 curl gosu \ +# Layer 1: stable runtime OS deps — cached across pyproject.toml/uv.lock changes. +# libgomp1: model training; curl: healthcheck; gosu: entrypoint privilege drop +# libpq5: runtime shared library required by the psycopg[c] C extension +RUN apt-get update \ + && apt-get install -y --no-install-recommends libgomp1 curl gosu libpq5 \ && rm -rf /var/lib/apt/lists/* -# Install python packages +# Layer 2: Python packages — only re-runs when pyproject.toml/uv.lock change. +# Build-only deps (gcc, python3-dev, libpq-dev) compile the psycopg[c] C +# extension and are purged in the same layer to keep the final image lean. COPY pyproject.toml uv.lock ./ -RUN uv sync --no-dev --locked +RUN apt-get update \ + && apt-get install -y --no-install-recommends gcc python3-dev libpq-dev \ + && uv sync --no-dev --locked \ + && uv cache clean \ + && apt-get purge -y gcc python3-dev libpq-dev \ + && apt-get autoremove -y \ + && rm -rf /var/lib/apt/lists/* # Copy files COPY . $APP_ROOT COPY --from=frontend-build /app/build /var/www/reactapp -# separation is required to avoid to re-execute os installation in case of change of python requirements +# Set up log directories, fix permissions, and remove frontend source (served from /var/www/reactapp) RUN mkdir -p ${LOG_PATH}/django ${LOG_PATH}/gunicorn \ && touch ${LOG_PATH}/django/api.log ${LOG_PATH}/django/api_errors.log \ && touch ${LOG_PATH}/django/greedybear.log ${LOG_PATH}/django/greedybear_errors.log \ diff --git a/pyproject.toml b/pyproject.toml index 77bd5b73..eb540dc8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "gunicorn==25.3.0", # Data stores "elasticsearch==9.3.0", - "psycopg2-binary==2.9.11", + "psycopg[c]==3.3.3", # ML / data science "scikit-learn==1.8.0", "pandas==3.0.2", diff --git a/uv.lock b/uv.lock index 8800fed4..bbcdece5 100644 --- a/uv.lock +++ b/uv.lock @@ -500,7 +500,7 @@ dependencies = [ { name = "joblib" }, { name = "numpy" }, { name = "pandas" }, - { name = "psycopg2-binary" }, + { name = "psycopg", extra = ["c"] }, { name = "requests" }, { name = "scikit-learn" }, { name = "slack-sdk" }, @@ -533,7 +533,7 @@ requires-dist = [ { name = "joblib", specifier = "==1.5.3" }, { name = "numpy", specifier = "==2.4.4" }, { name = "pandas", specifier = "==3.0.2" }, - { name = "psycopg2-binary", specifier = "==2.9.11" }, + { name = "psycopg", extras = ["c"], specifier = "==3.3.3" }, { name = "requests", specifier = "==2.33.1" }, { name = "scikit-learn", specifier = "==1.8.0" }, { name = "slack-sdk", specifier = "==3.41.0" }, @@ -717,24 +717,28 @@ wheels = [ ] [[package]] -name = "psycopg2-binary" -version = "2.9.11" +name = "psycopg" +version = "3.3.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624, upload-time = "2026-02-18T16:52:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572, upload-time = "2025-10-10T11:12:32.873Z" }, - { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529, upload-time = "2025-10-10T11:12:36.791Z" }, - { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, - { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133, upload-time = "2025-10-30T02:55:24.329Z" }, - { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, - { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712, upload-time = "2025-10-30T02:55:27.975Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, - { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215, upload-time = "2025-10-10T11:13:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" }, +] + +[package.optional-dependencies] +c = [ + { name = "psycopg-c", marker = "implementation_name != 'pypy'" }, ] +[[package]] +name = "psycopg-c" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/a0/8feb0ca8c7c20a8b9ac4d46b335ddd57e48e593b714262f006880f34fee5/psycopg_c-3.3.3.tar.gz", hash = "sha256:86ef6f4424348247828e83fb0882c9f8acb33e64d0a5ce66c1b4a5107ee73edd", size = 631965, upload-time = "2026-02-18T16:52:18.084Z" } + [[package]] name = "python-dateutil" version = "2.9.0.post0" From c1ed5c8c9f05d87f681672646d30ff4bba3a05f6 Mon Sep 17 00:00:00 2001 From: Manik Date: Wed, 8 Apr 2026 20:24:40 +0530 Subject: [PATCH 25/27] use os.sched_getaffinity for cgroup-aware CPU count in gunicorn config. Closes #1194 (#1205) --- configuration/gunicorn/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configuration/gunicorn/config.py b/configuration/gunicorn/config.py index 6d001381..d5765f82 100644 --- a/configuration/gunicorn/config.py +++ b/configuration/gunicorn/config.py @@ -1,10 +1,10 @@ -import multiprocessing +import os # Server socket bind = "unix:/run/gunicorn/main.sock" # Worker processes -workers = 2 * multiprocessing.cpu_count() + 1 +workers = 2 * len(os.sched_getaffinity(0)) + 1 max_requests = 1000 max_requests_jitter = 50 From ccee95208e2fd6f730175c32a2662462e3baf403 Mon Sep 17 00:00:00 2001 From: tim <46972822+regulartim@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:03:34 +0200 Subject: [PATCH 26/27] bump 3.3.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eb540dc8..bfcfe0d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "greedybear" -version = "3.3.0" +version = "3.3.1" requires-python = "==3.13.*" dependencies = [ # Django core From 7d9ac5a6b56c58c1d6dfe679cb967a25f98b0314 Mon Sep 17 00:00:00 2001 From: tim <46972822+regulartim@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:05:14 +0200 Subject: [PATCH 27/27] update uv lock file --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index bbcdece5..1aef058a 100644 --- a/uv.lock +++ b/uv.lock @@ -483,7 +483,7 @@ wheels = [ [[package]] name = "greedybear" -version = "3.3.0" +version = "3.3.1" source = { virtual = "." } dependencies = [ { name = "certego-saas" },