From 379df7800d9ffc4599b3d1dfa8ce1128b7630d0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 09:27:55 +0200 Subject: [PATCH 01/65] chore(deps): bump aiohttp from 3.13.3 to 3.13.5 in /api (#10538) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daniel Barranquero --- api/CHANGELOG.md | 1 + api/poetry.lock | 242 +++++++++++++++++++++++------------------------ 2 files changed, 122 insertions(+), 121 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 54b589e496..936ce9f6b3 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -36,6 +36,7 @@ All notable changes to the **Prowler API** are documented in this file. - Pin all unpinned dependencies to exact versions to prevent supply chain attacks and ensure reproducible builds [(#10469)](https://github.com/prowler-cloud/prowler/pull/10469) - `authlib` bumped from 1.6.6 to 1.6.9 to fix CVE-2026-28802 (JWT `alg: none` validation bypass) [(#10579)](https://github.com/prowler-cloud/prowler/pull/10579) +- `aiohttp` bumped from 3.13.3 to 3.13.5 to fix CVE-2026-34520 (the C parser accepted null bytes and control characters in response headers) [(#10538)](https://github.com/prowler-cloud/prowler/pull/10538) --- diff --git a/api/poetry.lock b/api/poetry.lock index 95f7cce24e..24fca80a26 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -103,132 +103,132 @@ files = [ [[package]] name = "aiohttp" -version = "3.13.3" +version = "3.13.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohttp-3.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a372fd5afd301b3a89582817fdcdb6c34124787c70dbcc616f259013e7eef7"}, - {file = "aiohttp-3.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:147e422fd1223005c22b4fe080f5d93ced44460f5f9c105406b753612b587821"}, - {file = "aiohttp-3.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859bd3f2156e81dd01432f5849fc73e2243d4a487c4fd26609b1299534ee1845"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dca68018bf48c251ba17c72ed479f4dafe9dbd5a73707ad8d28a38d11f3d42af"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fee0c6bc7db1de362252affec009707a17478a00ec69f797d23ca256e36d5940"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c048058117fd649334d81b4b526e94bde3ccaddb20463a815ced6ecbb7d11160"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:215a685b6fbbfcf71dfe96e3eba7a6f58f10da1dfdf4889c7dd856abe430dca7"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2c184bb1fe2cbd2cefba613e9db29a5ab559323f994b6737e370d3da0ac455"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75ca857eba4e20ce9f546cd59c7007b33906a4cd48f2ff6ccf1ccfc3b646f279"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81e97251d9298386c2b7dbeb490d3d1badbdc69107fb8c9299dd04eb39bddc0e"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0e2d366af265797506f0283487223146af57815b388623f0357ef7eac9b209d"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4e239d501f73d6db1522599e14b9b321a7e3b1de66ce33d53a765d975e9f4808"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0db318f7a6f065d84cb1e02662c526294450b314a02bd9e2a8e67f0d8564ce40"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bfc1cc2fe31a6026a8a88e4ecfb98d7f6b1fec150cfd708adbfd1d2f42257c29"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af71fff7bac6bb7508956696dce8f6eec2bbb045eceb40343944b1ae62b5ef11"}, - {file = "aiohttp-3.13.3-cp310-cp310-win32.whl", hash = "sha256:37da61e244d1749798c151421602884db5270faf479cf0ef03af0ff68954c9dd"}, - {file = "aiohttp-3.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:7e63f210bc1b57ef699035f2b4b6d9ce096b5914414a49b0997c839b2bd2223c"}, - {file = "aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b"}, - {file = "aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64"}, - {file = "aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29"}, - {file = "aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239"}, - {file = "aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f"}, - {file = "aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c"}, - {file = "aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168"}, - {file = "aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a"}, - {file = "aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046"}, - {file = "aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57"}, - {file = "aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c"}, - {file = "aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9"}, - {file = "aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591"}, - {file = "aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf"}, - {file = "aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e"}, - {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808"}, - {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415"}, - {file = "aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43"}, - {file = "aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1"}, - {file = "aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984"}, - {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c"}, - {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592"}, - {file = "aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa"}, - {file = "aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767"}, - {file = "aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344"}, - {file = "aiohttp-3.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31a83ea4aead760dfcb6962efb1d861db48c34379f2ff72db9ddddd4cda9ea2e"}, - {file = "aiohttp-3.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:988a8c5e317544fdf0d39871559e67b6341065b87fceac641108c2096d5506b7"}, - {file = "aiohttp-3.13.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b174f267b5cfb9a7dba9ee6859cecd234e9a681841eb85068059bc867fb8f02"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:947c26539750deeaee933b000fb6517cc770bbd064bad6033f1cff4803881e43"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9ebf57d09e131f5323464bd347135a88622d1c0976e88ce15b670e7ad57e4bd6"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4ae5b5a0e1926e504c81c5b84353e7a5516d8778fbbff00429fe7b05bb25cbce"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ba0eea45eb5cc3172dbfc497c066f19c41bac70963ea1a67d51fc92e4cf9a80"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bae5c2ed2eae26cc382020edad80d01f36cb8e746da40b292e68fec40421dc6a"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a60e60746623925eab7d25823329941aee7242d559baa119ca2b253c88a7bd6"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e50a2e1404f063427c9d027378472316201a2290959a295169bcf25992d04558"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9a9dc347e5a3dc7dfdbc1f82da0ef29e388ddb2ed281bfce9dd8248a313e62b7"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b46020d11d23fe16551466c77823df9cc2f2c1e63cc965daf67fa5eec6ca1877"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:69c56fbc1993fa17043e24a546959c0178fe2b5782405ad4559e6c13975c15e3"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b99281b0704c103d4e11e72a76f1b543d4946fea7dd10767e7e1b5f00d4e5704"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:40c5e40ecc29ba010656c18052b877a1c28f84344825efa106705e835c28530f"}, - {file = "aiohttp-3.13.3-cp39-cp39-win32.whl", hash = "sha256:56339a36b9f1fc708260c76c87e593e2afb30d26de9ae1eb445b5e051b98a7a1"}, - {file = "aiohttp-3.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:c6b8568a3bb5819a0ad087f16d40e5a3fb6099f39ea1d5625a3edc1e923fc538"}, - {file = "aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88"}, + {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02222e7e233295f40e011c1b00e3b0bd451f22cf853a0304c3595633ee47da4b"}, + {file = "aiohttp-3.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bace460460ed20614fa6bc8cb09966c0b8517b8c58ad8046828c6078d25333b5"}, + {file = "aiohttp-3.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f546a4dc1e6a5edbb9fd1fd6ad18134550e096a5a43f4ad74acfbd834fc6670"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c86969d012e51b8e415a8c6ce96f7857d6a87d6207303ab02d5d11ef0cad2274"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b6f6cd1560c5fa427e3b6074bb24d2c64e225afbb7165008903bd42e4e33e28a"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:636bc362f0c5bbc7372bc3ae49737f9e3030dbce469f0f422c8f38079780363d"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a7cbeb06d1070f1d14895eeeed4dac5913b22d7b456f2eb969f11f4b3993796"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca9ef7517fd7874a1a08970ae88f497bf5c984610caa0bf40bd7e8450852b95"}, + {file = "aiohttp-3.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:019a67772e034a0e6b9b17c13d0a8fe56ad9fb150fc724b7f3ffd3724288d9e5"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f34ecee82858e41dd217734f0c41a532bd066bcaab636ad830f03a30b2a96f2a"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4eac02d9af4813ee289cd63a361576da36dba57f5a1ab36377bc2600db0cbb73"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4beac52e9fe46d6abf98b0176a88154b742e878fdf209d2248e99fcdf73cd297"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c180f480207a9b2475f2b8d8bd7204e47aec952d084b2a2be58a782ffcf96074"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2837fb92951564d6339cedae4a7231692aa9f73cbc4fb2e04263b96844e03b4e"}, + {file = "aiohttp-3.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9010032a0b9710f58012a1e9c222528763d860ba2ee1422c03473eab47703e7"}, + {file = "aiohttp-3.13.5-cp310-cp310-win32.whl", hash = "sha256:7c4b6668b2b2b9027f209ddf647f2a4407784b5d88b8be4efcc72036f365baf9"}, + {file = "aiohttp-3.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:cd3db5927bf9167d5a6157ddb2f036f6b6b0ad001ac82355d43e97a4bde76d76"}, + {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6"}, + {file = "aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d"}, + {file = "aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc"}, + {file = "aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac"}, + {file = "aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3"}, + {file = "aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06"}, + {file = "aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8"}, + {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9"}, + {file = "aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416"}, + {file = "aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1"}, + {file = "aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe"}, + {file = "aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14"}, + {file = "aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3"}, + {file = "aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1"}, + {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61"}, + {file = "aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832"}, + {file = "aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665"}, + {file = "aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6"}, + {file = "aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c"}, + {file = "aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc"}, + {file = "aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83"}, + {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c"}, + {file = "aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be"}, + {file = "aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b"}, + {file = "aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1"}, + {file = "aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b"}, + {file = "aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3"}, + {file = "aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162"}, + {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a"}, + {file = "aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254"}, + {file = "aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a"}, + {file = "aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500"}, + {file = "aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9"}, + {file = "aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8"}, + {file = "aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9"}, + {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:347542f0ea3f95b2a955ee6656461fa1c776e401ac50ebce055a6c38454a0adf"}, + {file = "aiohttp-3.13.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:178c7b5e62b454c2bc790786e6058c3cc968613b4419251b478c153a4aec32b1"}, + {file = "aiohttp-3.13.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af545c2cffdb0967a96b6249e6f5f7b0d92cdfd267f9d5238d5b9ca63e8edb10"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:206b7b3ef96e4ce211754f0cd003feb28b7d81f0ad26b8d077a5d5161436067f"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ee5e86776273de1795947d17bddd6bb19e0365fd2af4289c0d2c5454b6b1d36b"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95d14ca7abefde230f7639ec136ade282655431fd5db03c343b19dda72dd1643"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:912d4b6af530ddb1338a66229dac3a25ff11d4448be3ec3d6340583995f56031"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e999f0c88a458c836d5fb521814e92ed2172c649200336a6df514987c1488258"}, + {file = "aiohttp-3.13.5-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39380e12bd1f2fdab4285b6e055ad48efbaed5c836433b142ed4f5b9be71036a"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9efcc0f11d850cefcafdd9275b9576ad3bfb539bed96807663b32ad99c4d4b88"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:147b4f501d0292077f29d5268c16bb7c864a1f054d7001c4c1812c0421ea1ed0"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d147004fede1b12f6013a6dbb2a26a986a671a03c6ea740ddc76500e5f1c399f"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9277145d36a01653863899c665243871434694bcc3431922c3b35c978061bdb8"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4e704c52438f66fdd89588346183d898bb42167cf88f8b7ff1c0f9fc957c348f"}, + {file = "aiohttp-3.13.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8a4d3427e8de1312ddf309cc482186466c79895b3a139fed3259fc01dfa9a5b"}, + {file = "aiohttp-3.13.5-cp39-cp39-win32.whl", hash = "sha256:6f497a6876aa4b1a102b04996ce4c1170c7040d83faa9387dd921c16e30d5c83"}, + {file = "aiohttp-3.13.5-cp39-cp39-win_amd64.whl", hash = "sha256:cb979826071c0986a5f08333a36104153478ce6018c58cba7f9caddaf63d5d67"}, + {file = "aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1"}, ] [package.dependencies] From b9270df3e6f664c0d9b807bbbfe7550c03c9f0d5 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Thu, 9 Apr 2026 09:39:52 +0200 Subject: [PATCH 02/65] feat(ui): improvements over findings groups feature (#10590) --- .../finding-groups.adapter.test.ts | 2 + .../finding-groups/finding-groups.adapter.ts | 4 +- .../finding-groups/finding-groups.test.ts | 174 +++++++----- ui/actions/finding-groups/finding-groups.ts | 203 +++++++------- ui/actions/findings/findings-by-resource.ts | 3 + ui/app/(prowler)/findings/page.test.ts | 37 +++ ui/app/(prowler)/findings/page.tsx | 46 ++-- ui/app/(prowler)/scans/page.tsx | 37 ++- ui/components/findings/findings-filters.tsx | 57 +--- .../findings/findings-filters.utils.test.ts | 148 +++++++++++ .../findings/findings-filters.utils.ts | 80 ++++++ .../table/column-finding-groups.test.tsx | 139 +++++++++- .../findings/table/column-finding-groups.tsx | 16 +- .../table/column-finding-resources.test.tsx | 203 ++++++++++++++ .../table/column-finding-resources.tsx | 24 +- .../table/finding-group-selection.test.ts | 45 ++++ .../findings/table/finding-group-selection.ts | 13 + .../table/finding-resource-selection.test.ts | 47 ++++ .../table/finding-resource-selection.ts | 5 + .../table/findings-group-drill-down.tsx | 11 +- .../findings/table/findings-group-table.tsx | 19 +- .../table/inline-resource-container.tsx | 9 +- .../resource-detail-drawer-content.test.tsx | 193 +++++++++++++- .../resource-detail-drawer-content.tsx | 166 ++++++++++-- .../resource-detail-skeleton.test.tsx | 26 ++ .../resource-detail-skeleton.tsx | 11 +- .../use-resource-detail-drawer.test.ts | 250 ++++++++++++++++++ .../use-resource-detail-drawer.ts | 64 ++++- ui/components/scans/scans-filters.tsx | 57 +++- ui/components/shadcn/card/card.tsx | 2 +- ui/components/ui/entities/date-with-time.tsx | 40 ++- ui/hooks/use-infinite-resources.test.ts | 32 +++ ui/hooks/use-infinite-resources.ts | 12 +- ui/lib/findings-scan-filters.test.ts | 112 ++++++++ ui/lib/findings-scan-filters.ts | 99 +++++++ ui/types/findings-table.ts | 2 + 36 files changed, 2061 insertions(+), 327 deletions(-) create mode 100644 ui/app/(prowler)/findings/page.test.ts create mode 100644 ui/components/findings/findings-filters.utils.test.ts create mode 100644 ui/components/findings/findings-filters.utils.ts create mode 100644 ui/components/findings/table/column-finding-resources.test.tsx create mode 100644 ui/components/findings/table/finding-group-selection.test.ts create mode 100644 ui/components/findings/table/finding-group-selection.ts create mode 100644 ui/components/findings/table/finding-resource-selection.test.ts create mode 100644 ui/components/findings/table/finding-resource-selection.ts create mode 100644 ui/components/findings/table/resource-detail-drawer/resource-detail-skeleton.test.tsx create mode 100644 ui/lib/findings-scan-filters.test.ts create mode 100644 ui/lib/findings-scan-filters.ts diff --git a/ui/actions/finding-groups/finding-groups.adapter.test.ts b/ui/actions/finding-groups/finding-groups.adapter.test.ts index 5874ddf7e5..1d5d04e62d 100644 --- a/ui/actions/finding-groups/finding-groups.adapter.test.ts +++ b/ui/actions/finding-groups/finding-groups.adapter.test.ts @@ -163,6 +163,7 @@ describe("adaptFindingGroupResourcesResponse — malformed input", () => { alias: "production", }, status: "FAIL", + delta: "new", severity: "critical", first_seen_at: null, last_seen_at: "2024-01-01T00:00:00Z", @@ -178,5 +179,6 @@ describe("adaptFindingGroupResourcesResponse — malformed input", () => { expect(result).toHaveLength(1); expect(result[0].checkId).toBe("s3_check"); expect(result[0].resourceName).toBe("my-bucket"); + expect(result[0].delta).toBe("new"); }); }); diff --git a/ui/actions/finding-groups/finding-groups.adapter.ts b/ui/actions/finding-groups/finding-groups.adapter.ts index 593171078d..2e3964522e 100644 --- a/ui/actions/finding-groups/finding-groups.adapter.ts +++ b/ui/actions/finding-groups/finding-groups.adapter.ts @@ -98,6 +98,7 @@ interface FindingGroupResourceAttributes { resource: ResourceInfo; provider: ProviderInfo; status: string; + delta?: string | null; severity: string; first_seen_at: string | null; last_seen_at: string | null; @@ -137,14 +138,15 @@ export function adaptFindingGroupResourcesResponse( providerAlias: item.attributes.provider?.alias || "", providerUid: item.attributes.provider?.uid || "", resourceName: item.attributes.resource?.name || "-", + resourceType: item.attributes.resource?.type || "-", resourceGroup: item.attributes.resource?.resource_group || "-", resourceUid: item.attributes.resource?.uid || "-", service: item.attributes.resource?.service || "-", region: item.attributes.resource?.region || "-", severity: (item.attributes.severity || "informational") as Severity, status: item.attributes.status, + delta: item.attributes.delta || null, isMuted: item.attributes.status === "MUTED", - // TODO: remove fallback once the API returns muted_reason in finding-group-resources mutedReason: item.attributes.muted_reason || undefined, firstSeenAt: item.attributes.first_seen_at, lastSeenAt: item.attributes.last_seen_at, diff --git a/ui/actions/finding-groups/finding-groups.test.ts b/ui/actions/finding-groups/finding-groups.test.ts index 0d8c2df53a..9f4bdc5830 100644 --- a/ui/actions/finding-groups/finding-groups.test.ts +++ b/ui/actions/finding-groups/finding-groups.test.ts @@ -47,10 +47,6 @@ import { getLatestFindingGroupResources, } from "./finding-groups"; -// --------------------------------------------------------------------------- -// Blocker 1 + 2: FAIL-first sort and FAIL-only filter for drill-down resources -// --------------------------------------------------------------------------- - // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- @@ -169,7 +165,7 @@ describe("getLatestFindingGroupResources — SSRF path traversal protection", () }); // --------------------------------------------------------------------------- -// Blocker 1: Resources list must show FAIL first (sort=-status) +// Resources list keeps FAIL-first sort but no longer forces FAIL-only filtering // --------------------------------------------------------------------------- describe("getFindingGroupResources — Blocker 1: FAIL-first sort", () => { @@ -181,30 +177,30 @@ describe("getFindingGroupResources — Blocker 1: FAIL-first sort", () => { fetchMock.mockResolvedValue(new Response("", { status: 200 })); }); - it("should include sort=-status in the API call so FAIL resources appear first", async () => { + it("should include the composite sort so FAIL resources appear first, then severity", async () => { // Given const checkId = "s3_bucket_public_access"; // When await getFindingGroupResources({ checkId }); - // Then — the URL must contain sort=-status + // Then — the URL must contain the composite sort const calledUrl = fetchMock.mock.calls[0][0] as string; const url = new URL(calledUrl); - expect(url.searchParams.get("sort")).toBe("-status"); + expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); }); - it("should include filter[status]=FAIL in the API call so only impacted resources are shown", async () => { + it("should not force filter[status]=FAIL so PASS resources can also be shown", async () => { // Given const checkId = "s3_bucket_public_access"; // When await getFindingGroupResources({ checkId }); - // Then — the URL must contain filter[status]=FAIL + // Then — the URL should not add a hardcoded status filter const calledUrl = fetchMock.mock.calls[0][0] as string; const url = new URL(calledUrl); - expect(url.searchParams.get("filter[status]")).toBe("FAIL"); + expect(url.searchParams.get("filter[status]")).toBeNull(); }); }); @@ -217,7 +213,7 @@ describe("getLatestFindingGroupResources — Blocker 1: FAIL-first sort", () => fetchMock.mockResolvedValue(new Response("", { status: 200 })); }); - it("should include sort=-status in the API call so FAIL resources appear first", async () => { + it("should include the composite sort so FAIL resources appear first, then severity", async () => { // Given const checkId = "iam_user_mfa_enabled"; @@ -227,10 +223,10 @@ describe("getLatestFindingGroupResources — Blocker 1: FAIL-first sort", () => // Then const calledUrl = fetchMock.mock.calls[0][0] as string; const url = new URL(calledUrl); - expect(url.searchParams.get("sort")).toBe("-status"); + expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); }); - it("should include filter[status]=FAIL in the API call so only impacted resources are shown", async () => { + it("should not force filter[status]=FAIL so PASS resources can also be shown", async () => { // Given const checkId = "iam_user_mfa_enabled"; @@ -240,7 +236,7 @@ describe("getLatestFindingGroupResources — Blocker 1: FAIL-first sort", () => // Then const calledUrl = fetchMock.mock.calls[0][0] as string; const url = new URL(calledUrl); - expect(url.searchParams.get("filter[status]")).toBe("FAIL"); + expect(url.searchParams.get("filter[status]")).toBeNull(); }); }); @@ -257,7 +253,7 @@ describe("getFindingGroupResources — triangulation: params coexist", () => { fetchMock.mockResolvedValue(new Response("", { status: 200 })); }); - it("should send sort=-status AND filter[status]=FAIL alongside pagination params", async () => { + it("should send the composite sort alongside pagination params without forcing filter[status]", async () => { // Given const checkId = "s3_bucket_versioning"; @@ -269,8 +265,8 @@ describe("getFindingGroupResources — triangulation: params coexist", () => { const url = new URL(calledUrl); expect(url.searchParams.get("page[number]")).toBe("2"); expect(url.searchParams.get("page[size]")).toBe("50"); - expect(url.searchParams.get("sort")).toBe("-status"); - expect(url.searchParams.get("filter[status]")).toBe("FAIL"); + expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); + expect(url.searchParams.get("filter[status]")).toBeNull(); }); }); @@ -283,7 +279,7 @@ describe("getLatestFindingGroupResources — triangulation: params coexist", () fetchMock.mockResolvedValue(new Response("", { status: 200 })); }); - it("should send sort=-status AND filter[status]=FAIL alongside pagination params", async () => { + it("should send the composite sort alongside pagination params without forcing filter[status]", async () => { // Given const checkId = "iam_root_mfa_enabled"; @@ -295,16 +291,16 @@ describe("getLatestFindingGroupResources — triangulation: params coexist", () const url = new URL(calledUrl); expect(url.searchParams.get("page[number]")).toBe("3"); expect(url.searchParams.get("page[size]")).toBe("20"); - expect(url.searchParams.get("sort")).toBe("-status"); - expect(url.searchParams.get("filter[status]")).toBe("FAIL"); + expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); + expect(url.searchParams.get("filter[status]")).toBeNull(); }); }); // --------------------------------------------------------------------------- -// Blocker: Duplicate filter[status] — caller-supplied status must be stripped +// Caller filters should propagate unchanged to the drill-down resources endpoint // --------------------------------------------------------------------------- -describe("getFindingGroupResources — Blocker: caller filter[status] is always overridden to FAIL", () => { +describe("getFindingGroupResources — caller filters are preserved", () => { beforeEach(() => { vi.clearAllMocks(); vi.stubGlobal("fetch", fetchMock); @@ -313,23 +309,7 @@ describe("getFindingGroupResources — Blocker: caller filter[status] is always fetchMock.mockResolvedValue(new Response("", { status: 200 })); }); - it("should use filter[status]=FAIL even when caller passes filter[status]=PASS", async () => { - // Given — caller explicitly passes PASS, which must be ignored - const checkId = "s3_bucket_public_access"; - const filters = { "filter[status]": "PASS" }; - - // When - await getFindingGroupResources({ checkId, filters }); - - // Then — the final URL must have exactly one filter[status]=FAIL, not PASS - const calledUrl = fetchMock.mock.calls[0][0] as string; - const url = new URL(calledUrl); - const allStatusValues = url.searchParams.getAll("filter[status]"); - expect(allStatusValues).toHaveLength(1); - expect(allStatusValues[0]).toBe("FAIL"); - }); - - it("should not have duplicate filter[status] params when caller passes filter[status]", async () => { + it("should preserve caller filter[status] when explicitly provided", async () => { // Given const checkId = "s3_bucket_public_access"; const filters = { "filter[status]": "PASS" }; @@ -337,14 +317,56 @@ describe("getFindingGroupResources — Blocker: caller filter[status] is always // When await getFindingGroupResources({ checkId, filters }); - // Then — no duplicates + // Then const calledUrl = fetchMock.mock.calls[0][0] as string; const url = new URL(calledUrl); - expect(url.searchParams.getAll("filter[status]")).toHaveLength(1); + const allStatusValues = url.searchParams.getAll("filter[status]"); + expect(allStatusValues).toHaveLength(1); + expect(allStatusValues[0]).toBe("PASS"); + }); + + it("should translate a single group status__in filter into filter[status] for resources", async () => { + // Given + const checkId = "s3_bucket_public_access"; + const filters = { + "filter[status__in]": "PASS", + "filter[severity__in]": "medium", + "filter[provider_type__in]": "aws", + }; + + // When + await getFindingGroupResources({ checkId, filters }); + + // Then + const calledUrl = fetchMock.mock.calls[0][0] as string; + const url = new URL(calledUrl); + expect(url.searchParams.get("filter[status]")).toBe("PASS"); + expect(url.searchParams.get("filter[status__in]")).toBeNull(); + expect(url.searchParams.get("filter[severity__in]")).toBe("medium"); + expect(url.searchParams.get("filter[provider_type__in]")).toBe("aws"); + }); + + it("should keep the composite sort when the resource search filter is applied", async () => { + // Given + const checkId = "s3_bucket_public_access"; + const filters = { + "filter[name__icontains]": "bucket-prod", + "filter[severity__in]": "high", + }; + + // When + await getFindingGroupResources({ checkId, filters }); + + // Then + const calledUrl = fetchMock.mock.calls[0][0] as string; + const url = new URL(calledUrl); + expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); + expect(url.searchParams.get("filter[name__icontains]")).toBe("bucket-prod"); + expect(url.searchParams.get("filter[severity__in]")).toBe("high"); }); }); -describe("getLatestFindingGroupResources — Blocker: caller filter[status] is always overridden to FAIL", () => { +describe("getLatestFindingGroupResources — caller filters are preserved", () => { beforeEach(() => { vi.clearAllMocks(); vi.stubGlobal("fetch", fetchMock); @@ -353,23 +375,7 @@ describe("getLatestFindingGroupResources — Blocker: caller filter[status] is a fetchMock.mockResolvedValue(new Response("", { status: 200 })); }); - it("should use filter[status]=FAIL even when caller passes filter[status]=PASS", async () => { - // Given — caller explicitly passes PASS, which must be ignored - const checkId = "iam_user_mfa_enabled"; - const filters = { "filter[status]": "PASS" }; - - // When - await getLatestFindingGroupResources({ checkId, filters }); - - // Then — the final URL must have exactly one filter[status]=FAIL, not PASS - const calledUrl = fetchMock.mock.calls[0][0] as string; - const url = new URL(calledUrl); - const allStatusValues = url.searchParams.getAll("filter[status]"); - expect(allStatusValues).toHaveLength(1); - expect(allStatusValues[0]).toBe("FAIL"); - }); - - it("should not have duplicate filter[status] params when caller passes filter[status]", async () => { + it("should preserve caller filter[status] when explicitly provided", async () => { // Given const checkId = "iam_user_mfa_enabled"; const filters = { "filter[status]": "PASS" }; @@ -377,9 +383,53 @@ describe("getLatestFindingGroupResources — Blocker: caller filter[status] is a // When await getLatestFindingGroupResources({ checkId, filters }); - // Then — no duplicates + // Then const calledUrl = fetchMock.mock.calls[0][0] as string; const url = new URL(calledUrl); - expect(url.searchParams.getAll("filter[status]")).toHaveLength(1); + const allStatusValues = url.searchParams.getAll("filter[status]"); + expect(allStatusValues).toHaveLength(1); + expect(allStatusValues[0]).toBe("PASS"); + }); + + it("should translate a single group status__in filter into filter[status] for latest resources", async () => { + // Given + const checkId = "iam_user_mfa_enabled"; + const filters = { + "filter[status__in]": "PASS", + "filter[severity__in]": "low", + "filter[provider_type__in]": "aws", + }; + + // When + await getLatestFindingGroupResources({ checkId, filters }); + + // Then + const calledUrl = fetchMock.mock.calls[0][0] as string; + const url = new URL(calledUrl); + expect(url.searchParams.get("filter[status]")).toBe("PASS"); + expect(url.searchParams.get("filter[status__in]")).toBeNull(); + expect(url.searchParams.get("filter[severity__in]")).toBe("low"); + expect(url.searchParams.get("filter[provider_type__in]")).toBe("aws"); + }); + + it("should keep the composite sort when the resource search filter is applied", async () => { + // Given + const checkId = "iam_user_mfa_enabled"; + const filters = { + "filter[name__icontains]": "instance-prod", + "filter[status__in]": "PASS,FAIL", + }; + + // When + await getLatestFindingGroupResources({ checkId, filters }); + + // Then + const calledUrl = fetchMock.mock.calls[0][0] as string; + const url = new URL(calledUrl); + expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); + expect(url.searchParams.get("filter[name__icontains]")).toBe( + "instance-prod", + ); + expect(url.searchParams.get("filter[status__in]")).toBe("PASS,FAIL"); }); }); diff --git a/ui/actions/finding-groups/finding-groups.ts b/ui/actions/finding-groups/finding-groups.ts index b31d7a5bfc..b08293c882 100644 --- a/ui/actions/finding-groups/finding-groups.ts +++ b/ui/actions/finding-groups/finding-groups.ts @@ -23,17 +23,68 @@ function mapSearchFilter( return mapped; } -export const getFindingGroups = async ({ - page = 1, - pageSize = 10, - sort = "", - filters = {}, -}) => { +function splitCsvFilterValues(value: string | string[] | undefined): string[] { + if (Array.isArray(value)) { + return value + .flatMap((item) => item.split(",")) + .map((item) => item.trim()) + .filter(Boolean); + } + + if (typeof value === "string") { + return value + .split(",") + .map((item) => item.trim()) + .filter(Boolean); + } + + return []; +} + +function normalizeFindingGroupResourceFilters( + filters: Record, +): Record { + const normalized = { ...filters }; + const exactStatusFilter = normalized["filter[status]"]; + + if (exactStatusFilter !== undefined) { + delete normalized["filter[status__in]"]; + return normalized; + } + + const statusValues = splitCsvFilterValues(normalized["filter[status__in]"]); + if (statusValues.length === 1) { + normalized["filter[status]"] = statusValues[0]; + delete normalized["filter[status__in]"]; + } + + return normalized; +} + +const DEFAULT_FINDING_GROUPS_SORT = + "-severity,-delta,-fail_count,-last_seen_at"; + +interface FetchFindingGroupsParams { + page?: number; + pageSize?: number; + sort?: string; + filters?: Record; +} + +async function fetchFindingGroupsEndpoint( + endpoint: string, + { + page = 1, + pageSize = 10, + sort = DEFAULT_FINDING_GROUPS_SORT, + filters = {}, + }: FetchFindingGroupsParams, +) { const headers = await getAuthHeaders({ contentType: false }); if (isNaN(Number(page)) || page < 1) redirect("/findings"); - const url = new URL(`${apiBaseUrl}/finding-groups`); + const url = new URL(`${apiBaseUrl}/${endpoint}`); if (page) url.searchParams.append("page[number]", page.toString()); if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); @@ -45,120 +96,60 @@ export const getFindingGroups = async ({ const response = await fetch(url.toString(), { headers }); return handleApiResponse(response); } catch (error) { - console.error("Error fetching finding groups:", error); + console.error(`Error fetching ${endpoint}:`, error); return undefined; } -}; +} -export const getLatestFindingGroups = async ({ - page = 1, - pageSize = 10, - sort = "", - filters = {}, -}) => { +export const getFindingGroups = async (params: FetchFindingGroupsParams = {}) => + fetchFindingGroupsEndpoint("finding-groups", params); + +export const getLatestFindingGroups = async ( + params: FetchFindingGroupsParams = {}, +) => fetchFindingGroupsEndpoint("finding-groups/latest", params); + +interface FetchFindingGroupResourcesParams { + checkId: string; + page?: number; + pageSize?: number; + filters?: Record; +} + +async function fetchFindingGroupResourcesEndpoint( + endpointPrefix: string, + { + checkId, + page = 1, + pageSize = 20, + filters = {}, + }: FetchFindingGroupResourcesParams, +) { const headers = await getAuthHeaders({ contentType: false }); + const normalizedFilters = normalizeFindingGroupResourceFilters(filters); - if (isNaN(Number(page)) || page < 1) redirect("/findings"); - - const url = new URL(`${apiBaseUrl}/finding-groups/latest`); + const url = new URL( + `${apiBaseUrl}/${endpointPrefix}/${encodeURIComponent(checkId)}/resources`, + ); if (page) url.searchParams.append("page[number]", page.toString()); if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); - if (sort) url.searchParams.append("sort", sort); + url.searchParams.append("sort", "-severity,-delta,-last_seen_at"); - appendSanitizedProviderFilters(url, mapSearchFilter(filters)); + appendSanitizedProviderFilters(url, normalizedFilters); try { const response = await fetch(url.toString(), { headers }); return handleApiResponse(response); } catch (error) { - console.error("Error fetching latest finding groups:", error); + console.error(`Error fetching ${endpointPrefix} resources:`, error); return undefined; } -}; +} -export const getFindingGroupResources = async ({ - checkId, - page = 1, - pageSize = 20, - filters = {}, -}: { - checkId: string; - page?: number; - pageSize?: number; - filters?: Record; -}) => { - const headers = await getAuthHeaders({ contentType: false }); +export const getFindingGroupResources = async ( + params: FetchFindingGroupResourcesParams, +) => fetchFindingGroupResourcesEndpoint("finding-groups", params); - const url = new URL( - `${apiBaseUrl}/finding-groups/${encodeURIComponent(checkId)}/resources`, - ); - - if (page) url.searchParams.append("page[number]", page.toString()); - if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); - // sort=-status is kept for future-proofing: if the filter[status]=FAIL - // constraint is ever relaxed to allow multiple statuses, the sort ensures - // FAIL resources still appear first in the result set. - url.searchParams.append("sort", "-status"); - - appendSanitizedProviderFilters(url, filters); - - // Use .set() AFTER appendSanitizedProviderFilters so our hardcoded FAIL - // always wins, even if the caller passed a different filter[status] value. - // Using .set() instead of .append() prevents duplicate filter[status] params. - url.searchParams.set("filter[status]", "FAIL"); - - try { - const response = await fetch(url.toString(), { - headers, - }); - - return handleApiResponse(response); - } catch (error) { - console.error("Error fetching finding group resources:", error); - return undefined; - } -}; - -export const getLatestFindingGroupResources = async ({ - checkId, - page = 1, - pageSize = 20, - filters = {}, -}: { - checkId: string; - page?: number; - pageSize?: number; - filters?: Record; -}) => { - const headers = await getAuthHeaders({ contentType: false }); - - const url = new URL( - `${apiBaseUrl}/finding-groups/latest/${encodeURIComponent(checkId)}/resources`, - ); - - if (page) url.searchParams.append("page[number]", page.toString()); - if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); - // sort=-status is kept for future-proofing: if the filter[status]=FAIL - // constraint is ever relaxed to allow multiple statuses, the sort ensures - // FAIL resources still appear first in the result set. - url.searchParams.append("sort", "-status"); - - appendSanitizedProviderFilters(url, filters); - - // Use .set() AFTER appendSanitizedProviderFilters so our hardcoded FAIL - // always wins, even if the caller passed a different filter[status] value. - // Using .set() instead of .append() prevents duplicate filter[status] params. - url.searchParams.set("filter[status]", "FAIL"); - - try { - const response = await fetch(url.toString(), { - headers, - }); - - return handleApiResponse(response); - } catch (error) { - console.error("Error fetching latest finding group resources:", error); - return undefined; - } -}; +export const getLatestFindingGroupResources = async ( + params: FetchFindingGroupResourcesParams, +) => fetchFindingGroupResourcesEndpoint("finding-groups/latest", params); diff --git a/ui/actions/findings/findings-by-resource.ts b/ui/actions/findings/findings-by-resource.ts index 12900ffdca..4c69d2e5ef 100644 --- a/ui/actions/findings/findings-by-resource.ts +++ b/ui/actions/findings/findings-by-resource.ts @@ -379,6 +379,9 @@ export const getLatestFindingsByResourceUid = async ({ ); url.searchParams.append("filter[resource_uid]", resourceUid); + url.searchParams.append("filter[status]", "FAIL"); + url.searchParams.append("filter[muted]", "include"); + url.searchParams.append("sort", "-severity,status,-updated_at"); if (page) url.searchParams.append("page[number]", page.toString()); if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); diff --git a/ui/app/(prowler)/findings/page.test.ts b/ui/app/(prowler)/findings/page.test.ts new file mode 100644 index 0000000000..76462dff99 --- /dev/null +++ b/ui/app/(prowler)/findings/page.test.ts @@ -0,0 +1,37 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import { describe, expect, it } from "vitest"; + +/** + * Source-level assertions for the findings page. + * + * Directly importing page.tsx triggers deep transitive imports + * (next-auth → next/server) that vitest cannot resolve without the + * full Next.js build pipeline. These tests verify key architectural + * invariants via source analysis instead. + */ +describe("findings page", () => { + const currentDir = path.dirname(fileURLToPath(import.meta.url)); + const pagePath = path.join(currentDir, "page.tsx"); + const source = readFileSync(pagePath, "utf8"); + + it("only passes sort to fetchFindingGroups when the user has an explicit sort param", () => { + expect(source).toContain("...(encodedSort && { sort: encodedSort })"); + }); + + it("normalizes scan filters with the required inserted_at params before fetching historical finding groups", () => { + expect(source).toContain("resolveFindingScanDateFilters"); + }); + + it("uses getLatestFindingGroups for non-date/scan queries and getFindingGroups for historical", () => { + expect(source).toContain("hasDateOrScan"); + expect(source).toContain("getFindingGroups"); + expect(source).toContain("getLatestFindingGroups"); + }); + + it("guards errors array access with a length check", () => { + expect(source).toContain("errors?.length > 0"); + }); +}); diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index 576d34ca1e..46749b38c2 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -7,7 +7,7 @@ import { } from "@/actions/finding-groups"; import { getLatestMetadataInfo, getMetadataInfo } from "@/actions/findings"; import { getProviders } from "@/actions/providers"; -import { getScans } from "@/actions/scans"; +import { getScan, getScans } from "@/actions/scans"; import { FindingsFilters } from "@/components/findings/findings-filters"; import { FindingsGroupTable, @@ -21,6 +21,7 @@ import { extractSortAndKey, hasDateOrScanFilter, } from "@/lib"; +import { resolveFindingScanDateFilters } from "@/lib/findings-scan-filters"; import { ScanEntity, ScanProps } from "@/types"; import { SearchParamsProps } from "@/types/components"; @@ -39,16 +40,28 @@ export default async function Findings({ // TODO: Re-implement deep link support (/findings?id=) using the grouped view's resource detail drawer // once the legacy FindingDetailsSheet is fully deprecated (still used by /resources and overview dashboard). - const [metadataInfoData, providersData, scansData] = await Promise.all([ - (hasDateOrScan ? getMetadataInfo : getLatestMetadataInfo)({ - query, - sort: encodedSort, - filters, - }), + const [providersData, scansData] = await Promise.all([ getProviders({ pageSize: 50 }), getScans({ pageSize: 50 }), ]); + const filtersWithScanDates = await resolveFindingScanDateFilters({ + filters, + scans: scansData?.data || [], + loadScan: async (scanId: string) => { + const response = await getScan(scanId); + return response?.data; + }, + }); + + const metadataInfoData = await ( + hasDateOrScan ? getMetadataInfo : getLatestMetadataInfo + )({ + query, + sort: encodedSort, + filters: filtersWithScanDates, + }); + // Extract unique regions, services, categories, groups from the new endpoint const uniqueRegions = metadataInfoData?.data?.attributes?.regions || []; const uniqueServices = metadataInfoData?.data?.attributes?.services || []; @@ -88,7 +101,10 @@ export default async function Findings({ /> }> - + @@ -97,19 +113,15 @@ export default async function Findings({ const SSRDataTable = async ({ searchParams, + filters, }: { searchParams: SearchParamsProps; + filters: Record; }) => { const page = parseInt(searchParams.page?.toString() || "1", 10); const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); - const defaultSort = "-severity,-fail_count,-last_seen_at"; - const { encodedSort } = extractSortAndKey({ - ...searchParams, - sort: searchParams.sort ?? defaultSort, - }); - - const { filters } = extractFiltersAndQuery(searchParams); + const { encodedSort } = extractSortAndKey(searchParams); // Check if the searchParams contain any date or scan filter const hasDateOrScan = hasDateOrScanFilter(searchParams); @@ -119,7 +131,7 @@ const SSRDataTable = async ({ const findingGroupsData = await fetchFindingGroups({ page, - sort: encodedSort, + ...(encodedSort && { sort: encodedSort }), filters, pageSize, }); @@ -131,7 +143,7 @@ const SSRDataTable = async ({ return ( <> - {findingGroupsData?.errors && ( + {findingGroupsData?.errors?.length > 0 && (

Error:

{findingGroupsData.errors[0].detail}

diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx index 2d0416f37c..55dbf7eb8a 100644 --- a/ui/app/(prowler)/scans/page.tsx +++ b/ui/app/(prowler)/scans/page.tsx @@ -18,7 +18,12 @@ import { createProviderDetailsMapping, extractProviderUIDs, } from "@/lib/provider-helpers"; -import { ProviderProps, ScanProps, SearchParamsProps } from "@/types"; +import { + ExpandedScanData, + ProviderProps, + ScanProps, + SearchParamsProps, +} from "@/types"; export default async function Scans({ searchParams, @@ -30,7 +35,34 @@ export default async function Scans({ const filteredParams = { ...resolvedSearchParams }; delete filteredParams.scanId; - const providersData = await getAllProviders(); + const [providersData, completedScansData] = await Promise.all([ + getAllProviders(), + getScans({ + filters: { "filter[state]": "completed" }, + pageSize: 50, + fields: { scans: "name,completed_at,provider" }, + include: "provider", + }), + ]); + + const completedScans: ExpandedScanData[] = (completedScansData?.data ?? []) + .map((scan: ScanProps) => { + const providerId = scan.relationships?.provider?.data?.id; + const providerData = completedScansData?.included?.find( + (item: { type: string; id: string }) => + item.type === "providers" && item.id === providerId, + ); + if (!providerData) return null; + return { + ...scan, + providerInfo: { + provider: providerData.attributes.provider, + uid: providerData.attributes.uid, + alias: providerData.attributes.alias, + }, + }; + }) + .filter(Boolean) as ExpandedScanData[]; const providerInfo = providersData?.data @@ -90,6 +122,7 @@ export default async function Scans({
diff --git a/ui/components/findings/findings-filters.tsx b/ui/components/findings/findings-filters.tsx index af169bfcc1..526b6240f3 100644 --- a/ui/components/findings/findings-filters.tsx +++ b/ui/components/findings/findings-filters.tsx @@ -18,11 +18,12 @@ import { Button } from "@/components/shadcn"; import { ExpandableSection } from "@/components/ui/expandable-section"; import { DataTableFilterCustom } from "@/components/ui/table"; import { useFilterBatch } from "@/hooks/use-filter-batch"; -import { formatLabel, getCategoryLabel, getGroupLabel } from "@/lib/categories"; -import { FilterType, FINDING_STATUS_DISPLAY_NAMES, ScanEntity } from "@/types"; +import { getCategoryLabel, getGroupLabel } from "@/lib/categories"; +import { FilterType, ScanEntity } from "@/types"; import { DATA_TABLE_FILTER_MODE, FilterParam } from "@/types/filters"; -import { getProviderDisplayName, ProviderProps } from "@/types/providers"; -import { SEVERITY_DISPLAY_NAMES } from "@/types/severities"; +import { ProviderProps } from "@/types/providers"; + +import { getFindingsFilterDisplayValue } from "./findings-filters.utils"; interface FindingsFiltersProps { /** Provider data for ProviderTypeSelector and AccountsSelector */ @@ -58,49 +59,6 @@ const FILTER_KEY_LABELS: Record = { "filter[muted]": "Muted", }; -/** - * Formats a raw filter value into a human-readable display string. - * - Provider types: uses shared getProviderDisplayName utility - * - Severities: uses shared SEVERITY_DISPLAY_NAMES (e.g. "critical" → "Critical") - * - Status: uses shared FINDING_STATUS_DISPLAY_NAMES (e.g. "FAIL" → "Fail") - * - Categories: uses getCategoryLabel (handles IAM, EC2, IMDSv1, etc.) - * - Resource groups: uses getGroupLabel (underscore-delimited) - * - Date (filter[inserted_at]): returns the ISO date string as-is (YYYY-MM-DD) - * - Other values: uses formatLabel as a generic fallback (avoids naive capitalisation) - */ -const formatFilterValue = (filterKey: string, value: string): string => { - if (!value) return value; - if (filterKey === "filter[provider_type__in]") { - return getProviderDisplayName(value); - } - if (filterKey === "filter[severity__in]") { - return ( - SEVERITY_DISPLAY_NAMES[ - value.toLowerCase() as keyof typeof SEVERITY_DISPLAY_NAMES - ] ?? formatLabel(value) - ); - } - if (filterKey === "filter[status__in]") { - return ( - FINDING_STATUS_DISPLAY_NAMES[ - value as keyof typeof FINDING_STATUS_DISPLAY_NAMES - ] ?? formatLabel(value) - ); - } - if (filterKey === "filter[category__in]") { - return getCategoryLabel(value); - } - if (filterKey === "filter[resource_groups__in]") { - return getGroupLabel(value); - } - // Date filter: preserve ISO date string (YYYY-MM-DD) — do not run through formatLabel - if (filterKey === "filter[inserted_at]") { - return value; - } - // Generic fallback: handles hyphen/underscore-delimited IDs with smart capitalisation - return formatLabel(value); -}; - export const FindingsFilters = ({ providers, completedScanIds, @@ -185,7 +143,10 @@ export const FindingsFilters = ({ key, label, value, - displayValue: formatFilterValue(key, value), + displayValue: getFindingsFilterDisplayValue(key, value, { + providers, + scans: scanDetails, + }), }); }); }); diff --git a/ui/components/findings/findings-filters.utils.test.ts b/ui/components/findings/findings-filters.utils.test.ts new file mode 100644 index 0000000000..86a3124b0a --- /dev/null +++ b/ui/components/findings/findings-filters.utils.test.ts @@ -0,0 +1,148 @@ +import { describe, expect, it } from "vitest"; + +import { ProviderProps } from "@/types/providers"; +import { ScanEntity } from "@/types/scans"; + +import { getFindingsFilterDisplayValue } from "./findings-filters.utils"; + +function makeProvider( + overrides: Partial & { id: string }, +): ProviderProps { + return { + type: "providers", + attributes: { + provider: "aws", + uid: "123456789012", + alias: "Production Account", + status: "completed", + resources: 10, + connection: { connected: true, last_checked_at: "2026-04-07T10:00:00Z" }, + scanner_args: { + only_logs: false, + excluded_checks: [], + aws_retries_max_attempts: 3, + }, + inserted_at: "2026-04-07T10:00:00Z", + updated_at: "2026-04-07T10:00:00Z", + created_by: { object: "user", id: "user-1" }, + }, + relationships: { + secret: { data: null }, + provider_groups: { meta: { count: 0 }, data: [] }, + }, + ...overrides, + } as ProviderProps; +} + +function makeScanMap( + scanId: string, + overrides?: Partial, +): { [scanId: string]: ScanEntity } { + return { + [scanId]: { + id: scanId, + providerInfo: { + provider: "aws", + alias: "Scan Account", + uid: "123456789012", + }, + attributes: { + name: "Nightly scan", + completed_at: "2026-04-07T10:00:00Z", + }, + ...overrides, + }, + }; +} + +const providers = [makeProvider({ id: "provider-1" })]; +const scans = [makeScanMap("scan-1")]; + +describe("getFindingsFilterDisplayValue", () => { + it("shows the account alias for provider_id filters instead of the raw provider id", () => { + expect( + getFindingsFilterDisplayValue("filter[provider_id__in]", "provider-1", { + providers, + }), + ).toBe("Production Account"); + }); + + it("falls back to the provider uid when the alias is empty", () => { + expect( + getFindingsFilterDisplayValue("filter[provider_id__in]", "provider-2", { + providers: [ + ...providers, + makeProvider({ + id: "provider-2", + attributes: { + ...providers[0].attributes, + alias: "", + uid: "210987654321", + }, + }), + ], + }), + ).toBe("210987654321"); + }); + + it("keeps the raw value when the provider cannot be resolved", () => { + expect( + getFindingsFilterDisplayValue( + "filter[provider_id__in]", + "missing-provider", + { providers }, + ), + ).toBe("missing-provider"); + }); + + it("shows the resolved scan badge label for scan filters instead of formatting the raw scan id", () => { + expect( + getFindingsFilterDisplayValue("filter[scan__in]", "scan-1", { scans }), + ).toBe("Scan Account"); + }); + + it("falls back to the scan provider uid when the alias is missing", () => { + expect( + getFindingsFilterDisplayValue("filter[scan__in]", "scan-2", { + scans: [ + ...scans, + makeScanMap("scan-2", { + providerInfo: { provider: "aws", uid: "210987654321" }, + attributes: { + name: "Weekly scan", + completed_at: "2026-04-08T10:00:00Z", + }, + }), + ], + }), + ).toBe("210987654321"); + }); + + it("keeps the raw scan value when the scan cannot be resolved", () => { + expect( + getFindingsFilterDisplayValue("filter[scan__in]", "missing-scan", { + scans, + }), + ).toBe("missing-scan"); + }); + + it("passes through date values for inserted_at__gte filters", () => { + expect( + getFindingsFilterDisplayValue( + "filter[inserted_at__gte]", + "2026-04-03", + {}, + ), + ).toBe("2026-04-03"); + }); + + it("passes through date values for inserted_at__lte filters", () => { + expect( + getFindingsFilterDisplayValue( + "filter[inserted_at__lte]", + "2026-04-07", + {}, + ), + ).toBe("2026-04-07"); + }); +}); diff --git a/ui/components/findings/findings-filters.utils.ts b/ui/components/findings/findings-filters.utils.ts new file mode 100644 index 0000000000..6cb9c19b28 --- /dev/null +++ b/ui/components/findings/findings-filters.utils.ts @@ -0,0 +1,80 @@ +import { formatLabel, getCategoryLabel, getGroupLabel } from "@/lib/categories"; +import { FINDING_STATUS_DISPLAY_NAMES } from "@/types"; +import { getProviderDisplayName, ProviderProps } from "@/types/providers"; +import { ScanEntity } from "@/types/scans"; +import { SEVERITY_DISPLAY_NAMES } from "@/types/severities"; + +interface GetFindingsFilterDisplayValueOptions { + providers?: ProviderProps[]; + scans?: Array<{ [scanId: string]: ScanEntity }>; +} + +function getProviderAccountDisplayValue( + providerId: string, + providers: ProviderProps[], +): string { + const provider = providers.find((item) => item.id === providerId); + if (!provider) { + return providerId; + } + + return provider.attributes.alias || provider.attributes.uid || providerId; +} + +function getScanDisplayValue( + scanId: string, + scans: Array<{ [scanId: string]: ScanEntity }>, +): string { + const scan = scans.find((item) => item[scanId])?.[scanId]; + if (!scan) { + return scanId; + } + + return scan.providerInfo.alias || scan.providerInfo.uid || scanId; +} + +export function getFindingsFilterDisplayValue( + filterKey: string, + value: string, + options: GetFindingsFilterDisplayValueOptions = {}, +): string { + if (!value) return value; + if (filterKey === "filter[provider_type__in]") { + return getProviderDisplayName(value); + } + if (filterKey === "filter[provider_id__in]") { + return getProviderAccountDisplayValue(value, options.providers || []); + } + if (filterKey === "filter[scan__in]") { + return getScanDisplayValue(value, options.scans || []); + } + if (filterKey === "filter[severity__in]") { + return ( + SEVERITY_DISPLAY_NAMES[ + value.toLowerCase() as keyof typeof SEVERITY_DISPLAY_NAMES + ] ?? formatLabel(value) + ); + } + if (filterKey === "filter[status__in]") { + return ( + FINDING_STATUS_DISPLAY_NAMES[ + value as keyof typeof FINDING_STATUS_DISPLAY_NAMES + ] ?? formatLabel(value) + ); + } + if (filterKey === "filter[category__in]") { + return getCategoryLabel(value); + } + if (filterKey === "filter[resource_groups__in]") { + return getGroupLabel(value); + } + if ( + filterKey === "filter[inserted_at]" || + filterKey === "filter[inserted_at__gte]" || + filterKey === "filter[inserted_at__lte]" + ) { + return value; + } + + return formatLabel(value); +} diff --git a/ui/components/findings/table/column-finding-groups.test.tsx b/ui/components/findings/table/column-finding-groups.test.tsx index ab5d26bc62..e723e51862 100644 --- a/ui/components/findings/table/column-finding-groups.test.tsx +++ b/ui/components/findings/table/column-finding-groups.test.tsx @@ -17,11 +17,20 @@ vi.mock("next/navigation", () => ({ vi.mock("@/components/shadcn", () => ({ Checkbox: ({ "aria-label": ariaLabel, + onCheckedChange, ...props }: InputHTMLAttributes & { "aria-label"?: string; size?: string; - }) => , + onCheckedChange?: (checked: boolean) => void; + }) => ( + onCheckedChange?.(event.target.checked)} + {...props} + /> + ), })); vi.mock("@/components/ui/table", () => ({ @@ -52,7 +61,13 @@ vi.mock("./impacted-providers-cell", () => ({ })); vi.mock("./impacted-resources-cell", () => ({ - ImpactedResourcesCell: () => null, + ImpactedResourcesCell: ({ + impacted, + total, + }: { + impacted: number; + total: number; + }) => {`${impacted}/${total}`}, })); vi.mock("./notification-indicator", () => ({ @@ -94,6 +109,7 @@ function makeGroup(overrides?: Partial): FindingGroupRow { function renderFindingCell( checkTitle: string, onDrillDown: (checkId: string, group: FindingGroupRow) => void, + overrides?: Partial, ) { const columns = getColumnFindingGroups({ rowSelection: {}, @@ -107,7 +123,7 @@ function renderFindingCell( ); if (!findingColumn?.cell) throw new Error("finding column not found"); - const group = makeGroup({ checkTitle }); + const group = makeGroup({ checkTitle, ...overrides }); // Render the cell directly with a minimal row mock const CellComponent = findingColumn.cell as (props: { row: { original: FindingGroupRow }; @@ -116,6 +132,67 @@ function renderFindingCell( render(
{CellComponent({ row: { original: group } })}
); } +function renderImpactedResourcesCell(overrides?: Partial) { + const columns = getColumnFindingGroups({ + rowSelection: {}, + selectableRowCount: 1, + onDrillDown: vi.fn(), + }); + + const impactedResourcesColumn = columns.find( + (col) => (col as { id?: string }).id === "impactedResources", + ); + if (!impactedResourcesColumn?.cell) { + throw new Error("impactedResources column not found"); + } + + const group = makeGroup(overrides); + const CellComponent = impactedResourcesColumn.cell as (props: { + row: { original: FindingGroupRow }; + }) => ReactNode; + + render(
{CellComponent({ row: { original: group } })}
); +} + +function renderSelectCell(overrides?: Partial) { + const toggleSelected = vi.fn(); + const columns = getColumnFindingGroups({ + rowSelection: {}, + selectableRowCount: 1, + onDrillDown: vi.fn(), + }); + + const selectColumn = columns.find( + (col) => (col as { id?: string }).id === "select", + ); + if (!selectColumn?.cell) { + throw new Error("select column not found"); + } + + const group = makeGroup(overrides); + const CellComponent = selectColumn.cell as (props: { + row: { + id: string; + original: FindingGroupRow; + toggleSelected: (selected: boolean) => void; + }; + }) => ReactNode; + + render( +
+ {CellComponent({ + row: { + id: "0", + original: group, + toggleSelected, + }, + })} +
, + ); + + return { toggleSelected }; +} + // --------------------------------------------------------------------------- // Fix 5: Accessibility —

→ + ), +})); + +vi.mock("@/components/shadcn/info-field/info-field", () => ({ + InfoField: () => null, +})); + +vi.mock("@/components/shadcn/spinner/spinner", () => ({ + Spinner: () => null, +})); + +vi.mock("@/components/ui/entities", () => ({ + DateWithTime: () => null, +})); + +vi.mock("@/components/ui/entities/entity-info", () => ({ + EntityInfo: ({ + entityAlias, + entityId, + }: { + entityAlias?: string; + entityId?: string; + }) => ( +

+ {entityAlias} + {entityId} +
+ ), +})); + +vi.mock("@/components/ui/table", () => ({ + SeverityBadge: ({ severity }: { severity: string }) => ( + {severity} + ), +})); + +vi.mock("@/components/ui/table/data-table-column-header", () => ({ + DataTableColumnHeader: ({ title }: { title: string }) => {title}, +})); + +vi.mock("@/components/ui/table/status-finding-badge", () => ({ + StatusFindingBadge: ({ status }: { status: string }) => {status}, +})); + +vi.mock("@/lib/date-utils", () => ({ + getFailingForLabel: () => "2d", +})); + +const notificationIndicatorMock = vi.fn((_props: unknown) => null); + +vi.mock("./notification-indicator", () => ({ + NotificationIndicator: (props: unknown) => { + notificationIndicatorMock(props); + return null; + }, +})); + +import type { FindingResourceRow } from "@/types"; + +import { getColumnFindingResources } from "./column-finding-resources"; + +function makeResource( + overrides?: Partial, +): FindingResourceRow { + return { + id: "resource-row-1", + rowType: "resource", + findingId: "finding-1", + checkId: "s3_check", + providerType: "aws", + providerAlias: "production", + providerUid: "123456789", + resourceName: "my-bucket", + resourceType: "bucket", + resourceGroup: "default", + resourceUid: "arn:aws:s3:::my-bucket", + service: "s3", + region: "us-east-1", + severity: "critical", + status: "FAIL", + delta: "new", + isMuted: false, + firstSeenAt: null, + lastSeenAt: "2024-01-01T00:00:00Z", + ...overrides, + }; +} + +describe("column-finding-resources", () => { + it("should pass delta to NotificationIndicator for resource rows", () => { + const columns = getColumnFindingResources({ + rowSelection: {}, + selectableRowCount: 1, + }); + + const selectColumn = columns.find( + (col) => (col as { id?: string }).id === "select", + ); + if (!selectColumn?.cell) { + throw new Error("select column not found"); + } + + const CellComponent = selectColumn.cell as (props: { + row: { + id: string; + original: FindingResourceRow; + toggleSelected: (selected: boolean) => void; + }; + }) => ReactNode; + + render( +
+ {CellComponent({ + row: { + id: "0", + original: makeResource(), + toggleSelected: vi.fn(), + }, + })} +
, + ); + + expect(screen.getByLabelText("Select resource")).toBeInTheDocument(); + expect(notificationIndicatorMock).toHaveBeenCalledWith( + expect.objectContaining({ + delta: "new", + isMuted: false, + }), + ); + }); + + it("should render the resource EntityInfo with resourceName as alias", () => { + const columns = getColumnFindingResources({ + rowSelection: {}, + selectableRowCount: 1, + }); + + const resourceColumn = columns.find( + (col) => (col as { id?: string }).id === "resource", + ); + if (!resourceColumn?.cell) { + throw new Error("resource column not found"); + } + + const CellComponent = resourceColumn.cell as (props: { + row: { original: FindingResourceRow }; + }) => ReactNode; + + render( +
+ {CellComponent({ + row: { + original: makeResource(), + }, + })} +
, + ); + + expect(screen.getByText("my-bucket")).toBeInTheDocument(); + expect(screen.getByText("arn:aws:s3:::my-bucket")).toBeInTheDocument(); + }); +}); diff --git a/ui/components/findings/table/column-finding-resources.tsx b/ui/components/findings/table/column-finding-resources.tsx index 98541a8316..50fbb52f20 100644 --- a/ui/components/findings/table/column-finding-resources.tsx +++ b/ui/components/findings/table/column-finding-resources.tsx @@ -25,11 +25,16 @@ import { import { getFailingForLabel } from "@/lib/date-utils"; import { FindingResourceRow } from "@/types"; +import { canMuteFindingResource } from "./finding-resource-selection"; import { FindingsSelectionContext } from "./findings-selection-context"; -import { NotificationIndicator } from "./notification-indicator"; +import { + type DeltaType, + NotificationIndicator, +} from "./notification-indicator"; const ResourceRowActions = ({ row }: { row: Row }) => { const resource = row.original; + const canMute = canMuteFindingResource(resource); const [isMuteModalOpen, setIsMuteModalOpen] = useState(false); const [isJiraModalOpen, setIsJiraModalOpen] = useState(false); const [resolvedIds, setResolvedIds] = useState([]); @@ -81,7 +86,7 @@ const ResourceRowActions = ({ row }: { row: Row }) => { return ( <> - {!resource.isMuted && ( + {canMute && ( }) => { ) } label={isResolving ? "Resolving..." : getMuteLabel()} - disabled={resource.isMuted || isResolving} + disabled={!canMute || isResolving} onSelect={handleMuteClick} /> (
@@ -178,7 +184,7 @@ export function getColumnFindingResources({ row.toggleSelected(checked === true)} onClick={(e) => e.stopPropagation()} aria-label="Select resource" @@ -198,7 +204,7 @@ export function getColumnFindingResources({
} - entityAlias={row.original.resourceGroup} + entityAlias={row.original.resourceName} entityId={row.original.resourceUid} />
@@ -213,8 +219,12 @@ export function getColumnFindingResources({ ), cell: ({ row }) => { const rawStatus = row.original.status; - const status = - rawStatus === "MUTED" ? "FAIL" : (rawStatus as FindingStatus); + const status: FindingStatus = + rawStatus === "MUTED" || rawStatus === "FAIL" + ? "FAIL" + : rawStatus === "PASS" + ? "PASS" + : "FAIL"; return ; }, enableSorting: false, diff --git a/ui/components/findings/table/finding-group-selection.test.ts b/ui/components/findings/table/finding-group-selection.test.ts new file mode 100644 index 0000000000..f00d17a73e --- /dev/null +++ b/ui/components/findings/table/finding-group-selection.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; + +import { canMuteFindingGroup } from "./finding-group-selection"; + +describe("canMuteFindingGroup", () => { + it("returns false when impacted resources is zero", () => { + expect( + canMuteFindingGroup({ + resourcesFail: 0, + resourcesTotal: 2, + mutedCount: 0, + }), + ).toBe(false); + }); + + it("returns false when all resources are already muted", () => { + expect( + canMuteFindingGroup({ + resourcesFail: 3, + resourcesTotal: 3, + mutedCount: 3, + }), + ).toBe(false); + }); + + it("returns false when all failing resources are muted even if PASS resources exist", () => { + expect( + canMuteFindingGroup({ + resourcesFail: 2, + resourcesTotal: 5, + mutedCount: 2, + }), + ).toBe(false); + }); + + it("returns true when the group still has failing resources to mute", () => { + expect( + canMuteFindingGroup({ + resourcesFail: 2, + resourcesTotal: 5, + mutedCount: 1, + }), + ).toBe(true); + }); +}); diff --git a/ui/components/findings/table/finding-group-selection.ts b/ui/components/findings/table/finding-group-selection.ts new file mode 100644 index 0000000000..f9db1cf11e --- /dev/null +++ b/ui/components/findings/table/finding-group-selection.ts @@ -0,0 +1,13 @@ +interface FindingGroupSelectionState { + resourcesFail: number; + resourcesTotal: number; + mutedCount: number; +} + +export function canMuteFindingGroup({ + resourcesFail, + mutedCount, +}: FindingGroupSelectionState): boolean { + const allMuted = mutedCount > 0 && mutedCount === resourcesFail; + return resourcesFail > 0 && !allMuted; +} diff --git a/ui/components/findings/table/finding-resource-selection.test.ts b/ui/components/findings/table/finding-resource-selection.test.ts new file mode 100644 index 0000000000..8abb3f7a8a --- /dev/null +++ b/ui/components/findings/table/finding-resource-selection.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from "vitest"; + +import type { FindingResourceRow } from "@/types"; + +import { canMuteFindingResource } from "./finding-resource-selection"; + +function makeResource( + overrides?: Partial, +): FindingResourceRow { + return { + id: "finding-1", + rowType: "resource", + findingId: "finding-1", + checkId: "check-1", + providerType: "aws", + providerAlias: "prod", + providerUid: "123456789012", + resourceName: "bucket-a", + resourceType: "Bucket", + resourceGroup: "bucket-a", + resourceUid: "arn:aws:s3:::bucket-a", + service: "s3", + region: "us-east-1", + severity: "high", + status: "FAIL", + isMuted: false, + firstSeenAt: null, + lastSeenAt: null, + ...overrides, + }; +} + +describe("canMuteFindingResource", () => { + it("should allow muting FAIL resources that are not muted", () => { + expect(canMuteFindingResource(makeResource())).toBe(true); + }); + + it("should disable muting for PASS resources", () => { + expect(canMuteFindingResource(makeResource({ status: "PASS" }))).toBe( + false, + ); + }); + + it("should disable muting for already muted resources", () => { + expect(canMuteFindingResource(makeResource({ isMuted: true }))).toBe(false); + }); +}); diff --git a/ui/components/findings/table/finding-resource-selection.ts b/ui/components/findings/table/finding-resource-selection.ts new file mode 100644 index 0000000000..f6bfb3312a --- /dev/null +++ b/ui/components/findings/table/finding-resource-selection.ts @@ -0,0 +1,5 @@ +import { FindingResourceRow } from "@/types"; + +export function canMuteFindingResource(resource: FindingResourceRow): boolean { + return resource.status === "FAIL" && !resource.isMuted; +} diff --git a/ui/components/findings/table/findings-group-drill-down.tsx b/ui/components/findings/table/findings-group-drill-down.tsx index 3046c09a4a..53a568e759 100644 --- a/ui/components/findings/table/findings-group-drill-down.tsx +++ b/ui/components/findings/table/findings-group-drill-down.tsx @@ -28,6 +28,7 @@ import { FindingGroupRow, FindingResourceRow } from "@/types"; import { FloatingMuteButton } from "../floating-mute-button"; import { getColumnFindingResources } from "./column-finding-resources"; +import { canMuteFindingResource } from "./finding-resource-selection"; import { FindingsSelectionContext } from "./findings-selection-context"; import { ImpactedResourcesCell } from "./impacted-resources-cell"; import { DeltaValues, NotificationIndicator } from "./notification-indicator"; @@ -82,7 +83,7 @@ export function FindingsGroupDrillDown({ setIsLoading(loading); }; - const { sentinelRef, refresh, loadMore } = useInfiniteResources({ + const { sentinelRef, refresh, loadMore, totalCount } = useInfiniteResources({ checkId: group.checkId, hasDateOrScanFilter: hasDateOrScan, filters, @@ -95,7 +96,7 @@ export function FindingsGroupDrillDown({ const drawer = useResourceDetailDrawer({ resources, checkId: group.checkId, - totalResourceCount: group.resourcesTotal, + totalResourceCount: totalCount ?? group.resourcesTotal, onRequestMoreResources: loadMore, }); @@ -108,7 +109,7 @@ export function FindingsGroupDrillDown({ const selectedFindingIds = Object.keys(rowSelection) .filter((key) => rowSelection[key]) .map((idx) => resources[parseInt(idx)]?.findingId) - .filter(Boolean); + .filter((id): id is string => id !== null && id !== undefined && id !== ""); /** Converts resource_ids (display) → resourceUids → finding UUIDs via API. */ const resolveResourceIds = async (ids: string[]) => { @@ -124,10 +125,10 @@ export function FindingsGroupDrillDown({ }); }; - const selectableRowCount = resources.filter((r) => !r.isMuted).length; + const selectableRowCount = resources.filter(canMuteFindingResource).length; const getRowCanSelect = (row: Row): boolean => { - return !row.original.isMuted; + return canMuteFindingResource(row.original); }; const clearSelection = () => { diff --git a/ui/components/findings/table/findings-group-table.tsx b/ui/components/findings/table/findings-group-table.tsx index bcbc8934b0..3d1fd24882 100644 --- a/ui/components/findings/table/findings-group-table.tsx +++ b/ui/components/findings/table/findings-group-table.tsx @@ -14,6 +14,7 @@ import { FindingGroupRow, MetaDataProps } from "@/types"; import { FloatingMuteButton } from "../floating-mute-button"; import { getColumnFindingGroups } from "./column-finding-groups"; +import { canMuteFindingGroup } from "./finding-group-selection"; import { FindingsSelectionContext } from "./findings-selection-context"; import { InlineResourceContainer, @@ -88,13 +89,21 @@ export function FindingsGroupTable({ .filter(Boolean); // Count of selectable rows (groups where not ALL findings are muted) - const selectableRowCount = safeData.filter( - (g) => !(g.mutedCount > 0 && g.mutedCount === g.resourcesTotal), + const selectableRowCount = safeData.filter((g) => + canMuteFindingGroup({ + resourcesFail: g.resourcesFail, + resourcesTotal: g.resourcesTotal, + mutedCount: g.mutedCount, + }), ).length; const getRowCanSelect = (row: Row): boolean => { const group = row.original; - return !(group.mutedCount > 0 && group.mutedCount === group.resourcesTotal); + return canMuteFindingGroup({ + resourcesFail: group.resourcesFail, + resourcesTotal: group.resourcesTotal, + mutedCount: group.mutedCount, + }); }; const clearSelection = () => { @@ -136,8 +145,8 @@ export function FindingsGroupTable({ }; const handleDrillDown = (checkId: string, group: FindingGroupRow) => { - // No impacted resources → nothing to show, skip drill-down - if (group.resourcesFail === 0) return; + // No resources in the group → nothing to show, skip drill-down + if (group.resourcesTotal === 0) return; // Toggle: same group = collapse, different = switch if (expandedCheckId === checkId) { diff --git a/ui/components/findings/table/inline-resource-container.tsx b/ui/components/findings/table/inline-resource-container.tsx index e44c3db2de..9b85b58406 100644 --- a/ui/components/findings/table/inline-resource-container.tsx +++ b/ui/components/findings/table/inline-resource-container.tsx @@ -22,6 +22,7 @@ import { hasDateOrScanFilter } from "@/lib"; import { FindingGroupRow, FindingResourceRow } from "@/types"; import { getColumnFindingResources } from "./column-finding-resources"; +import { canMuteFindingResource } from "./finding-resource-selection"; import { FindingsSelectionContext } from "./findings-selection-context"; import { ResourceDetailDrawer, @@ -180,7 +181,7 @@ export function InlineResourceContainer({ setIsLoading(loading); }; - const { sentinelRef, refresh, loadMore } = useInfiniteResources({ + const { sentinelRef, refresh, loadMore, totalCount } = useInfiniteResources({ checkId: group.checkId, hasDateOrScanFilter: hasDateOrScan, filters, @@ -194,7 +195,7 @@ export function InlineResourceContainer({ const drawer = useResourceDetailDrawer({ resources, checkId: group.checkId, - totalResourceCount: group.resourcesTotal, + totalResourceCount: totalCount ?? group.resourcesTotal, onRequestMoreResources: loadMore, }); @@ -222,10 +223,10 @@ export function InlineResourceContainer({ }); }; - const selectableRowCount = resources.filter((r) => !r.isMuted).length; + const selectableRowCount = resources.filter(canMuteFindingResource).length; const getRowCanSelect = (row: Row): boolean => { - return !row.original.isMuted; + return canMuteFindingResource(row.original); }; const clearSelection = () => { diff --git a/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.test.tsx b/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.test.tsx index d3e588dd9e..3835242e19 100644 --- a/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.test.tsx +++ b/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.test.tsx @@ -1,6 +1,7 @@ -import { render, screen } from "@testing-library/react"; +import { render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react"; +import { createPortal } from "react-dom"; import { afterEach, describe, expect, it, vi } from "vitest"; // --------------------------------------------------------------------------- @@ -10,17 +11,17 @@ import { afterEach, describe, expect, it, vi } from "vitest"; const { mockGetComplianceIcon, mockGetCompliancesOverview, - mockRouterPush, + mockWindowOpen, mockSearchParamsState, } = vi.hoisted(() => ({ mockGetComplianceIcon: vi.fn((_: string) => null as string | null), mockGetCompliancesOverview: vi.fn(), - mockRouterPush: vi.fn(), + mockWindowOpen: vi.fn(), mockSearchParamsState: { value: "" }, })); vi.mock("next/navigation", () => ({ - useRouter: () => ({ push: mockRouterPush, refresh: vi.fn() }), + useRouter: () => ({ refresh: vi.fn() }), usePathname: () => "/findings", useSearchParams: () => new URLSearchParams(mockSearchParamsState.value), redirect: vi.fn(), @@ -104,10 +105,30 @@ vi.mock("@/components/shadcn/card/card", () => ({ })); vi.mock("@/components/shadcn/dropdown", () => ({ - ActionDropdown: ({ children }: { children: ReactNode }) => ( -
{children}
+ ActionDropdown: ({ + children, + ariaLabel, + }: { + children: ReactNode; + ariaLabel?: string; + }) => ( +
+ {children} +
+ ), + ActionDropdownItem: ({ + label, + disabled, + onSelect, + }: { + label: string; + disabled?: boolean; + onSelect?: () => void; + }) => ( + ), - ActionDropdownItem: () => null, })); vi.mock("@/components/shadcn/skeleton/skeleton", () => ({ @@ -125,7 +146,25 @@ vi.mock("@/components/shadcn/tooltip", () => ({ })); vi.mock("@/components/findings/mute-findings-modal", () => ({ - MuteFindingsModal: () => null, + MuteFindingsModal: ({ + isOpen, + findingIds, + onComplete, + }: { + isOpen: boolean; + findingIds: string[]; + onComplete?: () => void; + }) => + isOpen + ? globalThis.document?.body && + // Render into body to mirror the real modal portal behavior. + createPortal( + , + globalThis.document.body, + ) + : null, })); vi.mock("@/components/findings/send-to-jira-modal", () => ({ @@ -547,9 +586,14 @@ describe("ResourceDetailDrawerContent — compliance icon styling", () => { }); describe("ResourceDetailDrawerContent — compliance navigation", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + it("should resolve the clicked framework against the selected scan and navigate to compliance detail", async () => { // Given const user = userEvent.setup(); + vi.stubGlobal("open", mockWindowOpen); mockSearchParamsState.value = "filter[scan__in]=scan-selected&filter[region__in]=eu-west-1"; mockGetCompliancesOverview.mockResolvedValue({ @@ -595,14 +639,17 @@ describe("ResourceDetailDrawerContent — compliance navigation", () => { expect(mockGetCompliancesOverview).toHaveBeenCalledWith({ scanId: "scan-selected", }); - expect(mockRouterPush).toHaveBeenCalledWith( + expect(mockWindowOpen).toHaveBeenCalledWith( "/compliance/PCI-DSS?complianceId=compliance-1&version=4.0&scanId=scan-selected&filter%5Bregion__in%5D=eu-west-1", + "_blank", + "noopener,noreferrer", ); }); it("should use the current finding scan when no scan filter is active", async () => { // Given const user = userEvent.setup(); + vi.stubGlobal("open", mockWindowOpen); mockGetCompliancesOverview.mockResolvedValue({ data: [ { @@ -662,8 +709,134 @@ describe("ResourceDetailDrawerContent — compliance navigation", () => { expect(mockGetCompliancesOverview).toHaveBeenCalledWith({ scanId: "scan-from-finding", }); - expect(mockRouterPush).toHaveBeenCalledWith( + expect(mockWindowOpen).toHaveBeenCalledWith( "/compliance/PCI-DSS?complianceId=compliance-2&version=4.0&scanId=scan-from-finding&scanData=%7B%22id%22%3A%22scan-from-finding%22%2C%22providerInfo%22%3A%7B%22provider%22%3A%22aws%22%2C%22alias%22%3A%22prod%22%2C%22uid%22%3A%22123456789%22%7D%2C%22attributes%22%3A%7B%22name%22%3A%22Nightly+scan%22%2C%22completed_at%22%3A%222026-03-30T10%3A05%3A00Z%22%7D%7D", + "_blank", + "noopener,noreferrer", + ); + }); + + it("should navigate when the finding framework is a short alias of the compliance overview framework", async () => { + // Given + const user = userEvent.setup(); + vi.stubGlobal("open", mockWindowOpen); + mockGetComplianceIcon.mockImplementation((framework: string) => + framework.toLowerCase().includes("kisa") ? "/kisa.svg" : null, + ); + mockGetCompliancesOverview.mockResolvedValue({ + data: [ + { + id: "compliance-kisa", + type: "compliance-overviews", + attributes: { + framework: "KISA-ISMS-P", + version: "1.0", + requirements_passed: 5, + requirements_failed: 1, + requirements_manual: 0, + total_requirements: 6, + }, + }, + ], + }); + const findingWithScan = { + ...mockFinding, + scan: { + id: "scan-from-finding", + name: "Nightly scan", + trigger: "manual", + state: "completed", + uniqueResourceCount: 25, + progress: 100, + duration: 300, + startedAt: "2026-03-30T10:00:00Z", + completedAt: "2026-03-30T10:05:00Z", + insertedAt: "2026-03-30T09:59:00Z", + scheduledAt: null, + }, + }; + + render( + , + ); + + // When + await user.click( + screen.getByRole("button", { + name: "Open KISA compliance details", + }), + ); + + // Then + expect(mockGetCompliancesOverview).toHaveBeenCalledWith({ + scanId: "scan-from-finding", + }); + expect(mockWindowOpen).toHaveBeenCalledWith( + "/compliance/KISA-ISMS-P?complianceId=compliance-kisa&version=1.0&scanId=scan-from-finding&scanData=%7B%22id%22%3A%22scan-from-finding%22%2C%22providerInfo%22%3A%7B%22provider%22%3A%22aws%22%2C%22alias%22%3A%22prod%22%2C%22uid%22%3A%22123456789%22%7D%2C%22attributes%22%3A%7B%22name%22%3A%22Nightly+scan%22%2C%22completed_at%22%3A%222026-03-30T10%3A05%3A00Z%22%7D%7D", + "_blank", + "noopener,noreferrer", ); }); }); + +describe("ResourceDetailDrawerContent — other findings mute refresh", () => { + it("should update only the muted other-finding row without refreshing the current finding group", async () => { + // Given + const user = userEvent.setup(); + const onMuteComplete = vi.fn(); + const otherFinding: ResourceDrawerFinding = { + ...mockFinding, + id: "finding-2", + uid: "uid-2", + checkId: "ec2_check", + checkTitle: "EC2 Check", + updatedAt: "2026-03-30T10:05:00Z", + }; + + render( + , + ); + + // When + const row = screen.getByText("EC2 Check").closest("tr"); + expect(row).not.toBeNull(); + + await user.click( + within(row as HTMLElement).getByRole("button", { name: "Mute" }), + ); + await user.click( + screen.getByRole("button", { name: "Confirm mute finding-2" }), + ); + + // Then + expect( + within(row as HTMLElement).getByRole("button", { name: "Muted" }), + ).toBeDisabled(); + expect(onMuteComplete).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx b/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx index 509f128b97..09bf786f07 100644 --- a/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx +++ b/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx @@ -12,7 +12,7 @@ import { } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useSearchParams } from "next/navigation"; import { useState } from "react"; import { getCompliancesOverview } from "@/actions/compliances"; @@ -84,7 +84,90 @@ function normalizeComplianceFrameworkName(framework: string): string { return framework .trim() .toLowerCase() - .replace(/[\s_]+/g, "-"); + .replace(/[\s_]+/g, "-") + .replace(/-+/g, "-"); +} + +function stripComplianceVersionSuffix(framework: string): string { + return framework.replace(/-\d+(?:\.\d+)*$/g, ""); +} + +function canonicalComplianceKey(framework: string): string { + return stripComplianceVersionSuffix( + normalizeComplianceFrameworkName(framework), + ) + .replace(/[^a-z0-9]+/g, "") + .trim(); +} + +function complianceTokens(framework: string): string[] { + return stripComplianceVersionSuffix( + normalizeComplianceFrameworkName(framework), + ) + .split("-") + .map((token) => token.trim()) + .filter(Boolean) + .filter((token) => !/^\d+(?:\.\d+)*$/.test(token)); +} + +function complianceMatchScore( + sourceFramework: string, + targetFramework: string, +): number { + const normalizedSource = normalizeComplianceFrameworkName(sourceFramework); + const normalizedTarget = normalizeComplianceFrameworkName(targetFramework); + + if (normalizedSource === normalizedTarget) { + return 5; + } + + const canonicalSource = canonicalComplianceKey(sourceFramework); + const canonicalTarget = canonicalComplianceKey(targetFramework); + + if (canonicalSource === canonicalTarget) { + return 4; + } + + if (canonicalSource && canonicalTarget) { + const sourceTokens = canonicalSource.split("-"); + const targetTokens = canonicalTarget.split("-"); + if ( + sourceTokens.length !== targetTokens.length && + (sourceTokens.every((t) => targetTokens.includes(t)) || + targetTokens.every((t) => sourceTokens.includes(t))) + ) { + return 3; + } + } + + const sourceTokens = complianceTokens(sourceFramework); + const targetTokens = complianceTokens(targetFramework); + if (!sourceTokens.length || !targetTokens.length) { + return 0; + } + + const sourceMatchesTarget = sourceTokens.every((token) => + targetTokens.includes(token), + ); + const targetMatchesSource = targetTokens.every((token) => + sourceTokens.includes(token), + ); + + if (sourceMatchesTarget || targetMatchesSource) { + return 2; + } + + if ( + sourceTokens.some((token) => targetTokens.includes(token)) && + canonicalSource && + canonicalTarget && + (canonicalTarget.includes(canonicalSource) || + canonicalSource.includes(canonicalTarget)) + ) { + return 1; + } + + return 0; } function parseSelectedScanIds(scanFilterValue: string | null): string[] { @@ -110,12 +193,13 @@ function resolveComplianceMatch( return null; } - const normalizedFramework = normalizeComplianceFrameworkName(framework); - const match = compliances.find( - (compliance) => - normalizeComplianceFrameworkName(compliance.attributes.framework) === - normalizedFramework, - ); + const match = compliances + .map((compliance) => ({ + compliance, + score: complianceMatchScore(framework, compliance.attributes.framework), + })) + .filter(({ score }) => score > 0) + .sort((a, b) => b.score - a.score)[0]?.compliance; if (!match) { return null; @@ -202,13 +286,15 @@ export function ResourceDetailDrawerContent({ onNavigateNext, onMuteComplete, }: ResourceDetailDrawerContentProps) { - const router = useRouter(); const searchParams = useSearchParams(); const [isMuteModalOpen, setIsMuteModalOpen] = useState(false); const [isJiraModalOpen, setIsJiraModalOpen] = useState(false); const [resolvingFramework, setResolvingFramework] = useState( null, ); + const [optimisticallyMutedIds, setOptimisticallyMutedIds] = useState< + Set + >(new Set()); // Initial load — no check metadata yet if (!checkMeta && isLoading) { @@ -284,7 +370,7 @@ export function ResourceDetailDrawerContent({ return; } - router.push( + window.open( buildComplianceDetailHref({ complianceId: complianceMatch.complianceId, framework: complianceMatch.framework, @@ -294,6 +380,8 @@ export function ResourceDetailDrawerContent({ currentFinding: f, includeScanData: f?.scan?.id === complianceScanId, }), + "_blank", + "noopener,noreferrer", ); } catch (error) { console.error("Error resolving compliance detail:", error); @@ -428,10 +516,10 @@ export function ResourceDetailDrawerContent({ )}
- {/* Navigation: "Impacted Resource (X of N)" */} + {/* Navigation: "Resource (X of N)" */}
- Impacted Resource + Resource {currentIndex + 1} of {totalResources} @@ -477,7 +565,7 @@ export function ResourceDetailDrawerContent({ /> } - entityAlias={f.resourceGroup} + entityAlias={f.resourceName} entityId={f.resourceUid} idLabel="UID" /> @@ -505,7 +593,9 @@ export function ResourceDetailDrawerContent({ {getFailingForLabel(f.firstSeenAt) || "-"} -
+ + {f.resourceGroup || "-"} + {/* Row 3: IDs */} @@ -529,6 +619,11 @@ export function ResourceDetailDrawerContent({ className="max-w-full text-sm" /> + + {/* Row 4: Resource metadata */} + + {f.resourceType || "-"} +
{/* Actions button — fixed size, aligned with row 1 */} @@ -757,10 +852,7 @@ export function ResourceDetailDrawerContent({
) : ( <> -
-

- Failed Findings For This Resource -

+
{otherFindings.length} Total Entries @@ -796,7 +888,18 @@ export function ResourceDetailDrawerContent({ {otherFindings.length > 0 ? ( otherFindings.map((finding) => ( - + + setOptimisticallyMutedIds((prev) => + new Set(prev).add(finding.id), + ) + } + /> )) ) : ( @@ -908,19 +1011,32 @@ export function ResourceDetailDrawerContent({ ); } -function OtherFindingRow({ finding }: { finding: ResourceDrawerFinding }) { +function OtherFindingRow({ + finding, + isOptimisticallyMuted, + onMuted, +}: { + finding: ResourceDrawerFinding; + isOptimisticallyMuted: boolean; + onMuted: () => void; +}) { const [isMuteModalOpen, setIsMuteModalOpen] = useState(false); const [isJiraModalOpen, setIsJiraModalOpen] = useState(false); + const isMuted = finding.isMuted || isOptimisticallyMuted; const findingUrl = `/findings?filter%5Bcheck_id__in%5D=${encodeURIComponent(finding.checkId)}&filter%5Bmuted%5D=include`; return ( <> - {!finding.isMuted && ( + {!isMuted && ( { + setIsMuteModalOpen(false); + onMuted(); + }} /> )} window.open(findingUrl, "_blank", "noopener,noreferrer")} > - + @@ -955,14 +1071,14 @@ function OtherFindingRow({ finding }: { finding: ResourceDrawerFinding }) { ) : ( ) } - label={finding.isMuted ? "Muted" : "Mute"} - disabled={finding.isMuted} + label={isMuted ? "Muted" : "Mute"} + disabled={isMuted} onSelect={() => setIsMuteModalOpen(true)} /> ({ + Skeleton: ({ className }: { className?: string }) => ( +
+ ), +})); + +import { ResourceDetailSkeleton } from "./resource-detail-skeleton"; + +describe("ResourceDetailSkeleton", () => { + it("should include placeholders for group and resource type fields", () => { + render(); + + const blocks = screen.getAllByTestId("skeleton-block"); + const classes = blocks.map( + (block) => block.getAttribute("data-class") ?? "", + ); + + expect(classes).toContain("h-3.5 w-10 rounded"); + expect(classes).toContain("h-5 w-18 rounded"); + expect(classes).toContain("h-3.5 w-20 rounded"); + expect(classes).toContain("h-5 w-28 rounded"); + }); +}); diff --git a/ui/components/findings/table/resource-detail-drawer/resource-detail-skeleton.tsx b/ui/components/findings/table/resource-detail-drawer/resource-detail-skeleton.tsx index ed123983f5..9ef08ee14e 100644 --- a/ui/components/findings/table/resource-detail-drawer/resource-detail-skeleton.tsx +++ b/ui/components/findings/table/resource-detail-drawer/resource-detail-skeleton.tsx @@ -2,8 +2,8 @@ import { Skeleton } from "@/components/shadcn/skeleton/skeleton"; /** * Skeleton placeholder for the resource info grid in the detail drawer. - * Mirrors the 4-column layout: EntityInfo × 2, InfoField × 2 per row, - * plus the actions button. + * Mirrors the drawer layout so added metadata fields don't leave visual gaps + * while the next resource is loading. */ export function ResourceDetailSkeleton() { return ( @@ -15,16 +15,19 @@ export function ResourceDetailSkeleton() { - {/* Row 2: Last detected, First seen, Failing for */} + {/* Row 2: Last detected, First seen, Failing for, Group */} -
+ {/* Row 3: Check ID, Finding ID, Finding UID */} + + {/* Row 4: Resource type */} +
{/* Actions button */} diff --git a/ui/components/findings/table/resource-detail-drawer/use-resource-detail-drawer.test.ts b/ui/components/findings/table/resource-detail-drawer/use-resource-detail-drawer.test.ts index 404be9b165..4ce921a4e8 100644 --- a/ui/components/findings/table/resource-detail-drawer/use-resource-detail-drawer.test.ts +++ b/ui/components/findings/table/resource-detail-drawer/use-resource-detail-drawer.test.ts @@ -26,6 +26,7 @@ vi.mock("next/navigation", () => ({ // Import after mocks // --------------------------------------------------------------------------- +import type { ResourceDrawerFinding } from "@/actions/findings"; import type { FindingResourceRow } from "@/types"; import { useResourceDetailDrawer } from "./use-resource-detail-drawer"; @@ -60,6 +61,46 @@ function makeResource( } as FindingResourceRow; } +function makeDrawerFinding( + overrides?: Partial, +): ResourceDrawerFinding { + return { + id: "finding-1", + uid: "uid-1", + checkId: "s3_check", + checkTitle: "S3 Check", + status: "FAIL", + severity: "high", + delta: null, + isMuted: false, + mutedReason: null, + firstSeenAt: null, + updatedAt: null, + resourceId: "resource-1", + resourceUid: "arn:aws:s3:::my-bucket", + resourceName: "my-bucket", + resourceService: "s3", + resourceRegion: "us-east-1", + resourceType: "bucket", + resourceGroup: "default", + providerType: "aws", + providerAlias: "prod", + providerUid: "123", + risk: "high", + description: "desc", + statusExtended: "status", + complianceFrameworks: [], + categories: [], + remediation: { + recommendation: { text: "", url: "" }, + code: { cli: "", other: "", nativeiac: "", terraform: "" }, + }, + additionalUrls: [], + scan: null, + ...overrides, + }; +} + // --------------------------------------------------------------------------- // Fix 2: AbortController cleanup on unmount // --------------------------------------------------------------------------- @@ -128,3 +169,212 @@ describe("useResourceDetailDrawer — unmount cleanup", () => { expect(abortSpy).not.toHaveBeenCalled(); }); }); + +describe("useResourceDetailDrawer — other findings filtering", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should exclude the current finding from otherFindings and preserve API order", async () => { + const resources = [makeResource()]; + + getLatestFindingsByResourceUidMock.mockResolvedValue({ data: [] }); + adaptFindingsByResourceResponseMock.mockReturnValue([ + makeDrawerFinding({ + id: "current", + checkId: "s3_check", + checkTitle: "Current", + status: "FAIL", + severity: "critical", + }), + makeDrawerFinding({ + id: "other-1", + checkId: "check-other-1", + checkTitle: "Other 1", + status: "PASS", + severity: "critical", + }), + makeDrawerFinding({ + id: "other-2", + checkId: "check-other-2", + checkTitle: "Other 2", + status: "FAIL", + severity: "medium", + }), + ]); + + const { result } = renderHook(() => + useResourceDetailDrawer({ + resources, + checkId: "s3_check", + }), + ); + + await act(async () => { + result.current.openDrawer(0); + await Promise.resolve(); + }); + + expect(result.current.otherFindings.map((finding) => finding.id)).toEqual([ + "other-1", + "other-2", + ]); + }); + + it("should keep isNavigating true for a cached resource long enough to render skeletons", async () => { + vi.useFakeTimers(); + + const resources = [ + makeResource({ + id: "row-1", + findingId: "finding-1", + resourceUid: "arn:aws:s3:::first-bucket", + resourceName: "first-bucket", + }), + makeResource({ + id: "row-2", + findingId: "finding-2", + resourceUid: "arn:aws:s3:::second-bucket", + resourceName: "second-bucket", + }), + ]; + + getLatestFindingsByResourceUidMock.mockImplementation( + async ({ resourceUid }: { resourceUid: string }) => ({ + data: [resourceUid], + }), + ); + adaptFindingsByResourceResponseMock.mockImplementation( + (response: { data: string[] }) => [ + makeDrawerFinding({ + id: response.data[0].includes("first") ? "finding-1" : "finding-2", + resourceUid: response.data[0], + resourceName: response.data[0].includes("first") + ? "first-bucket" + : "second-bucket", + }), + ], + ); + + const { result } = renderHook(() => + useResourceDetailDrawer({ + resources, + checkId: "s3_check", + }), + ); + + await act(async () => { + result.current.openDrawer(0); + await Promise.resolve(); + }); + + await act(async () => { + result.current.navigateNext(); + await Promise.resolve(); + }); + + expect(result.current.currentIndex).toBe(1); + expect(result.current.currentFinding?.id).toBe("finding-2"); + + act(() => { + result.current.navigatePrev(); + }); + + expect(result.current.currentIndex).toBe(0); + expect(result.current.isNavigating).toBe(true); + + await act(async () => { + vi.runAllTimers(); + await Promise.resolve(); + }); + + expect(result.current.isNavigating).toBe(false); + expect(result.current.currentFinding?.id).toBe("finding-1"); + + vi.useRealTimers(); + }); + + it("should keep isNavigating true for a fast uncached navigation long enough to avoid flicker", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-04-08T15:00:00.000Z")); + + const resources = [ + makeResource({ + id: "row-1", + findingId: "finding-1", + resourceUid: "arn:aws:s3:::first-bucket", + resourceName: "first-bucket", + }), + makeResource({ + id: "row-2", + findingId: "finding-2", + resourceUid: "arn:aws:s3:::second-bucket", + resourceName: "second-bucket", + }), + ]; + + getLatestFindingsByResourceUidMock.mockImplementation( + async ({ resourceUid }: { resourceUid: string }) => ({ + data: [resourceUid], + }), + ); + adaptFindingsByResourceResponseMock.mockImplementation( + (response: { data: string[] }) => [ + makeDrawerFinding({ + id: response.data[0].includes("first") ? "finding-1" : "finding-2", + resourceUid: response.data[0], + resourceName: response.data[0].includes("first") + ? "first-bucket" + : "second-bucket", + }), + ], + ); + + const { result } = renderHook(() => + useResourceDetailDrawer({ + resources, + checkId: "s3_check", + }), + ); + + await act(async () => { + result.current.openDrawer(0); + await Promise.resolve(); + }); + + act(() => { + result.current.navigateNext(); + }); + + expect(result.current.currentIndex).toBe(1); + expect(result.current.isNavigating).toBe(true); + + await act(async () => { + await Promise.resolve(); + }); + + expect(result.current.currentFinding?.id).toBe("finding-2"); + expect(result.current.isNavigating).toBe(true); + + await act(async () => { + vi.advanceTimersByTime(119); + await Promise.resolve(); + }); + + expect(result.current.isNavigating).toBe(true); + + await act(async () => { + vi.advanceTimersByTime(1); + await Promise.resolve(); + }); + + await act(async () => { + vi.runOnlyPendingTimers(); + await Promise.resolve(); + }); + + expect(result.current.isNavigating).toBe(false); + + vi.useRealTimers(); + }); +}); diff --git a/ui/components/findings/table/resource-detail-drawer/use-resource-detail-drawer.ts b/ui/components/findings/table/resource-detail-drawer/use-resource-detail-drawer.ts index d1d9eb96d3..c9bf263dc0 100644 --- a/ui/components/findings/table/resource-detail-drawer/use-resource-detail-drawer.ts +++ b/ui/components/findings/table/resource-detail-drawer/use-resource-detail-drawer.ts @@ -9,6 +9,10 @@ import { } from "@/actions/findings"; import { FindingResourceRow } from "@/types"; +// Keep fast carousel navigations in a loading state for one short beat so +// React doesn't batch away the skeleton frame when switching resources. +const MIN_NAVIGATION_SKELETON_MS = 300; + /** * Check-level metadata that is identical across all resources for a given check. * Extracted once on first successful fetch and kept stable during navigation. @@ -83,18 +87,65 @@ export function useResourceDetailDrawer({ const cacheRef = useRef>(new Map()); const checkMetaRef = useRef(null); const fetchControllerRef = useRef(null); + const navigationTimeoutRef = useRef | null>( + null, + ); + const navigationStartedAtRef = useRef(null); + + const clearNavigationTimeout = () => { + if (navigationTimeoutRef.current !== null) { + clearTimeout(navigationTimeoutRef.current); + navigationTimeoutRef.current = null; + } + }; + + const finishNavigation = () => { + clearNavigationTimeout(); + setIsLoading(false); + + const navigationStartedAt = navigationStartedAtRef.current; + if (navigationStartedAt === null) { + navigationStartedAtRef.current = null; + setIsNavigating(false); + return; + } + + const elapsed = Date.now() - navigationStartedAt; + const remaining = Math.max(0, MIN_NAVIGATION_SKELETON_MS - elapsed); + + if (remaining === 0) { + navigationStartedAtRef.current = null; + setIsNavigating(false); + return; + } + + navigationTimeoutRef.current = setTimeout(() => { + setIsNavigating(false); + navigationStartedAtRef.current = null; + navigationTimeoutRef.current = null; + }, remaining); + }; + + const startNavigation = () => { + clearNavigationTimeout(); + navigationStartedAtRef.current = Date.now(); + setIsNavigating(true); + }; // Abort any in-flight request on unmount to prevent state updates // on an already-unmounted component. useEffect(() => { return () => { fetchControllerRef.current?.abort(); + clearNavigationTimeout(); + navigationStartedAtRef.current = null; }; }, []); const fetchFindings = async (resourceUid: string) => { // Abort any in-flight request to prevent stale data from out-of-order responses fetchControllerRef.current?.abort(); + clearNavigationTimeout(); const controller = new AbortController(); fetchControllerRef.current = controller; @@ -106,8 +157,7 @@ export function useResourceDetailDrawer({ if (main) checkMetaRef.current = extractCheckMeta(main); } setFindings(cached); - setIsLoading(false); - setIsNavigating(false); + finishNavigation(); return; } @@ -135,8 +185,7 @@ export function useResourceDetailDrawer({ } } finally { if (!controller.signal.aborted) { - setIsLoading(false); - setIsNavigating(false); + finishNavigation(); } } }; @@ -145,8 +194,11 @@ export function useResourceDetailDrawer({ const resource = resources[index]; if (!resource) return; + clearNavigationTimeout(); + navigationStartedAtRef.current = null; setCurrentIndex(index); setIsOpen(true); + setIsNavigating(false); setFindings([]); fetchFindings(resource.resourceUid); }; @@ -159,7 +211,7 @@ export function useResourceDetailDrawer({ const resource = resources[currentIndex]; if (!resource) return; cacheRef.current.delete(resource.resourceUid); - setIsNavigating(true); + startNavigation(); fetchFindings(resource.resourceUid); }; @@ -168,7 +220,7 @@ export function useResourceDetailDrawer({ if (!resource) return; setCurrentIndex(index); - setIsNavigating(true); + startNavigation(); fetchFindings(resource.resourceUid); }; diff --git a/ui/components/scans/scans-filters.tsx b/ui/components/scans/scans-filters.tsx index 6273acac77..60dbca0bfc 100644 --- a/ui/components/scans/scans-filters.tsx +++ b/ui/components/scans/scans-filters.tsx @@ -3,20 +3,23 @@ import { X } from "lucide-react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { ScanSelector } from "@/components/compliance/compliance-header"; import { filterScans } from "@/components/filters/data-filters"; import { FilterControls } from "@/components/filters/filter-controls"; import { Badge } from "@/components/shadcn/badge/badge"; import { useRelatedFilters } from "@/hooks"; -import { FilterEntity, FilterType } from "@/types"; +import { ExpandedScanData, FilterEntity, FilterType } from "@/types"; interface ScansFiltersProps { providerUIDs: string[]; providerDetails: { [uid: string]: FilterEntity }[]; + completedScans?: ExpandedScanData[]; } export const ScansFilters = ({ providerUIDs, providerDetails, + completedScans = [], }: ScansFiltersProps) => { const router = useRouter(); const pathname = usePathname(); @@ -36,24 +39,50 @@ export const ScansFilters = ({ router.push(`${pathname}?${params.toString()}`); }; - const scanIdChip = idFilter ? ( -
- - Scan: - {idFilter} + const handleScanChange = (selectedScanId: string) => { + const params = new URLSearchParams(searchParams.toString()); + params.set("filter[id__in]", selectedScanId); + router.push(`${pathname}?${params.toString()}`); + }; + + const scanIdElement = idFilter ? ( + completedScans.length > 0 ? ( +
+ - -
+
+ ) : ( +
+ + + Scan: + + {idFilter} + + +
+ ) ) : null; return ( @@ -68,7 +97,7 @@ export const ScansFilters = ({ index: 1, }, ]} - prependElement={scanIdChip} + prependElement={scanIdElement} /> ); }; diff --git a/ui/components/shadcn/card/card.tsx b/ui/components/shadcn/card/card.tsx index 8226ae698f..7c60a51f5f 100644 --- a/ui/components/shadcn/card/card.tsx +++ b/ui/components/shadcn/card/card.tsx @@ -20,7 +20,7 @@ const cardVariants = cva("flex flex-col gap-6 rounded-xl border", { inner: "rounded-[12px] backdrop-blur-[46px] border-border-neutral-tertiary bg-bg-neutral-tertiary", danger: - "gap-1 rounded-[12px] border-border-error-primary bg-bg-fail-secondary", + "gap-1 rounded-[12px] border-[rgba(67,34,50,0.5)] bg-[rgba(67,34,50,0.2)] dark:border-[rgba(67,34,50,0.7)] dark:bg-[rgba(67,34,50,0.3)]", }, padding: { default: "", diff --git a/ui/components/ui/entities/date-with-time.tsx b/ui/components/ui/entities/date-with-time.tsx index fd43fdbd5a..90a801e9ed 100644 --- a/ui/components/ui/entities/date-with-time.tsx +++ b/ui/components/ui/entities/date-with-time.tsx @@ -1,5 +1,10 @@ import { format, parseISO } from "date-fns"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/shadcn/tooltip"; import { cn } from "@/lib/utils"; interface DateWithTimeProps { @@ -33,25 +38,52 @@ export const DateWithTime = ({ ?.substring(0, 3) .toUpperCase() || ""; - return ( + const fullText = showTime + ? `${formattedDate} ${formattedTime} ${timezone}` + : formattedDate; + + const content = (
- + {formattedDate} {showTime && ( - + {formattedTime} {timezone} )}
); + + if (inline) { + return ( + + +
{content}
+
+ {fullText} +
+ ); + } + + return content; } catch { return -; } diff --git a/ui/hooks/use-infinite-resources.test.ts b/ui/hooks/use-infinite-resources.test.ts index 49ab0f9105..618de56fba 100644 --- a/ui/hooks/use-infinite-resources.test.ts +++ b/ui/hooks/use-infinite-resources.test.ts @@ -163,6 +163,38 @@ describe("useInfiniteResources", () => { findingGroupActionsMock.getLatestFindingGroupResources, ).not.toHaveBeenCalled(); }); + + it("should forward the active finding-group filters to the resources endpoint", async () => { + // Given + const apiResponse = makeApiResponse([], { pages: 1 }); + const filters = { + "filter[status__in]": "PASS", + "filter[severity__in]": "medium", + "filter[provider_type__in]": "aws", + }; + findingGroupActionsMock.getLatestFindingGroupResources.mockResolvedValue( + apiResponse, + ); + findingGroupActionsMock.adaptFindingGroupResourcesResponse.mockReturnValue( + [], + ); + + // When + renderHook(() => useInfiniteResources(defaultOptions({ filters }))); + await flushAsync(); + + // Then + expect( + findingGroupActionsMock.getLatestFindingGroupResources, + ).toHaveBeenCalledWith( + expect.objectContaining({ + checkId: "check_1", + page: 1, + pageSize: 10, + filters, + }), + ); + }); }); describe("when all resources fit in one page", () => { diff --git a/ui/hooks/use-infinite-resources.ts b/ui/hooks/use-infinite-resources.ts index fc720dd9f7..df710ebf10 100644 --- a/ui/hooks/use-infinite-resources.ts +++ b/ui/hooks/use-infinite-resources.ts @@ -32,6 +32,8 @@ interface UseInfiniteResourcesReturn { refresh: () => void; /** Imperatively load the next page (e.g. from drawer navigation). */ loadMore: () => void; + /** Total number of resources matching current filters (from API pagination). */ + totalCount: number | null; } /** @@ -60,6 +62,7 @@ export function useInfiniteResources({ const currentCheckIdRef = useRef(checkId); const controllerRef = useRef(null); const observerRef = useRef(null); + const totalCountRef = useRef(null); // Store latest values in refs so the fetch function always reads current values // without being recreated on every render @@ -70,6 +73,7 @@ export function useInfiniteResources({ const onSetLoadingRef = useRef(onSetLoading); // Keep refs in sync with latest props + currentCheckIdRef.current = checkId; hasDateOrScanRef.current = hasDateOrScanFilter; filtersRef.current = filters; onSetResourcesRef.current = onSetResources; @@ -110,6 +114,7 @@ export function useInfiniteResources({ ); const totalPages = response?.meta?.pagination?.pages ?? 1; const hasMore = page < totalPages; + totalCountRef.current = response?.meta?.pagination?.count ?? null; // Commit the page number only after a successful (non-aborted) fetch. // This prevents a premature pageRef increment from loadNextPage being @@ -209,5 +214,10 @@ export function useInfiniteResources({ fetchPage(1, false, currentCheckIdRef.current, controller.signal); } - return { sentinelRef, refresh, loadMore: loadNextPage }; + return { + sentinelRef, + refresh, + loadMore: loadNextPage, + totalCount: totalCountRef.current, + }; } diff --git a/ui/lib/findings-scan-filters.test.ts b/ui/lib/findings-scan-filters.test.ts new file mode 100644 index 0000000000..fcf32b507d --- /dev/null +++ b/ui/lib/findings-scan-filters.test.ts @@ -0,0 +1,112 @@ +import { describe, expect, it, vi } from "vitest"; + +import { + buildFindingScanDateFilters, + resolveFindingScanDateFilters, +} from "./findings-scan-filters"; + +describe("buildFindingScanDateFilters", () => { + it("uses an exact inserted_at filter when all selected scans belong to the same day", () => { + expect( + buildFindingScanDateFilters([ + "2026-04-07T10:00:00Z", + "2026-04-07T18:30:00Z", + ]), + ).toEqual({ + "filter[inserted_at]": "2026-04-07", + }); + }); + + it("ignores whitespace-only date strings", () => { + expect(buildFindingScanDateFilters([" ", "2026-04-07T10:00:00Z"])).toEqual( + { + "filter[inserted_at]": "2026-04-07", + }, + ); + }); + + it("uses a date range when selected scans span multiple days", () => { + expect( + buildFindingScanDateFilters([ + "2026-04-03T10:00:00Z", + "2026-04-07T18:30:00Z", + "2026-04-05T12:00:00Z", + ]), + ).toEqual({ + "filter[inserted_at__gte]": "2026-04-03", + "filter[inserted_at__lte]": "2026-04-07", + }); + }); +}); + +describe("resolveFindingScanDateFilters", () => { + it("adds the required inserted_at filter for a selected scan when the URL only contains scan__in", async () => { + const result = await resolveFindingScanDateFilters({ + filters: { + "filter[muted]": "false", + "filter[scan__in]": "scan-1", + }, + scans: [ + { + id: "scan-1", + attributes: { + inserted_at: "2026-04-07T10:00:00Z", + }, + }, + ], + loadScan: vi.fn(), + }); + + expect(result).toEqual({ + "filter[muted]": "false", + "filter[scan__in]": "scan-1", + "filter[inserted_at]": "2026-04-07", + }); + }); + + it("fetches missing scan details when the selected scan is not present in the prefetched scans list", async () => { + const loadScan = vi.fn().mockResolvedValue({ + id: "scan-2", + attributes: { + inserted_at: "2026-04-05T08:00:00Z", + }, + }); + + const result = await resolveFindingScanDateFilters({ + filters: { + "filter[scan__in]": "scan-2", + }, + scans: [], + loadScan, + }); + + expect(loadScan).toHaveBeenCalledWith("scan-2"); + expect(result).toEqual({ + "filter[scan__in]": "scan-2", + "filter[inserted_at]": "2026-04-05", + }); + }); + + it("does not override an explicit inserted_at filter already chosen in the frontend", async () => { + const result = await resolveFindingScanDateFilters({ + filters: { + "filter[scan__in]": "scan-1", + "filter[inserted_at__gte]": "2026-04-01", + }, + scans: [ + { + id: "scan-1", + attributes: { + inserted_at: "2026-04-07T10:00:00Z", + }, + }, + ], + loadScan: vi.fn(), + }); + + expect(result).toEqual({ + "filter[scan__in]": "scan-1", + "filter[inserted_at__gte]": "2026-04-01", + }); + }); +}); diff --git a/ui/lib/findings-scan-filters.ts b/ui/lib/findings-scan-filters.ts new file mode 100644 index 0000000000..dfbb1bc44c --- /dev/null +++ b/ui/lib/findings-scan-filters.ts @@ -0,0 +1,99 @@ +interface ScanDateSource { + id: string; + attributes?: { + inserted_at?: string; + }; +} + +interface ResolveFindingScanDateFiltersOptions { + filters: Record; + scans: ScanDateSource[]; + loadScan: (scanId: string) => Promise; +} + +const INSERTED_AT_FILTER_KEYS = [ + "filter[inserted_at]", + "filter[inserted_at__date]", + "filter[inserted_at__gte]", + "filter[inserted_at__lte]", +] as const; + +function getScanFilterIds(filters: Record): string[] { + const scanIds = filters["filter[scan__in]"] || filters["filter[scan]"] || ""; + return Array.from(new Set(scanIds.split(",").filter(Boolean))); +} + +function formatScanDate(dateTime?: string): string | null { + if (!dateTime) return null; + const [date] = dateTime.split("T"); + return date?.trim() || null; +} + +function hasInsertedAtFilter(filters: Record): boolean { + return INSERTED_AT_FILTER_KEYS.some((key) => Boolean(filters[key])); +} + +export function buildFindingScanDateFilters( + scanInsertedAtValues: string[], +): Record { + const dates = Array.from( + new Set(scanInsertedAtValues.map(formatScanDate).filter(Boolean)), + ).sort() as string[]; + + if (dates.length === 0) { + return {}; + } + + if (dates.length === 1) { + return { + "filter[inserted_at]": dates[0], + }; + } + + return { + "filter[inserted_at__gte]": dates[0], + "filter[inserted_at__lte]": dates[dates.length - 1], + }; +} + +export async function resolveFindingScanDateFilters({ + filters, + scans, + loadScan, +}: ResolveFindingScanDateFiltersOptions): Promise> { + const scanIds = getScanFilterIds(filters); + + if (scanIds.length === 0 || hasInsertedAtFilter(filters)) { + return filters; + } + + const scansById = new Map(scans.map((scan) => [scan.id, scan])); + const missingScanIds = scanIds.filter((scanId) => !scansById.has(scanId)); + + if (missingScanIds.length > 0) { + const missingScans = await Promise.all( + missingScanIds.map((scanId) => loadScan(scanId)), + ); + + missingScans.forEach((scan) => { + if (scan) { + scansById.set(scan.id, scan); + } + }); + } + + const scanInsertedAtValues = scanIds + .map((scanId) => scansById.get(scanId)?.attributes?.inserted_at) + .filter((insertedAt): insertedAt is string => Boolean(insertedAt)); + + const dateFilters = buildFindingScanDateFilters(scanInsertedAtValues); + + if (Object.keys(dateFilters).length === 0) { + return filters; + } + + return { + ...filters, + ...dateFilters, + }; +} diff --git a/ui/types/findings-table.ts b/ui/types/findings-table.ts index 30198404f5..f8837884f0 100644 --- a/ui/types/findings-table.ts +++ b/ui/types/findings-table.ts @@ -34,12 +34,14 @@ export interface FindingResourceRow { providerAlias: string; providerUid: string; resourceName: string; + resourceType: string; resourceGroup: string; resourceUid: string; service: string; region: string; severity: Severity; status: string; + delta?: string | null; isMuted: boolean; mutedReason?: string; firstSeenAt: string | null; From baf1194824b9f3bde6bf4594d1556d798d990f39 Mon Sep 17 00:00:00 2001 From: Davidm4r Date: Thu, 9 Apr 2026 10:11:52 +0200 Subject: [PATCH 03/65] feat(ui): invitation flow smart routing (#10589) Co-authored-by: Pablo Fernandez Guerra (PFE) <148432447+pfe-nazaries@users.noreply.github.com> Co-authored-by: Pablo F.G Co-authored-by: Claude Opus 4.6 (1M context) --- ui/CHANGELOG.md | 4 + ui/actions/invitations/invitation.ts | 35 +++ ui/app/(auth)/(guest-only)/layout.tsx | 18 ++ .../{ => (guest-only)}/sign-in/page.tsx | 0 .../{ => (guest-only)}/sign-up/page.tsx | 0 .../accept/accept-invitation-client.tsx | 219 ++++++++++++++++++ ui/app/(auth)/invitation/accept/page.tsx | 22 ++ ui/app/(auth)/layout.tsx | 20 +- ui/auth.config.ts | 11 +- .../invitations/invitation-details.tsx | 2 +- ui/lib/invitation-routing.ts | 10 + ui/proxy.ts | 22 +- ui/tests/auth/auth-middleware.spec.ts | 4 +- ui/tests/auth/auth-session-errors.spec.ts | 15 ++ 14 files changed, 360 insertions(+), 22 deletions(-) create mode 100644 ui/app/(auth)/(guest-only)/layout.tsx rename ui/app/(auth)/{ => (guest-only)}/sign-in/page.tsx (100%) rename ui/app/(auth)/{ => (guest-only)}/sign-up/page.tsx (100%) create mode 100644 ui/app/(auth)/invitation/accept/accept-invitation-client.tsx create mode 100644 ui/app/(auth)/invitation/accept/page.tsx create mode 100644 ui/lib/invitation-routing.ts diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 941e7c5ff4..c115911475 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -6,6 +6,9 @@ All notable changes to the **Prowler UI** are documented in this file. ### 🚀 Added +- Invitation accept smart router for handling invitation flow routing [(#10573)](https://github.com/prowler-cloud/prowler/pull/10573) +- Invitation link backward compatibility [(#10583)](https://github.com/prowler-cloud/prowler/pull/10583) +- Updated invitation link to use smart router [(#10575)](https://github.com/prowler-cloud/prowler/pull/10575) - Multi-tenant organization management: create, switch, edit, and delete organizations from the profile page [(#10491)](https://github.com/prowler-cloud/prowler/pull/10491) - Findings grouped view with drill-down table showing resources per check, resource detail drawer, infinite scroll pagination, and bulk mute support [(#10425)](https://github.com/prowler-cloud/prowler/pull/10425) - Resource events tool to Lighthouse AI [(#10412)](https://github.com/prowler-cloud/prowler/pull/10412) @@ -18,6 +21,7 @@ All notable changes to the **Prowler UI** are documented in this file. ### 🐞 Fixed +- Preserve query parameters in callbackUrl during invitation flow [(#10571)](https://github.com/prowler-cloud/prowler/pull/10571) - Deleting the active organization now switches to the target org before deleting, preventing JWT rejection from the backend [(#10491)](https://github.com/prowler-cloud/prowler/pull/10491) - Clear Filters now resets all filters including muted findings and auto-applies, Clear all in pills only removes pill-visible sub-filters, and the discard icon is now an Undo text button [(#10446)](https://github.com/prowler-cloud/prowler/pull/10446) - Send to Jira modal now dynamically fetches and displays available issue types per project instead of hardcoding `"Task"`, fixing failures on non-English Jira instances [(#10534)](https://github.com/prowler-cloud/prowler/pull/10534) diff --git a/ui/actions/invitations/invitation.ts b/ui/actions/invitations/invitation.ts index 8ad037cbbe..72f591705a 100644 --- a/ui/actions/invitations/invitation.ts +++ b/ui/actions/invitations/invitation.ts @@ -2,10 +2,13 @@ import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; +import { z } from "zod"; import { apiBaseUrl, getAuthHeaders } from "@/lib"; import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper"; +const invitationTokenSchema = z.string().min(1).max(500); + export const getInvitations = async ({ page = 1, query = "", @@ -195,3 +198,35 @@ export const revokeInvite = async (formData: FormData) => { handleApiError(error); } }; + +export const acceptInvitation = async (token: string) => { + const parsed = invitationTokenSchema.safeParse(token); + if (!parsed.success) { + return { error: "Invalid invitation token" }; + } + + const headers = await getAuthHeaders({ contentType: true }); + + const url = new URL(`${apiBaseUrl}/invitations/accept`); + + const body = JSON.stringify({ + data: { + type: "invitations", + attributes: { + invitation_token: parsed.data, + }, + }, + }); + + try { + const response = await fetch(url.toString(), { + method: "POST", + headers, + body, + }); + + return handleApiResponse(response); + } catch (error) { + return handleApiError(error); + } +}; diff --git a/ui/app/(auth)/(guest-only)/layout.tsx b/ui/app/(auth)/(guest-only)/layout.tsx new file mode 100644 index 0000000000..3ff93d836b --- /dev/null +++ b/ui/app/(auth)/(guest-only)/layout.tsx @@ -0,0 +1,18 @@ +import { redirect } from "next/navigation"; +import { ReactNode } from "react"; + +import { auth } from "@/auth.config"; + +export default async function GuestOnlyLayout({ + children, +}: { + children: ReactNode; +}) { + const session = await auth(); + + if (session?.user) { + redirect("/"); + } + + return <>{children}; +} diff --git a/ui/app/(auth)/sign-in/page.tsx b/ui/app/(auth)/(guest-only)/sign-in/page.tsx similarity index 100% rename from ui/app/(auth)/sign-in/page.tsx rename to ui/app/(auth)/(guest-only)/sign-in/page.tsx diff --git a/ui/app/(auth)/sign-up/page.tsx b/ui/app/(auth)/(guest-only)/sign-up/page.tsx similarity index 100% rename from ui/app/(auth)/sign-up/page.tsx rename to ui/app/(auth)/(guest-only)/sign-up/page.tsx diff --git a/ui/app/(auth)/invitation/accept/accept-invitation-client.tsx b/ui/app/(auth)/invitation/accept/accept-invitation-client.tsx new file mode 100644 index 0000000000..73e6dbe8fd --- /dev/null +++ b/ui/app/(auth)/invitation/accept/accept-invitation-client.tsx @@ -0,0 +1,219 @@ +"use client"; + +import { Icon } from "@iconify/react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { signOut } from "next-auth/react"; +import { useEffect, useRef, useState } from "react"; + +import { acceptInvitation } from "@/actions/invitations"; +import { Button } from "@/components/shadcn"; +import { + INVITATION_ACTION_PARAM, + INVITATION_SIGNUP_ACTION, +} from "@/lib/invitation-routing"; + +type AcceptState = + | { kind: "no-token" } + | { kind: "accepting" } + | { kind: "error"; message: string; canRetry: boolean; needsSignOut: boolean } + | { kind: "choose" }; + +function mapApiError(status: number | undefined): { + message: string; + canRetry: boolean; + needsSignOut: boolean; +} { + switch (status) { + case 410: + return { + message: + "This invitation has expired. Please contact your administrator for a new one.", + canRetry: false, + needsSignOut: false, + }; + case 400: + return { + message: "This invitation has already been used.", + canRetry: false, + needsSignOut: false, + }; + case 404: + return { + message: + "This invitation was sent to a different email address. Please sign in with the correct account.", + canRetry: false, + needsSignOut: true, + }; + default: + return { + message: "Something went wrong while accepting the invitation.", + canRetry: true, + needsSignOut: false, + }; + } +} + +export function AcceptInvitationClient({ + isAuthenticated, + token, +}: { + isAuthenticated: boolean; + token: string | null; +}) { + const router = useRouter(); + const [state, setState] = useState(() => { + if (!token) return { kind: "no-token" }; + if (!isAuthenticated) return { kind: "choose" }; + return { kind: "accepting" }; + }); + const hasStartedRef = useRef(false); + + async function doAccept() { + if (!token) return; + setState({ kind: "accepting" }); + + const result = await acceptInvitation(token); + + if (result?.error) { + const { message, canRetry, needsSignOut } = mapApiError(result.status); + setState({ kind: "error", message, canRetry, needsSignOut }); + } else { + router.push("/"); + } + } + + async function handleSignOutAndRedirect() { + if (!token) return; + const callbackPath = `/invitation/accept?invitation_token=${encodeURIComponent(token)}`; + await signOut({ redirect: false }); + router.push(`/sign-in?callbackUrl=${encodeURIComponent(callbackPath)}`); + } + + useEffect(() => { + if (hasStartedRef.current) return; + hasStartedRef.current = true; + + if (!token) { + setState({ kind: "no-token" }); + return; + } + + if (isAuthenticated) { + doAccept(); + } else { + setState({ kind: "choose" }); + } + }, [token, isAuthenticated]); // eslint-disable-line react-hooks/exhaustive-deps + + return ( +
+
+ {/* No token */} + {state.kind === "no-token" && ( +
+ +

Invalid Invitation Link

+

+ No invitation token was provided. Please check the link you + received. +

+ +
+ )} + + {/* Accepting */} + {state.kind === "accepting" && ( +
+ +

Accepting Invitation...

+

+ Please wait while we process your invitation. +

+
+ )} + + {/* Error */} + {state.kind === "error" && ( +
+ +

+ Could Not Accept Invitation +

+

{state.message}

+
+ {state.canRetry && } + {state.needsSignOut ? ( + + ) : ( + + )} +
+
+ )} + + {/* Choice page for unauthenticated users */} + {state.kind === "choose" && ( +
+ +
+

+ You've Been Invited +

+

+ You've been invited to join a tenant on Prowler. How would + you like to continue? +

+
+
+ + +
+
+ )} +
+
+ ); +} diff --git a/ui/app/(auth)/invitation/accept/page.tsx b/ui/app/(auth)/invitation/accept/page.tsx new file mode 100644 index 0000000000..9bfc5e0110 --- /dev/null +++ b/ui/app/(auth)/invitation/accept/page.tsx @@ -0,0 +1,22 @@ +import { auth } from "@/auth.config"; +import { SearchParamsProps } from "@/types"; + +import { AcceptInvitationClient } from "./accept-invitation-client"; + +export default async function AcceptInvitationPage({ + searchParams, +}: { + searchParams: Promise; +}) { + const session = await auth(); + const resolvedSearchParams = await searchParams; + + const token = + typeof resolvedSearchParams?.invitation_token === "string" + ? resolvedSearchParams.invitation_token + : null; + + return ( + + ); +} diff --git a/ui/app/(auth)/layout.tsx b/ui/app/(auth)/layout.tsx index ab6b1e9bfa..07fe3a60c3 100644 --- a/ui/app/(auth)/layout.tsx +++ b/ui/app/(auth)/layout.tsx @@ -2,10 +2,8 @@ import "@/styles/globals.css"; import { GoogleTagManager } from "@next/third-parties/google"; import { Metadata, Viewport } from "next"; -import { redirect } from "next/navigation"; -import { ReactNode } from "react"; +import { ReactNode, Suspense } from "react"; -import { auth } from "@/auth.config"; import { NavigationProgress, Toaster } from "@/components/ui"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; @@ -31,17 +29,7 @@ export const viewport: Viewport = { ], }; -export default async function RootLayout({ - children, -}: { - children: ReactNode; -}) { - const session = await auth(); - - if (session?.user) { - redirect("/"); - } - +export default function AuthLayout({ children }: { children: ReactNode }) { return ( @@ -53,7 +41,9 @@ export default async function RootLayout({ )} > - + + + {children} { ? window.location.origin : "http://localhost:3000"; - const invitationLink = `${baseUrl}/sign-up?invitation_token=${attributes.token}`; + const invitationLink = `${baseUrl}/invitation/accept?invitation_token=${attributes.token}`; return (
diff --git a/ui/lib/invitation-routing.ts b/ui/lib/invitation-routing.ts new file mode 100644 index 0000000000..85f132d922 --- /dev/null +++ b/ui/lib/invitation-routing.ts @@ -0,0 +1,10 @@ +/** + * Query param name + value used to bypass the backward-compat redirect + * in proxy.ts when the user explicitly chose "Create an account" + * from the invitation smart router. + * + * Client sends: /sign-up?invitation_token=…&action=signup + * Proxy skips redirect when "action" param is present. + */ +export const INVITATION_ACTION_PARAM = "action"; +export const INVITATION_SIGNUP_ACTION = "signup"; diff --git a/ui/proxy.ts b/ui/proxy.ts index 98b0725dea..553de6e9ba 100644 --- a/ui/proxy.ts +++ b/ui/proxy.ts @@ -1,10 +1,12 @@ import { NextRequest, NextResponse } from "next/server"; import { auth } from "@/auth.config"; +import { INVITATION_ACTION_PARAM } from "@/lib/invitation-routing"; const publicRoutes = [ "/sign-in", "/sign-up", + "/invitation/accept", // In Cloud uncomment the following lines: // "/reset-password", // "/email-verification", @@ -18,6 +20,22 @@ const isPublicRoute = (pathname: string): boolean => { // NextAuth's auth() wrapper - renamed from middleware to proxy export default auth((req: NextRequest & { auth: any }) => { const { pathname } = req.nextUrl; + + // Backward compatibility: redirect old invitation links to new smart router + // Skip redirect when the user explicitly chose "Create an account" from the smart router + if ( + pathname === "/sign-up" && + req.nextUrl.searchParams.has("invitation_token") && + !req.nextUrl.searchParams.has(INVITATION_ACTION_PARAM) + ) { + const acceptUrl = new URL("/invitation/accept", req.url); + acceptUrl.searchParams.set( + "invitation_token", + req.nextUrl.searchParams.get("invitation_token")!, + ); + return NextResponse.redirect(acceptUrl); + } + const user = req.auth?.user; const sessionError = req.auth?.error; @@ -25,13 +43,13 @@ export default auth((req: NextRequest & { auth: any }) => { if (sessionError && !isPublicRoute(pathname)) { const signInUrl = new URL("/sign-in", req.url); signInUrl.searchParams.set("error", sessionError); - signInUrl.searchParams.set("callbackUrl", pathname); + signInUrl.searchParams.set("callbackUrl", pathname + req.nextUrl.search); return NextResponse.redirect(signInUrl); } if (!user && !isPublicRoute(pathname)) { const signInUrl = new URL("/sign-in", req.url); - signInUrl.searchParams.set("callbackUrl", pathname); + signInUrl.searchParams.set("callbackUrl", pathname + req.nextUrl.search); return NextResponse.redirect(signInUrl); } diff --git a/ui/tests/auth/auth-middleware.spec.ts b/ui/tests/auth/auth-middleware.spec.ts index 959f08250d..d7f1d23619 100644 --- a/ui/tests/auth/auth-middleware.spec.ts +++ b/ui/tests/auth/auth-middleware.spec.ts @@ -65,7 +65,9 @@ test.describe("Middleware Error Handling", () => { await freshPage.goto(`/scans?e2e_mw=${cacheBuster}`, { waitUntil: "commit", }); - await freshSignInPage.verifyRedirectWithCallback("/scans"); + await freshSignInPage.verifyRedirectWithCallback( + `/scans?e2e_mw=${cacheBuster}`, + ); } finally { await invalidSessionContext.close(); } diff --git a/ui/tests/auth/auth-session-errors.spec.ts b/ui/tests/auth/auth-session-errors.spec.ts index 000ade831d..ac640d53a3 100644 --- a/ui/tests/auth/auth-session-errors.spec.ts +++ b/ui/tests/auth/auth-session-errors.spec.ts @@ -69,4 +69,19 @@ test.describe("Session Error Messages", () => { await signInPage.verifyRedirectWithCallback("/providers"); }, ); + + test( + "should preserve query parameters in callbackUrl", + { tag: ["@e2e", "@auth", "@session", "@AUTH-SESSION-E2E-005"] }, + async ({ page, context }) => { + const signInPage = new SignInPage(page); + await context.clearCookies(); + + // Navigate to a protected route with query params and assert they are preserved. + await page.goto("/providers?ref=test", { + waitUntil: "commit", + }); + await signInPage.verifyRedirectWithCallback("/providers?ref=test"); + }, + ); }); From 1bfed74db58a061d0b87cc9c242636e841a61c8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:14:27 +0200 Subject: [PATCH 04/65] chore(deps): bump docker/build-push-action from 6.19.2 to 7.0.0 (#10557) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/api-container-build-push.yml | 2 +- .github/workflows/api-container-checks.yml | 2 +- .github/workflows/mcp-container-build-push.yml | 2 +- .github/workflows/mcp-container-checks.yml | 2 +- .github/workflows/sdk-container-build-push.yml | 2 +- .github/workflows/sdk-container-checks.yml | 2 +- .github/workflows/ui-container-build-push.yml | 2 +- .github/workflows/ui-container-checks.yml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/api-container-build-push.yml b/.github/workflows/api-container-build-push.yml index 0cd3b82071..178509efbb 100644 --- a/.github/workflows/api-container-build-push.yml +++ b/.github/workflows/api-container-build-push.yml @@ -148,7 +148,7 @@ jobs: - name: Build and push API container for ${{ matrix.arch }} id: container-push if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ${{ env.WORKING_DIRECTORY }} push: true diff --git a/.github/workflows/api-container-checks.yml b/.github/workflows/api-container-checks.yml index 14c163e89a..d6334fe1e0 100644 --- a/.github/workflows/api-container-checks.yml +++ b/.github/workflows/api-container-checks.yml @@ -119,7 +119,7 @@ jobs: - name: Build container for ${{ matrix.arch }} if: steps.check-changes.outputs.any_changed == 'true' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ${{ env.API_WORKING_DIR }} push: false diff --git a/.github/workflows/mcp-container-build-push.yml b/.github/workflows/mcp-container-build-push.yml index 3f59a455db..806b826a91 100644 --- a/.github/workflows/mcp-container-build-push.yml +++ b/.github/workflows/mcp-container-build-push.yml @@ -134,7 +134,7 @@ jobs: - name: Build and push MCP container for ${{ matrix.arch }} id: container-push if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ${{ env.WORKING_DIRECTORY }} push: true diff --git a/.github/workflows/mcp-container-checks.yml b/.github/workflows/mcp-container-checks.yml index f8fbfca12f..10020ef042 100644 --- a/.github/workflows/mcp-container-checks.yml +++ b/.github/workflows/mcp-container-checks.yml @@ -109,7 +109,7 @@ jobs: - name: Build MCP container for ${{ matrix.arch }} if: steps.check-changes.outputs.any_changed == 'true' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ${{ env.MCP_WORKING_DIR }} push: false diff --git a/.github/workflows/sdk-container-build-push.yml b/.github/workflows/sdk-container-build-push.yml index 4a0563b89f..fe586a4e2b 100644 --- a/.github/workflows/sdk-container-build-push.yml +++ b/.github/workflows/sdk-container-build-push.yml @@ -217,7 +217,7 @@ jobs: - name: Build and push SDK container for ${{ matrix.arch }} id: container-push if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: . file: ${{ env.DOCKERFILE_PATH }} diff --git a/.github/workflows/sdk-container-checks.yml b/.github/workflows/sdk-container-checks.yml index dacf160a86..791dcc41a1 100644 --- a/.github/workflows/sdk-container-checks.yml +++ b/.github/workflows/sdk-container-checks.yml @@ -131,7 +131,7 @@ jobs: - name: Build SDK container for ${{ matrix.arch }} if: steps.check-changes.outputs.any_changed == 'true' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: . push: false diff --git a/.github/workflows/ui-container-build-push.yml b/.github/workflows/ui-container-build-push.yml index c8886a5ae9..a5328c525c 100644 --- a/.github/workflows/ui-container-build-push.yml +++ b/.github/workflows/ui-container-build-push.yml @@ -138,7 +138,7 @@ jobs: - name: Build and push UI container for ${{ matrix.arch }} id: container-push if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ${{ env.WORKING_DIRECTORY }} build-args: | diff --git a/.github/workflows/ui-container-checks.yml b/.github/workflows/ui-container-checks.yml index 10a910ace4..53fa600659 100644 --- a/.github/workflows/ui-container-checks.yml +++ b/.github/workflows/ui-container-checks.yml @@ -112,7 +112,7 @@ jobs: - name: Build UI container for ${{ matrix.arch }} if: steps.check-changes.outputs.any_changed == 'true' - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ${{ env.UI_WORKING_DIR }} target: prod From def59a8cc23905ab463cfd2cafe0cc831ce971d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:16:00 +0200 Subject: [PATCH 05/65] chore(deps): bump docker/setup-buildx-action from 3.12.0 to 4.0.0 (#10556) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/api-container-build-push.yml | 2 +- .github/workflows/api-container-checks.yml | 2 +- .github/workflows/mcp-container-build-push.yml | 2 +- .github/workflows/mcp-container-checks.yml | 2 +- .github/workflows/sdk-container-build-push.yml | 2 +- .github/workflows/sdk-container-checks.yml | 2 +- .github/workflows/ui-container-build-push.yml | 2 +- .github/workflows/ui-container-checks.yml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/api-container-build-push.yml b/.github/workflows/api-container-build-push.yml index 178509efbb..3f5bd96cc2 100644 --- a/.github/workflows/api-container-build-push.yml +++ b/.github/workflows/api-container-build-push.yml @@ -143,7 +143,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build and push API container for ${{ matrix.arch }} id: container-push diff --git a/.github/workflows/api-container-checks.yml b/.github/workflows/api-container-checks.yml index d6334fe1e0..bf7a2e8900 100644 --- a/.github/workflows/api-container-checks.yml +++ b/.github/workflows/api-container-checks.yml @@ -115,7 +115,7 @@ jobs: - name: Set up Docker Buildx if: steps.check-changes.outputs.any_changed == 'true' - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build container for ${{ matrix.arch }} if: steps.check-changes.outputs.any_changed == 'true' diff --git a/.github/workflows/mcp-container-build-push.yml b/.github/workflows/mcp-container-build-push.yml index 806b826a91..969cb8c04d 100644 --- a/.github/workflows/mcp-container-build-push.yml +++ b/.github/workflows/mcp-container-build-push.yml @@ -129,7 +129,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build and push MCP container for ${{ matrix.arch }} id: container-push diff --git a/.github/workflows/mcp-container-checks.yml b/.github/workflows/mcp-container-checks.yml index 10020ef042..d88af01ef8 100644 --- a/.github/workflows/mcp-container-checks.yml +++ b/.github/workflows/mcp-container-checks.yml @@ -105,7 +105,7 @@ jobs: - name: Set up Docker Buildx if: steps.check-changes.outputs.any_changed == 'true' - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build MCP container for ${{ matrix.arch }} if: steps.check-changes.outputs.any_changed == 'true' diff --git a/.github/workflows/sdk-container-build-push.yml b/.github/workflows/sdk-container-build-push.yml index fe586a4e2b..f4b8629051 100644 --- a/.github/workflows/sdk-container-build-push.yml +++ b/.github/workflows/sdk-container-build-push.yml @@ -212,7 +212,7 @@ jobs: AWS_REGION: ${{ env.AWS_REGION }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build and push SDK container for ${{ matrix.arch }} id: container-push diff --git a/.github/workflows/sdk-container-checks.yml b/.github/workflows/sdk-container-checks.yml index 791dcc41a1..3d78d919c0 100644 --- a/.github/workflows/sdk-container-checks.yml +++ b/.github/workflows/sdk-container-checks.yml @@ -127,7 +127,7 @@ jobs: - name: Set up Docker Buildx if: steps.check-changes.outputs.any_changed == 'true' - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build SDK container for ${{ matrix.arch }} if: steps.check-changes.outputs.any_changed == 'true' diff --git a/.github/workflows/ui-container-build-push.yml b/.github/workflows/ui-container-build-push.yml index a5328c525c..172b3944d5 100644 --- a/.github/workflows/ui-container-build-push.yml +++ b/.github/workflows/ui-container-build-push.yml @@ -133,7 +133,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build and push UI container for ${{ matrix.arch }} id: container-push diff --git a/.github/workflows/ui-container-checks.yml b/.github/workflows/ui-container-checks.yml index 53fa600659..ddefab0370 100644 --- a/.github/workflows/ui-container-checks.yml +++ b/.github/workflows/ui-container-checks.yml @@ -108,7 +108,7 @@ jobs: - name: Set up Docker Buildx if: steps.check-changes.outputs.any_changed == 'true' - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build UI container for ${{ matrix.arch }} if: steps.check-changes.outputs.any_changed == 'true' From eda90c4673d2b36ae7ac6a00b9c0d19fc02646de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:18:16 +0200 Subject: [PATCH 06/65] chore(deps): bump actions/upload-artifact from 6.0.0 to 7.0.0 (#10555) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/issue-triage.lock.yml | 10 +++++----- .github/workflows/ui-e2e-tests-v2.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index fcc6f1e363..34f059e023 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -772,7 +772,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload Safe Outputs if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: safe-output path: ${{ env.GH_AW_SAFE_OUTPUTS }} @@ -793,13 +793,13 @@ jobs: await main(); - name: Upload sanitized agent output if: always() && env.GH_AW_AGENT_OUTPUT - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: agent-output path: ${{ env.GH_AW_AGENT_OUTPUT }} if-no-files-found: warn - name: Upload engine output files - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: agent_outputs path: | @@ -839,7 +839,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: agent-artifacts path: | @@ -1071,7 +1071,7 @@ jobs: await main(); - name: Upload threat detection log if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: threat-detection.log path: /tmp/gh-aw/threat-detection/detection.log diff --git a/.github/workflows/ui-e2e-tests-v2.yml b/.github/workflows/ui-e2e-tests-v2.yml index 53533474fa..c739099b08 100644 --- a/.github/workflows/ui-e2e-tests-v2.yml +++ b/.github/workflows/ui-e2e-tests-v2.yml @@ -259,7 +259,7 @@ jobs: fi - name: Upload test reports - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 if: failure() with: name: playwright-report From f3b142c0cf6e561ea6b980ea980104a65784750e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:19:00 +0200 Subject: [PATCH 07/65] chore(deps): bump docker/login-action from 3.7.0 to 4.0.0 (#10554) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/api-container-build-push.yml | 4 ++-- .github/workflows/mcp-container-build-push.yml | 4 ++-- .github/workflows/sdk-container-build-push.yml | 12 ++++++------ .github/workflows/ui-container-build-push.yml | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/api-container-build-push.yml b/.github/workflows/api-container-build-push.yml index 3f5bd96cc2..b045bb0322 100644 --- a/.github/workflows/api-container-build-push.yml +++ b/.github/workflows/api-container-build-push.yml @@ -137,7 +137,7 @@ jobs: sed -i "s|prowler-cloud/prowler.git@master|prowler-cloud/prowler.git@${LATEST_SHA}|" api/pyproject.toml - name: Login to DockerHub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -178,7 +178,7 @@ jobs: auth.docker.io:443 production.cloudflare.docker.com:443 - name: Login to DockerHub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/mcp-container-build-push.yml b/.github/workflows/mcp-container-build-push.yml index 969cb8c04d..27d59d7bc1 100644 --- a/.github/workflows/mcp-container-build-push.yml +++ b/.github/workflows/mcp-container-build-push.yml @@ -123,7 +123,7 @@ jobs: persist-credentials: false - name: Login to DockerHub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -173,7 +173,7 @@ jobs: release-assets.githubusercontent.com:443 - name: Login to DockerHub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/sdk-container-build-push.yml b/.github/workflows/sdk-container-build-push.yml index f4b8629051..ebe595bbbc 100644 --- a/.github/workflows/sdk-container-build-push.yml +++ b/.github/workflows/sdk-container-build-push.yml @@ -197,13 +197,13 @@ jobs: persist-credentials: false - name: Login to DockerHub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to Public ECR - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: public.ecr.aws username: ${{ secrets.PUBLIC_ECR_AWS_ACCESS_KEY_ID }} @@ -252,13 +252,13 @@ jobs: - name: Login to DockerHub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to Public ECR - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: public.ecr.aws username: ${{ secrets.PUBLIC_ECR_AWS_ACCESS_KEY_ID }} @@ -295,7 +295,7 @@ jobs: # Push to toniblyx/prowler only for current version (latest/stable/release tags) - name: Login to DockerHub (toniblyx) if: needs.setup.outputs.latest_tag == 'latest' - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.TONIBLYX_DOCKERHUB_USERNAME }} password: ${{ secrets.TONIBLYX_DOCKERHUB_PASSWORD }} @@ -320,7 +320,7 @@ jobs: # Re-login as prowlercloud for cleanup of intermediate tags - name: Login to DockerHub (prowlercloud) if: always() - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/ui-container-build-push.yml b/.github/workflows/ui-container-build-push.yml index 172b3944d5..4b73540b9b 100644 --- a/.github/workflows/ui-container-build-push.yml +++ b/.github/workflows/ui-container-build-push.yml @@ -127,7 +127,7 @@ jobs: persist-credentials: false - name: Login to DockerHub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -172,7 +172,7 @@ jobs: production.cloudflare.docker.com:443 - name: Login to DockerHub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} From c21cf0ac20afa594872e3bf8ce36de94314a445f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:19:28 +0200 Subject: [PATCH 08/65] chore(deps): bump tj-actions/changed-files from 47.0.4 to 47.0.5 (#10552) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/api-code-quality.yml | 2 +- .github/workflows/api-container-checks.yml | 4 +-- .github/workflows/api-security.yml | 2 +- .github/workflows/api-tests.yml | 2 +- .github/workflows/mcp-container-checks.yml | 4 +-- .github/workflows/pr-check-changelog.yml | 2 +- .../workflows/pr-check-compliance-mapping.yml | 2 +- .github/workflows/pr-conflict-checker.yml | 2 +- .github/workflows/sdk-code-quality.yml | 2 +- .github/workflows/sdk-container-checks.yml | 4 +-- .github/workflows/sdk-security.yml | 2 +- .github/workflows/sdk-tests.yml | 32 +++++++++---------- .github/workflows/test-impact-analysis.yml | 2 +- .github/workflows/ui-container-checks.yml | 4 +-- .github/workflows/ui-tests.yml | 6 ++-- 15 files changed, 36 insertions(+), 36 deletions(-) diff --git a/.github/workflows/api-code-quality.yml b/.github/workflows/api-code-quality.yml index 68928833a2..4e15f54dbf 100644 --- a/.github/workflows/api-code-quality.yml +++ b/.github/workflows/api-code-quality.yml @@ -50,7 +50,7 @@ jobs: - name: Check for API changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | api/** diff --git a/.github/workflows/api-container-checks.yml b/.github/workflows/api-container-checks.yml index bf7a2e8900..5192d1bd07 100644 --- a/.github/workflows/api-container-checks.yml +++ b/.github/workflows/api-container-checks.yml @@ -42,7 +42,7 @@ jobs: - name: Check if Dockerfile changed id: dockerfile-changed - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: api/Dockerfile @@ -104,7 +104,7 @@ jobs: - name: Check for API changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: api/** files_ignore: | diff --git a/.github/workflows/api-security.yml b/.github/workflows/api-security.yml index 9cd6b14623..505c24f57a 100644 --- a/.github/workflows/api-security.yml +++ b/.github/workflows/api-security.yml @@ -53,7 +53,7 @@ jobs: - name: Check for API changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | api/** diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index ce00b03108..21c4f03965 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -99,7 +99,7 @@ jobs: - name: Check for API changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | api/** diff --git a/.github/workflows/mcp-container-checks.yml b/.github/workflows/mcp-container-checks.yml index d88af01ef8..665153f1f1 100644 --- a/.github/workflows/mcp-container-checks.yml +++ b/.github/workflows/mcp-container-checks.yml @@ -42,7 +42,7 @@ jobs: - name: Check if Dockerfile changed id: dockerfile-changed - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: mcp_server/Dockerfile @@ -96,7 +96,7 @@ jobs: - name: Check for MCP changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: mcp_server/** files_ignore: | diff --git a/.github/workflows/pr-check-changelog.yml b/.github/workflows/pr-check-changelog.yml index c729875843..c021e079cc 100644 --- a/.github/workflows/pr-check-changelog.yml +++ b/.github/workflows/pr-check-changelog.yml @@ -45,7 +45,7 @@ jobs: - name: Get changed files id: changed-files - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | api/** diff --git a/.github/workflows/pr-check-compliance-mapping.yml b/.github/workflows/pr-check-compliance-mapping.yml index dcf61602ba..939e17d0b4 100644 --- a/.github/workflows/pr-check-compliance-mapping.yml +++ b/.github/workflows/pr-check-compliance-mapping.yml @@ -43,7 +43,7 @@ jobs: - name: Get changed files id: changed-files - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | prowler/providers/**/services/**/*.metadata.json diff --git a/.github/workflows/pr-conflict-checker.yml b/.github/workflows/pr-conflict-checker.yml index 330a038fa0..e53a34ea23 100644 --- a/.github/workflows/pr-conflict-checker.yml +++ b/.github/workflows/pr-conflict-checker.yml @@ -39,7 +39,7 @@ jobs: - name: Get changed files id: changed-files - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: '**' diff --git a/.github/workflows/sdk-code-quality.yml b/.github/workflows/sdk-code-quality.yml index b854e9ddeb..08f8001120 100644 --- a/.github/workflows/sdk-code-quality.yml +++ b/.github/workflows/sdk-code-quality.yml @@ -46,7 +46,7 @@ jobs: - name: Check for SDK changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: ./** files_ignore: | diff --git a/.github/workflows/sdk-container-checks.yml b/.github/workflows/sdk-container-checks.yml index 3d78d919c0..19d11647c4 100644 --- a/.github/workflows/sdk-container-checks.yml +++ b/.github/workflows/sdk-container-checks.yml @@ -41,7 +41,7 @@ jobs: - name: Check if Dockerfile changed id: dockerfile-changed - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: Dockerfile @@ -102,7 +102,7 @@ jobs: - name: Check for SDK changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: ./** files_ignore: | diff --git a/.github/workflows/sdk-security.yml b/.github/workflows/sdk-security.yml index 2229568b3f..f4924ab030 100644 --- a/.github/workflows/sdk-security.yml +++ b/.github/workflows/sdk-security.yml @@ -44,7 +44,7 @@ jobs: - name: Check for SDK changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: ./** diff --git a/.github/workflows/sdk-tests.yml b/.github/workflows/sdk-tests.yml index b649db4f5a..7b9fc9d6ef 100644 --- a/.github/workflows/sdk-tests.yml +++ b/.github/workflows/sdk-tests.yml @@ -67,7 +67,7 @@ jobs: - name: Check for SDK changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: ./** files_ignore: | @@ -109,7 +109,7 @@ jobs: - name: Check if AWS files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-aws - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/aws/** @@ -239,7 +239,7 @@ jobs: - name: Check if Azure files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-azure - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/azure/** @@ -263,7 +263,7 @@ jobs: - name: Check if GCP files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-gcp - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/gcp/** @@ -287,7 +287,7 @@ jobs: - name: Check if Kubernetes files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-kubernetes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/kubernetes/** @@ -311,7 +311,7 @@ jobs: - name: Check if GitHub files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-github - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/github/** @@ -335,7 +335,7 @@ jobs: - name: Check if NHN files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-nhn - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/nhn/** @@ -359,7 +359,7 @@ jobs: - name: Check if M365 files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-m365 - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/m365/** @@ -383,7 +383,7 @@ jobs: - name: Check if IaC files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-iac - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/iac/** @@ -407,7 +407,7 @@ jobs: - name: Check if MongoDB Atlas files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-mongodbatlas - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/mongodbatlas/** @@ -431,7 +431,7 @@ jobs: - name: Check if OCI files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-oraclecloud - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/oraclecloud/** @@ -455,7 +455,7 @@ jobs: - name: Check if OpenStack files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-openstack - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/openstack/** @@ -479,7 +479,7 @@ jobs: - name: Check if Google Workspace files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-googleworkspace - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/googleworkspace/** @@ -503,7 +503,7 @@ jobs: - name: Check if Vercel files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-vercel - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/**/vercel/** @@ -527,7 +527,7 @@ jobs: - name: Check if Lib files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-lib - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/lib/** @@ -551,7 +551,7 @@ jobs: - name: Check if Config files changed if: steps.check-changes.outputs.any_changed == 'true' id: changed-config - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ./prowler/config/** diff --git a/.github/workflows/test-impact-analysis.yml b/.github/workflows/test-impact-analysis.yml index f03f99d0fc..34a1536cbc 100644 --- a/.github/workflows/test-impact-analysis.yml +++ b/.github/workflows/test-impact-analysis.yml @@ -66,7 +66,7 @@ jobs: - name: Get changed files id: changed-files - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/ui-container-checks.yml b/.github/workflows/ui-container-checks.yml index ddefab0370..56c9dd13f4 100644 --- a/.github/workflows/ui-container-checks.yml +++ b/.github/workflows/ui-container-checks.yml @@ -42,7 +42,7 @@ jobs: - name: Check if Dockerfile changed id: dockerfile-changed - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: ui/Dockerfile @@ -98,7 +98,7 @@ jobs: - name: Check for UI changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: ui/** files_ignore: | diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 57cb149523..fa28e0c569 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -49,7 +49,7 @@ jobs: - name: Check for UI changes id: check-changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ui/** @@ -62,7 +62,7 @@ jobs: - name: Get changed source files for targeted tests id: changed-source if: steps.check-changes.outputs.any_changed == 'true' - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ui/**/*.ts @@ -78,7 +78,7 @@ jobs: - name: Check for critical path changes (run all tests) id: critical-changes if: steps.check-changes.outputs.any_changed == 'true' - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | ui/lib/** From 9a6a43637d4eac7d9c982ee9db8fa82ef982d0e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:19:50 +0200 Subject: [PATCH 09/65] chore(deps): bump pnpm/action-setup from 4.2.0 to 5.0.0 (#10551) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ui-e2e-tests-v2.yml | 2 +- .github/workflows/ui-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ui-e2e-tests-v2.yml b/.github/workflows/ui-e2e-tests-v2.yml index c739099b08..e75b46c687 100644 --- a/.github/workflows/ui-e2e-tests-v2.yml +++ b/.github/workflows/ui-e2e-tests-v2.yml @@ -163,7 +163,7 @@ jobs: node-version: '24.13.0' - name: Setup pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 with: package_json_file: ui/package.json run_install: false diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index fa28e0c569..a5a609648f 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -96,7 +96,7 @@ jobs: - name: Setup pnpm if: steps.check-changes.outputs.any_changed == 'true' - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 with: package_json_file: ui/package.json run_install: false From 82d487a1e7319f88ceb03bf10e7b2782ab86f4c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:20:11 +0200 Subject: [PATCH 10/65] chore(deps): bump sorenlouv/backport-github-action from 10.2.0 to 11.0.0 (#10540) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index feb8c7c9bf..3563db7154 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -46,7 +46,7 @@ jobs: - name: Backport PR if: steps.label_check.outputs.label_check == 'success' - uses: sorenlouv/backport-github-action@516854e7c9f962b9939085c9a92ea28411d1ae90 # v10.2.0 + uses: sorenlouv/backport-github-action@9460b7102fea25466026ce806c9ebf873ac48721 # v11.0.0 with: github_token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }} auto_backport_label_prefix: ${{ env.BACKPORT_LABEL_PREFIX }} From 632f2633c128a05a0ecddf53872b9e2b9cf835ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:20:34 +0200 Subject: [PATCH 11/65] chore(deps): bump zizmorcore/zizmor-action from 0.5.0 to 0.5.2 (#10550) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-zizmor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-zizmor.yml b/.github/workflows/ci-zizmor.yml index 5962b01efd..2c8d61b961 100644 --- a/.github/workflows/ci-zizmor.yml +++ b/.github/workflows/ci-zizmor.yml @@ -49,6 +49,6 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0 + uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 with: token: ${{ github.token }} From d2f7169537c0fd28630edd549cbf270c0bb51dea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:22:26 +0200 Subject: [PATCH 12/65] chore(deps): bump actions/checkout from 6.0.1 to 6.0.2 (#10548) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/helm-chart-checks.yml | 2 +- .github/workflows/helm-chart-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/helm-chart-checks.yml b/.github/workflows/helm-chart-checks.yml index a0a24c2516..27b3545a1f 100644 --- a/.github/workflows/helm-chart-checks.yml +++ b/.github/workflows/helm-chart-checks.yml @@ -36,7 +36,7 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/helm-chart-release.yml b/.github/workflows/helm-chart-release.yml index c0c5773bdb..e25e154006 100644 --- a/.github/workflows/helm-chart-release.yml +++ b/.github/workflows/helm-chart-release.yml @@ -29,7 +29,7 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false From a2b083e8c850c8adf7343b8e6bd044c13db75fb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:22:58 +0200 Subject: [PATCH 13/65] chore(deps): bump actions/cache from 5.0.3 to 5.0.4 (#10546) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ui-e2e-tests-v2.yml | 4 ++-- .github/workflows/ui-tests.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ui-e2e-tests-v2.yml b/.github/workflows/ui-e2e-tests-v2.yml index e75b46c687..13ccb954eb 100644 --- a/.github/workflows/ui-e2e-tests-v2.yml +++ b/.github/workflows/ui-e2e-tests-v2.yml @@ -172,7 +172,7 @@ jobs: run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - name: Setup pnpm and Next.js cache - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: | ${{ env.STORE_PATH }} @@ -192,7 +192,7 @@ jobs: run: pnpm run build - name: Cache Playwright browsers - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 id: playwright-cache with: path: ~/.cache/ms-playwright diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index a5a609648f..87f9564e41 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -108,7 +108,7 @@ jobs: - name: Setup pnpm and Next.js cache if: steps.check-changes.outputs.any_changed == 'true' - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: | ${{ env.STORE_PATH }} From c6c000a36932e5165612a74a6b099b1221f03b6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:23:18 +0200 Subject: [PATCH 14/65] chore(deps): bump actions/setup-node from 6.2.0 to 6.3.0 (#10545) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ui-e2e-tests-v2.yml | 2 +- .github/workflows/ui-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ui-e2e-tests-v2.yml b/.github/workflows/ui-e2e-tests-v2.yml index 13ccb954eb..a83073b16d 100644 --- a/.github/workflows/ui-e2e-tests-v2.yml +++ b/.github/workflows/ui-e2e-tests-v2.yml @@ -158,7 +158,7 @@ jobs: ' - name: Setup Node.js - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '24.13.0' diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 87f9564e41..631af22615 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -90,7 +90,7 @@ jobs: - name: Setup Node.js ${{ env.NODE_VERSION }} if: steps.check-changes.outputs.any_changed == 'true' - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ env.NODE_VERSION }} From c8d41745ddd74afd4b1084ac00b941e70a66590d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:23:44 +0200 Subject: [PATCH 15/65] chore(deps): bump softprops/action-gh-release from 2.5.0 to 2.6.1 (#10544) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/prepare-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index f0aee83174..c4163aa397 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -380,7 +380,7 @@ jobs: no-changelog - name: Create draft release - uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 with: tag_name: ${{ env.PROWLER_VERSION }} name: Prowler ${{ env.PROWLER_VERSION }} From 10dd9460e9448fb4b7f050a4fdeff906728df5cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:24:42 +0200 Subject: [PATCH 16/65] chore(deps): bump azure/setup-helm from 4.3.0 to 5.0.0 (#10543) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/helm-chart-checks.yml | 2 +- .github/workflows/helm-chart-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/helm-chart-checks.yml b/.github/workflows/helm-chart-checks.yml index 27b3545a1f..3cc857abd7 100644 --- a/.github/workflows/helm-chart-checks.yml +++ b/.github/workflows/helm-chart-checks.yml @@ -41,7 +41,7 @@ jobs: persist-credentials: false - name: Set up Helm - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 + uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 - name: Update chart dependencies run: helm dependency update ${{ env.CHART_PATH }} diff --git a/.github/workflows/helm-chart-release.yml b/.github/workflows/helm-chart-release.yml index e25e154006..e947f8af82 100644 --- a/.github/workflows/helm-chart-release.yml +++ b/.github/workflows/helm-chart-release.yml @@ -34,7 +34,7 @@ jobs: persist-credentials: false - name: Set up Helm - uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4.3.0 + uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 - name: Set appVersion from release tag run: | From ad36938717395fd2ee63d4dcd401e19d2a42ffc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 10:25:14 +0200 Subject: [PATCH 17/65] chore(deps): bump actions/download-artifact from 6.0.0 to 8.0.1 (#10541) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/issue-triage.lock.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 34f059e023..b694e8ecef 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -880,7 +880,7 @@ jobs: destination: /opt/gh-aw/actions - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent-output path: /tmp/gh-aw/safeoutputs/ @@ -992,13 +992,13 @@ jobs: destination: /opt/gh-aw/actions - name: Download agent artifacts continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent-artifacts path: /tmp/gh-aw/threat-detection/ - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent-output path: /tmp/gh-aw/threat-detection/ @@ -1174,7 +1174,7 @@ jobs: destination: /opt/gh-aw/actions - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent-output path: /tmp/gh-aw/safeoutputs/ From b0d8534907b9cdb4c04cf30952a3d795a91720a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Thu, 9 Apr 2026 14:36:55 +0200 Subject: [PATCH 18/65] feat(api): add needed changes for GoogleWorkspace compliance (#10629) --- api/CHANGELOG.md | 1 + api/src/backend/tasks/jobs/export.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 936ce9f6b3..33c573924a 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to the **Prowler API** are documented in this file. - `VALKEY_SCHEME`, `VALKEY_USERNAME`, and `VALKEY_PASSWORD` environment variables to configure Celery broker TLS/auth connection details for Valkey/ElastiCache [(#10420)](https://github.com/prowler-cloud/prowler/pull/10420) - `Vercel` provider support [(#10190)](https://github.com/prowler-cloud/prowler/pull/10190) - Finding groups list and latest endpoints support `sort=delta`, ordering by `new_count` then `changed_count` so groups with the most new findings rank highest [(#10606)](https://github.com/prowler-cloud/prowler/pull/10606) +- Handle CIS and CISA SCuBA compliance framework from google workspace [(#10629)](https://github.com/prowler-cloud/prowler/pull/10629) ### 🔄 Changed diff --git a/api/src/backend/tasks/jobs/export.py b/api/src/backend/tasks/jobs/export.py index 4b8498f7e7..83ef77ad0c 100644 --- a/api/src/backend/tasks/jobs/export.py +++ b/api/src/backend/tasks/jobs/export.py @@ -32,9 +32,13 @@ from prowler.lib.outputs.compliance.cis.cis_aws import AWSCIS from prowler.lib.outputs.compliance.cis.cis_azure import AzureCIS from prowler.lib.outputs.compliance.cis.cis_gcp import GCPCIS from prowler.lib.outputs.compliance.cis.cis_github import GithubCIS +from prowler.lib.outputs.compliance.cis.cis_googleworkspace import GoogleWorkspaceCIS from prowler.lib.outputs.compliance.cis.cis_kubernetes import KubernetesCIS from prowler.lib.outputs.compliance.cis.cis_m365 import M365CIS from prowler.lib.outputs.compliance.cis.cis_oraclecloud import OracleCloudCIS +from prowler.lib.outputs.compliance.cisa_scuba.cisa_scuba_googleworkspace import ( + GoogleWorkspaceCISASCuBA, +) from prowler.lib.outputs.compliance.csa.csa_alibabacloud import AlibabaCloudCSA from prowler.lib.outputs.compliance.csa.csa_aws import AWSCSA from prowler.lib.outputs.compliance.csa.csa_azure import AzureCSA @@ -133,6 +137,10 @@ COMPLIANCE_CLASS_MAP = { "github": [ (lambda name: name.startswith("cis_"), GithubCIS), ], + "googleworkspace": [ + (lambda name: name.startswith("cis_"), GoogleWorkspaceCIS), + (lambda name: name.startswith("cisa_scuba_"), GoogleWorkspaceCISASCuBA), + ], "iac": [ # IaC provider doesn't have specific compliance frameworks yet # Trivy handles its own compliance checks From 56c370d3a4102daf5b3d09b8596049b0ec3d2e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Thu, 9 Apr 2026 15:27:18 +0200 Subject: [PATCH 19/65] chore(ccc): update with latest version and improve mapping (#10625) --- api/src/backend/tasks/jobs/export.py | 6 +- prowler/CHANGELOG.md | 1 + prowler/compliance/aws/ccc_aws.json | 10286 ++++++++------- prowler/compliance/azure/ccc_azure.json | 9857 +++++++++------ prowler/compliance/gcp/ccc_gcp.json | 10463 +++++++++------- prowler/lib/outputs/compliance/ccc/ccc.py | 98 + prowler/lib/outputs/compliance/compliance.py | 10 + tests/lib/outputs/compliance/ccc/__init__.py | 0 .../outputs/compliance/ccc/ccc_aws_test.py | 138 + .../outputs/compliance/ccc/ccc_azure_test.py | 99 + .../outputs/compliance/ccc/ccc_gcp_test.py | 99 + tests/lib/outputs/compliance/fixtures.py | 167 + 12 files changed, 18378 insertions(+), 12846 deletions(-) create mode 100644 prowler/lib/outputs/compliance/ccc/ccc.py create mode 100644 tests/lib/outputs/compliance/ccc/__init__.py create mode 100644 tests/lib/outputs/compliance/ccc/ccc_aws_test.py create mode 100644 tests/lib/outputs/compliance/ccc/ccc_azure_test.py create mode 100644 tests/lib/outputs/compliance/ccc/ccc_gcp_test.py diff --git a/api/src/backend/tasks/jobs/export.py b/api/src/backend/tasks/jobs/export.py index 83ef77ad0c..3be9b81544 100644 --- a/api/src/backend/tasks/jobs/export.py +++ b/api/src/backend/tasks/jobs/export.py @@ -97,7 +97,7 @@ COMPLIANCE_CLASS_MAP = { (lambda name: name.startswith("iso27001_"), AWSISO27001), (lambda name: name.startswith("kisa"), AWSKISAISMSP), (lambda name: name == "prowler_threatscore_aws", ProwlerThreatScoreAWS), - (lambda name: name == "ccc_aws", CCC_AWS), + (lambda name: name.startswith("ccc_"), CCC_AWS), (lambda name: name.startswith("c5_"), AWSC5), (lambda name: name.startswith("csa_"), AWSCSA), ], @@ -106,7 +106,7 @@ COMPLIANCE_CLASS_MAP = { (lambda name: name == "mitre_attack_azure", AzureMitreAttack), (lambda name: name.startswith("ens_"), AzureENS), (lambda name: name.startswith("iso27001_"), AzureISO27001), - (lambda name: name == "ccc_azure", CCC_Azure), + (lambda name: name.startswith("ccc_"), CCC_Azure), (lambda name: name == "prowler_threatscore_azure", ProwlerThreatScoreAzure), (lambda name: name == "c5_azure", AzureC5), (lambda name: name.startswith("csa_"), AzureCSA), @@ -117,7 +117,7 @@ COMPLIANCE_CLASS_MAP = { (lambda name: name.startswith("ens_"), GCPENS), (lambda name: name.startswith("iso27001_"), GCPISO27001), (lambda name: name == "prowler_threatscore_gcp", ProwlerThreatScoreGCP), - (lambda name: name == "ccc_gcp", CCC_GCP), + (lambda name: name.startswith("ccc_"), CCC_GCP), (lambda name: name == "c5_gcp", GCPC5), (lambda name: name.startswith("csa_"), GCPCSA), ], diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index cd5e995eae..8f60b4a7d7 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -21,6 +21,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - `entra_conditional_access_policy_device_registration_mfa_required` check and `entra_intune_enrollment_sign_in_frequency_every_time` enhancement for M365 provider [(#10222)](https://github.com/prowler-cloud/prowler/pull/10222) - `entra_conditional_access_policy_block_elevated_insider_risk` check for M365 provider [(#10234)](https://github.com/prowler-cloud/prowler/pull/10234) - `Vercel` provider support with 30 checks [(#10189)](https://github.com/prowler-cloud/prowler/pull/10189) +- CCC improvements with the latest checks and new mappings [(#10625)](https://github.com/prowler-cloud/prowler/pull/10625) ### 🔄 Changed diff --git a/prowler/compliance/aws/ccc_aws.json b/prowler/compliance/aws/ccc_aws.json index 1f6da6d5f6..11aa32f4ee 100644 --- a/prowler/compliance/aws/ccc_aws.json +++ b/prowler/compliance/aws/ccc_aws.json @@ -1,10 +1,1835 @@ { "Framework": "CCC", - "Version": "", + "Version": "v2025.10", "Provider": "AWS", "Name": "Common Cloud Controls Catalog (CCC)", "Description": "Common Cloud Controls Catalog (CCC) for AWS", "Requirements": [ + { + "Id": "CCC.Core.CN01.AR01", + "Description": "When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not already set, ensure that your services are configured or updated accordingly.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "cloudfront_distributions_https_enabled", + "cloudfront_distributions_origin_traffic_encrypted", + "cloudfront_distributions_using_deprecated_ssl_protocols", + "elb_insecure_ssl_ciphers", + "elb_ssl_listeners", + "elbv2_insecure_ssl_ciphers", + "elbv2_ssl_listeners", + "elbv2_nlb_tls_termination_enabled", + "s3_bucket_secure_transport_policy", + "opensearch_service_domains_https_communications_enforced", + "opensearch_service_domains_node_to_node_encryption_enabled", + "elasticache_redis_cluster_in_transit_encryption_enabled", + "dynamodb_accelerator_cluster_in_transit_encryption_enabled", + "dms_endpoint_ssl_enabled", + "dms_endpoint_redis_in_transit_encryption_enabled", + "kafka_cluster_in_transit_encryption_enabled", + "kafka_connector_in_transit_encryption_enabled", + "redshift_cluster_in_transit_encryption_enabled", + "rds_instance_transport_encrypted", + "transfer_server_in_transit_encryption_enabled", + "glue_database_connections_ssl_enabled", + "sns_subscription_not_using_http_endpoints" + ] + }, + { + "Id": "CCC.Core.CN01.AR02", + "Description": "When a port is exposed for SSH network traffic, all traffic MUST include a SSH handshake AND be encrypted using SSHv2 or higher.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Any time port 22 is exposed, ensure that it has a properly implemented SSH server with SSHv2 enabled and configured with strong ciphers.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "ec2_instance_port_ssh_exposed_to_internet", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", + "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", + "ec2_networkacl_allow_ingress_tcp_port_22" + ] + }, + { + "Id": "CCC.Core.CN01.AR03", + "Description": "When the service receives unencrypted traffic, then it MUST either block the request or automatically redirect it to the secure equivalent.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Review firewall, load balancer, and application configurations to ensure insecure protocols such as HTTP, FTP, and Telnet are not exposed. Where possible, implement automatic redirection to secure protocols such as HTTPS, SFTP, SSH, and regularly scan for protocol drift.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "cloudfront_distributions_https_enabled", + "cloudfront_distributions_origin_traffic_encrypted", + "opensearch_service_domains_https_communications_enforced", + "transfer_server_in_transit_encryption_enabled", + "s3_bucket_secure_transport_policy", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_ftp_20_21", + "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_telnet_23" + ] + }, + { + "Id": "CCC.Core.CN01.AR07", + "Description": "When a port is exposed, the service MUST ensure that the protocol and service officially assigned to that port number by the IANA Service Name and Transport Protocol Port Number Registry, and no other, is run on that port.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number Registry for more information about correct protocol-to-port assignments. Avoid running non-standard services on well-known ports.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN01.AR08", + "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be implemented to require both client and server certificate authentication for all connections.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Configure mTLS for all endpoints that process or transmit sensitive data. Ensure both client and server certificates are validated and managed securely. Regularly review certificate authorities and automate certificate rotation where possible.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "apigateway_restapi_client_certificate_enabled", + "kafka_cluster_mutual_tls_authentication_enabled" + ] + }, + { + "Id": "CCC.Core.CN13.AR01", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST only use valid, unexpired certificates issued by a trusted certificate authority.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH18" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "acm_certificates_expiration_check", + "acm_certificates_with_secure_key_algorithms", + "acm_certificates_transparency_logs_enabled" + ] + }, + { + "Id": "CCC.Core.CN13.AR02", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 180 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "Applicability": [ + "tlp-amber" + ], + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH18" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "acm_certificates_expiration_check" + ] + }, + { + "Id": "CCC.Core.CN13.AR03", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 90 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH18" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "acm_certificates_expiration_check" + ] + }, + { + "Id": "CCC.Core.CN06.AR01", + "Description": "When the service is running, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", + "SubSection": "", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate the service's deployment location is included in this list.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-19" + ] + } + ] + } + ], + "Checks": [ + "organizations_scp_check_deny_regions" + ] + }, + { + "Id": "CCC.Core.CN06.AR02", + "Description": "When a child resource is deployed, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", + "SubSection": "", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate that child resources can only be deployed to locations included in this list.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-19" + ] + } + ] + } + ], + "Checks": [ + "organizations_scp_check_deny_regions" + ] + }, + { + "Id": "CCC.Core.CN08.AR01", + "Description": "When data is created or modified, the data MUST have a complete and recoverable duplicate that is stored in a physically separate data center.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", + "SubSection": "", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement automated data replication processes to ensure that data is consistently duplicated in another region or availability zone. Regularly test data recovery from the replicated location to ensure integrity and availability.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "BCR-08", + "BCR-10", + "BCR-11" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_cross_region_replication", + "backup_plans_exist", + "backup_vaults_exist", + "dynamodb_table_protected_by_backup_plan", + "rds_cluster_protected_by_backup_plan", + "rds_instance_protected_by_backup_plan", + "rds_cluster_multi_az", + "rds_instance_multi_az", + "efs_multi_az_enabled", + "neptune_cluster_multi_az", + "documentdb_cluster_multi_az_enabled", + "elasticache_redis_cluster_multi_az_enabled" + ] + }, + { + "Id": "CCC.Core.CN08.AR02", + "Description": "When data is replicated into a second location, the service MUST be able to accurately represent the replication locations, replication status, and data synchronization status.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", + "SubSection": "", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "BCR-08", + "BCR-10", + "BCR-11" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_cross_region_replication" + ] + }, + { + "Id": "CCC.Core.CN09.AR01", + "Description": "When the service is operational, its logs and any child resource logs MUST NOT be accessible from the resource they record access to.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07", + "CCC.Core.TH09", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-02", + "LOG-04", + "LOG-09" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", + "cloudwatch_log_group_not_publicly_accessible", + "cloudtrail_logs_s3_bucket_access_logging_enabled", + "cloudtrail_log_file_validation_enabled", + "cloudtrail_kms_encryption_enabled", + "cloudtrail_bucket_requires_mfa_delete" + ] + }, + { + "Id": "CCC.Core.CN09.AR02", + "Description": "When the service is operational, disabling the logs for the service or its child resources MUST NOT be possible without also disabling the corresponding resource.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "No normal business operations should disable logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging mechanisms are tightly integrated with service operations, so that logging cannot be disabled without stopping the service itself.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07", + "CCC.Core.TH09", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-02", + "LOG-04", + "LOG-09" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_log_file_validation_enabled", + "cloudtrail_kms_encryption_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled" + ] + }, + { + "Id": "CCC.Core.CN09.AR03", + "Description": "When the service is operational, any attempt to redirect logs for the service or its child resources MUST NOT be possible without halting operation of the corresponding resource and publishing corresponding events to monitored channels.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "No normal business operations should result in the redirection of logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging configurations are immutable during service operation so that any changes require stopping the service and publishing corresponding events to monitored channels.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07", + "CCC.Core.TH09", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-02", + "LOG-04", + "LOG-09" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_log_file_validation_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_changes_to_network_acls_alarm_configured", + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_route_tables_alarm_configured", + "cloudwatch_changes_to_vpcs_alarm_configured" + ] + }, + { + "Id": "CCC.Core.CN10.AR01", + "Description": "When data is replicated, the service MUST ensure that replication only occurs to destinations that are explicitly included within the defined trust perimeter.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN10 Restrict Data Replication to Trust Perimeter", + "SubSection": "", + "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-10", + "DSP-19" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_cross_region_replication" + ] + }, + { + "Id": "CCC.Core.CN02.AR01", + "Description": "When data is stored, it MUST be encrypted using the latest industry-standard encryption methods.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN02 Encrypt Data for Storage", + "SubSection": "", + "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong encryption algorithms.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "UEM-08", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_default_encryption", + "s3_bucket_kms_encryption", + "ec2_ebs_default_encryption", + "ec2_ebs_volume_encryption", + "ec2_ebs_snapshots_encrypted", + "efs_encryption_at_rest_enabled", + "storagegateway_fileshare_encryption_enabled", + "rds_instance_storage_encrypted", + "rds_cluster_storage_encrypted", + "rds_snapshots_encrypted", + "redshift_cluster_encrypted_at_rest", + "documentdb_cluster_storage_encrypted", + "neptune_cluster_storage_encrypted", + "neptune_cluster_snapshot_encrypted", + "dynamodb_tables_kms_cmk_encryption_enabled", + "dynamodb_accelerator_cluster_encryption_enabled", + "kafka_cluster_encryption_at_rest_uses_cmk", + "kinesis_stream_encrypted_at_rest", + "firehose_stream_encrypted_at_rest", + "sns_topics_kms_encryption_at_rest_enabled", + "sqs_queues_server_side_encryption_enabled", + "opensearch_service_domains_encryption_at_rest_enabled", + "athena_workgroup_encryption", + "glue_data_catalogs_metadata_encryption_enabled", + "glue_data_catalogs_connection_passwords_encryption_enabled", + "glue_etl_jobs_amazon_s3_encryption_enabled", + "backup_vaults_encrypted", + "backup_recovery_point_encrypted", + "cloudtrail_kms_encryption_enabled", + "cloudwatch_log_group_kms_encryption_enabled", + "eks_cluster_kms_cmk_encryption_in_secrets_enabled", + "sagemaker_notebook_instance_encryption_enabled", + "apigateway_restapi_cache_encrypted" + ] + }, + { + "Id": "CCC.Core.CN11.AR01", + "Description": "When encryption keys are used, the service MUST verify that all encryption keys use the latest industry-standard cryptographic algorithms.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "acm_certificates_with_secure_key_algorithms" + ] + }, + { + "Id": "CCC.Core.CN11.AR02", + "Description": "When encryption keys are used, the service MUST rotate active keys within 180 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "kms_cmk_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN11.AR03", + "Description": "When encrypting data, the service MUST verify that customer-managed encryption keys (CMEKs) are used.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "kms_cmk_are_used" + ] + }, + { + "Id": "CCC.Core.CN11.AR04", + "Description": "When encryption keys are accessed, the service MUST verify that access to encryption keys is restricted to authorized personnel and services, following the principle of least privilege.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "iam_inline_policy_no_full_access_to_kms", + "iam_policy_no_full_access_to_kms", + "kms_cmk_not_deleted_unintentionally" + ] + }, + { + "Id": "CCC.Core.CN11.AR05", + "Description": "When encryption keys are used, the service MUST rotate active keys within 365 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-clear", + "tlp-green" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "kms_cmk_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN11.AR06", + "Description": "When encryption keys are used, the service MUST rotate active keys within 90 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "kms_cmk_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN14.AR01", + "Description": "When backups are created for disaster recovery purposes, the storage mechanism MUST NOT allow modification or deletion within 30 days of creation.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN14 Maintain Recent Backups", + "SubSection": "", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Use immutable storage solutions where possible. Implement backup retention policies that enforce a minimum retention period of 30 days.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "backup_vaults_exist", + "backup_plans_exist", + "rds_instance_backup_enabled", + "neptune_cluster_backup_enabled", + "documentdb_cluster_backup_enabled" + ] + }, + { + "Id": "CCC.Core.CN14.AR02", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 30 days.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN14 Maintain Recent Backups", + "SubSection": "", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber" + ], + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 30 days.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "backup_vaults_exist", + "backup_plans_exist", + "backup_reportplans_exist", + "backup_recovery_point_encrypted", + "rds_instance_backup_enabled", + "rds_instance_protected_by_backup_plan", + "rds_cluster_protected_by_backup_plan", + "neptune_cluster_backup_enabled", + "documentdb_cluster_backup_enabled", + "dynamodb_table_protected_by_backup_plan", + "efs_have_backup_enabled" + ] + }, + { + "Id": "CCC.Core.CN14.AR03", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 14 days.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN14 Maintain Recent Backups", + "SubSection": "", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 14 days.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "backup_vaults_exist", + "backup_plans_exist", + "rds_instance_backup_enabled", + "neptune_cluster_backup_enabled", + "documentdb_cluster_backup_enabled" + ] + }, + { + "Id": "CCC.Core.CN03.AR01", + "Description": "When an entity attempts to modify the service through a user interface, the authentication process MUST require multiple identifying factors for authentication.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "iam_root_mfa_enabled", + "iam_root_hardware_mfa_enabled", + "iam_user_mfa_enabled_console_access", + "iam_user_hardware_mfa_enabled", + "iam_administrator_access_with_mfa", + "cognito_user_pool_mfa_enabled" + ] + }, + { + "Id": "CCC.Core.CN03.AR02", + "Description": "When an entity attempts to modify the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "iam_no_root_access_key", + "iam_root_credentials_management_enabled", + "iam_user_no_setup_initial_access_key", + "iam_administrator_access_with_mfa", + "apigateway_restapi_authorizers_enabled", + "apigatewayv2_api_authorizers_enabled", + "apigateway_restapi_public_with_authorizer" + ] + }, + { + "Id": "CCC.Core.CN03.AR03", + "Description": "When an entity attempts to view information on the service through a user interface, the authentication process MUST require multiple identifying factors from the user.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "iam_user_mfa_enabled_console_access", + "iam_user_hardware_mfa_enabled", + "iam_root_mfa_enabled", + "iam_root_hardware_mfa_enabled", + "iam_administrator_access_with_mfa", + "cognito_user_pool_mfa_enabled" + ] + }, + { + "Id": "CCC.Core.CN03.AR04", + "Description": "When an entity attempts to view information on the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "iam_no_root_access_key", + "iam_user_no_setup_initial_access_key", + "apigateway_restapi_authorizers_enabled", + "apigatewayv2_api_authorizers_enabled", + "apigateway_restapi_public_with_authorizer" + ] + }, + { + "Id": "CCC.Core.CN05.AR01", + "Description": "When an attempt is made to modify data on the service or a child resource, the service MUST block requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "apigateway_restapi_authorizers_enabled", + "apigateway_restapi_public", + "apigateway_restapi_public_with_authorizer", + "apigatewayv2_api_authorizers_enabled", + "awslambda_function_url_public", + "awslambda_function_not_publicly_accessible", + "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", + "s3_bucket_public_access", + "s3_bucket_public_list_acl", + "s3_bucket_public_write_acl", + "s3_bucket_cross_account_access", + "s3_account_level_public_access_blocks", + "iam_policy_no_full_access_to_cloudtrail", + "iam_policy_no_full_access_to_kms", + "iam_inline_policy_no_full_access_to_cloudtrail", + "iam_inline_policy_no_full_access_to_kms", + "iam_role_administratoraccess_policy", + "iam_group_administrator_access_policy", + "iam_user_administrator_access_policy", + "iam_policy_attached_only_to_group_or_roles", + "iam_role_cross_account_readonlyaccess_policy", + "iam_role_cross_service_confused_deputy_prevention" + ] + }, + { + "Id": "CCC.Core.CN05.AR02", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST refuse requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "iam_root_mfa_enabled", + "iam_root_hardware_mfa_enabled", + "iam_avoid_root_usage", + "iam_user_mfa_enabled_console_access", + "iam_administrator_access_with_mfa", + "iam_group_administrator_access_policy", + "iam_role_administratoraccess_policy", + "iam_user_administrator_access_policy", + "iam_inline_policy_no_full_access_to_cloudtrail", + "iam_inline_policy_no_full_access_to_kms", + "iam_policy_no_full_access_to_cloudtrail", + "iam_policy_no_full_access_to_kms", + "iam_policy_allows_privilege_escalation", + "iam_inline_policy_allows_privilege_escalation", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_customer_unattached_policy_no_administrative_privileges", + "iam_aws_attached_policy_no_administrative_privileges", + "iam_password_policy_minimum_length_14", + "iam_password_policy_uppercase", + "iam_password_policy_lowercase", + "iam_password_policy_symbol", + "iam_password_policy_number", + "iam_password_policy_expires_passwords_within_90_days_or_less", + "iam_password_policy_reuse_24" + ] + }, + { + "Id": "CCC.Core.CN05.AR03", + "Description": "When administrative access or configuration change is attempted on the service or a child resource in a multi-tenant environment, the service MUST refuse requests across tenant boundaries unless the origin is explicitly included in a pre-approved allowlist.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "vpc_endpoint_services_allowed_principals_trust_boundaries", + "vpc_endpoint_connections_trust_boundaries", + "iam_role_cross_service_confused_deputy_prevention", + "iam_role_cross_account_readonlyaccess_policy", + "s3_bucket_cross_account_access", + "eventbridge_bus_cross_account_access", + "eventbridge_schema_registry_cross_account_access" + ] + }, + { + "Id": "CCC.Core.CN05.AR04", + "Description": "When data is requested from outside the trust perimeter, the service MUST refuse requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "accessanalyzer_enabled", + "accessanalyzer_enabled_without_findings", + "vpc_endpoint_connections_trust_boundaries", + "vpc_endpoint_services_allowed_principals_trust_boundaries", + "s3_bucket_cross_account_access", + "s3_bucket_public_access", + "iam_administrator_access_with_mfa", + "iam_inline_policy_no_full_access_to_kms", + "iam_inline_policy_no_full_access_to_cloudtrail", + "iam_policy_no_full_access_to_kms", + "iam_policy_no_full_access_to_cloudtrail", + "iam_policy_attached_only_to_group_or_roles" + ] + }, + { + "Id": "CCC.Core.CN05.AR05", + "Description": "When any request is made from outside the trust perimeter, the service MUST NOT provide any response that may indicate the service exists.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "awslambda_function_url_public", + "apigateway_restapi_public", + "s3_bucket_public_access", + "s3_bucket_policy_public_write_access", + "sns_topics_not_publicly_accessible", + "sqs_queues_not_publicly_accessible", + "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", + "ec2_networkacl_allow_ingress_any_port", + "ec2_securitygroup_default_restrict_traffic", + "vpc_endpoint_for_ec2_enabled", + "vpc_endpoint_connections_trust_boundaries", + "vpc_endpoint_services_allowed_principals_trust_boundaries" + ] + }, + { + "Id": "CCC.Core.CN05.AR06", + "Description": "When any request is made to the service or a child resource, the service MUST refuse requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "iam_root_mfa_enabled", + "iam_root_hardware_mfa_enabled", + "iam_no_root_access_key", + "iam_administrator_access_with_mfa", + "iam_user_mfa_enabled_console_access", + "iam_user_hardware_mfa_enabled", + "iam_root_credentials_management_enabled", + "iam_check_saml_providers_sts", + "iam_policy_attached_only_to_group_or_roles", + "iam_role_cross_service_confused_deputy_prevention", + "iam_role_cross_account_readonlyaccess_policy", + "vpc_endpoint_connections_trust_boundaries", + "vpc_endpoint_services_allowed_principals_trust_boundaries" + ] + }, + { + "Id": "CCC.Core.CN04.AR01", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN04 Log All Access and Changes", + "SubSection": "", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-08" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_multi_region_enabled_logging_management_events", + "cloudtrail_cloudwatch_logging_enabled", + "cloudtrail_log_file_validation_enabled", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "cloudwatch_log_metric_filter_root_usage", + "cloudwatch_log_metric_filter_sign_in_without_mfa", + "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", + "cloudwatch_log_metric_filter_policy_changes", + "cloudwatch_log_metric_filter_security_group_changes", + "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk", + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", + "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", + "cloudwatch_changes_to_network_acls_alarm_configured", + "cloudwatch_changes_to_network_gateways_alarm_configured", + "cloudwatch_changes_to_network_route_tables_alarm_configured", + "cloudwatch_changes_to_vpcs_alarm_configured", + "config_recorder_all_regions_enabled" + ] + }, + { + "Id": "CCC.Core.CN04.AR02", + "Description": "When any attempt is made to modify data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN04 Log All Access and Changes", + "SubSection": "", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-08" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_s3_dataevents_write_enabled", + "cloudtrail_multi_region_enabled_logging_management_events", + "cloudtrail_cloudwatch_logging_enabled", + "awslambda_function_invoke_api_operations_cloudtrail_logging_enabled", + "vpc_flow_logs_enabled", + "apigateway_restapi_logging_enabled", + "apigatewayv2_api_access_logging_enabled" + ] + }, + { + "Id": "CCC.Core.CN04.AR03", + "Description": "When any attempt is made to read data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN04 Log All Access and Changes", + "SubSection": "", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-08" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_s3_dataevents_read_enabled", + "cloudtrail_insights_exist", + "cloudtrail_cloudwatch_logging_enabled", + "cloudtrail_multi_region_enabled", + "apigateway_restapi_logging_enabled", + "apigatewayv2_api_access_logging_enabled", + "s3_bucket_server_access_logging_enabled" + ] + }, + { + "Id": "CCC.Core.CN07.AR01", + "Description": "When enumeration activities are detected, the service MUST publish an event to a monitored channel which includes the client identity, time, and nature of the activity.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement event publication mechanisms and alerts for patterns indicative of enumeration activities, such as repeated access attempts, requests, or liveness probes. Configure alerts to notify security teams of any activities that merit further investigation.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH15" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-05", + "SEF-05" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_threat_detection_enumeration", + "guardduty_is_enabled", + "guardduty_no_high_severity_findings" + ] + }, + { + "Id": "CCC.Core.CN07.AR02", + "Description": "When enumeration activities are detected, the service MUST log the client identity, time, and nature of the activity.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement logging mechanisms to capture details of enumeration activities, including client identity, timestamps, and activity nature. Retain logs according to organizational policies, and occasionally review them for patterns that may indicate reconnaissance activities.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH15" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-05", + "SEF-05" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_threat_detection_enumeration" + ] + }, { "Id": "CCC.AuditLog.CN01.AR01", "Description": "When the signature validation process is performed, then it MUST detect any modification of data.", @@ -18,13 +1843,13 @@ "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure hash of data is included in digital signature. ", + "Recommendation": "Ensure hash of data is included in digital signature.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -61,13 +1886,13 @@ "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure verification process includes a chained hash function. ", + "Recommendation": "Ensure verification process includes a chained hash function.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -110,7 +1935,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06" + "CCC.Core.TH06" ] } ], @@ -135,17 +1960,10 @@ "Checks": [ "cloudtrail_multi_region_enabled", "cloudtrail_multi_region_enabled_logging_management_events", - "cloudtrail_insights_exist", - "cloudtrail_log_file_validation_enabled", - "cloudtrail_kms_encryption_enabled", "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_logs_s3_bucket_access_logging_enabled", "cloudtrail_s3_dataevents_read_enabled", "cloudtrail_s3_dataevents_write_enabled", - "cloudtrail_threat_detection_enumeration", - "cloudtrail_threat_detection_llm_jacking", - "cloudtrail_threat_detection_privilege_escalation" + "cloudtrail_insights_exist" ] }, { @@ -162,12 +1980,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured ", + "Recommendation": "Ensure alerting is correctly configured", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -189,14 +2007,7 @@ } ], "Checks": [ - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudtrail_log_file_validation_enabled", - "cloudtrail_kms_encryption_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_multi_region_enabled", - "cloudtrail_insights_exist", - "s3_bucket_object_lock", - "cloudtrail_multi_region_enabled_logging_management_events" + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled" ] }, { @@ -213,12 +2024,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured ", + "Recommendation": "Ensure alerting is correctly configured", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -240,17 +2051,7 @@ } ], "Checks": [ - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudwatch_changes_to_network_acls_alarm_configured", - "cloudwatch_changes_to_network_gateways_alarm_configured", - "cloudwatch_changes_to_network_route_tables_alarm_configured", - "cloudwatch_changes_to_vpcs_alarm_configured", - "cloudwatch_log_metric_filter_policy_changes", - "cloudwatch_log_metric_filter_aws_organizations_changes", - "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", - "cloudwatch_log_metric_filter_security_group_changes", - "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk", - "cloudwatch_log_metric_filter_unauthorized_api_calls" + "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes" ] }, { @@ -267,13 +2068,13 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure the audit log bucket to enable server access logging. Ensure the target logging bucket is configured for appropriate security, including restricted access and immutability. ", + "Recommendation": "Configure the audit log bucket to enable server access logging. Ensure the target logging bucket is configured for appropriate security, including restricted access and immutability.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01", - "CCC.TH09" + "CCC.Core.TH01", + "CCC.Core.TH09" ] } ], @@ -296,9 +2097,6 @@ ], "Checks": [ "cloudtrail_logs_s3_bucket_access_logging_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_bucket_requires_mfa_delete", - "cloudtrail_kms_encryption_enabled", "s3_bucket_server_access_logging_enabled" ] }, @@ -316,12 +2114,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure audit log exporting. ", + "Recommendation": "Configure audit log exporting.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -344,16 +2142,10 @@ } ], "Checks": [ - "cloudtrail_kms_encryption_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_logs_s3_bucket_access_logging_enabled", - "cloudtrail_bucket_requires_mfa_delete", + "cloudtrail_multi_region_enabled", "cloudtrail_s3_dataevents_read_enabled", "cloudtrail_s3_dataevents_write_enabled", - "cloudtrail_multi_region_enabled", - "cloudtrail_multi_region_enabled_logging_management_events", - "s3_bucket_cross_region_replication", - "s3_bucket_cross_account_access" + "s3_bucket_cross_region_replication" ] }, { @@ -371,13 +2163,13 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce the required data retention period. ", + "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce the required data retention period.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -417,13 +2209,13 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations) on the audit log bucket. ", + "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations) on the audit log bucket.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -462,12 +2254,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy. ", + "Recommendation": "Configure object lock policy.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -506,12 +2298,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Review field level access controls on audit data. ", + "Recommendation": "Review field level access controls on audit data.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -537,11 +2329,8 @@ } ], "Checks": [ - "cloudtrail_kms_encryption_enabled", "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_bucket_requires_mfa_delete", - "cloudwatch_log_group_not_publicly_accessible", - "s3_bucket_public_access" + "cloudwatch_log_group_not_publicly_accessible" ] }, { @@ -559,12 +2348,12 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -587,7 +2376,7 @@ ], "Checks": [ "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_kms_encryption_enabled", + "s3_bucket_public_access", "s3_bucket_public_list_acl", "s3_bucket_public_write_acl" ] @@ -607,12 +2396,12 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -635,220 +2424,34 @@ ], "Checks": [ "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "s3_bucket_public_write_acl", - "s3_bucket_public_list_acl", "s3_bucket_public_access", + "s3_bucket_public_list_acl", + "s3_bucket_public_write_acl", "s3_bucket_policy_public_write_access" ] }, { - "Id": "CCC.Build.CN01.AR01", - "Description": "Attempt to initiate a build using an unauthorized build agent and verify that the build is rejected.", - "Attributes": [ - { - "FamilyName": "Access Control", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.CN01 Restrict Allowed Build Agents", - "SubSection": "", - "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain control over the build environment and prevent unauthorized code execution.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - } - ] - } - ], - "Checks": [ - "codebuild_project_user_controlled_buildspec", - "codebuild_project_source_repo_url_no_sensitive_credentials", - "codebuild_project_uses_allowed_github_organizations", - "codebuild_project_not_publicly_accessible", - "codebuild_project_logging_enabled", - "codebuild_project_s3_logs_encrypted", - "codebuild_project_no_secrets_in_variables", - "codebuild_project_older_90_days" - ] - }, - { - "Id": "CCC.Build.CN02.AR01", - "Description": "Attempt to trigger a build from an unauthorized external service or repository and verify that the build does not start.", - "Attributes": [ - { - "FamilyName": "Access Control", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.CN02 Restrict Allowed External Services for Build Triggers", - "SubSection": "", - "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or repositories to prevent unauthorized code execution or tampering.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - } - ] - } - ], - "Checks": [ - "codebuild_project_uses_allowed_github_organizations", - "codebuild_project_user_controlled_buildspec", - "codebuild_project_source_repo_url_no_sensitive_credentials", - "codebuild_project_not_publicly_accessible" - ] - }, - { - "Id": "CCC.Build.CN03.AR01", - "Description": "Attempt to access the build environment from an external network and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.CN03 Deny External Network Access for Build Environments", - "SubSection": "", - "SubSectionObjective": "Ensure that build environments do not have external network access to prevent unauthorized external access and data exfiltration.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02", - "CCC.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-5" - ] - } - ] - } - ], - "Checks": [ - "codebuild_project_not_publicly_accessible" - ] - }, - { - "Id": "CCC.CntrReg.CN01.AR01", - "Description": "Attempt to push an artifact with known vulnerabilities to the registry and observe if it is flagged or rejected by the vulnerability scanning process.", - "Attributes": [ - { - "FamilyName": "Risk Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CntrReg.CN01 Implement Vulnerability Scanning for Artifacts", - "SubSection": "", - "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for vulnerabilities to identify and remediate security issues before deployment.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.CntrReg.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "ID.RA-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "RA-5", - "SI-5" - ] - } - ] - } - ], - "Checks": [ - "ecr_registry_scan_images_on_push_enabled", - "ecr_repositories_scan_vulnerabilities_in_latest_image" - ] - }, - { - "Id": "CCC.DataWar.CN01.AR01", - "Description": "Attempt to access underlying database tables directly without using managed views and verify that access is denied.", + "Id": "CCC.Logging.CN01.AR01", + "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be enabled by default and directed to the central sink.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.CN01 Enforce Use of Managed Views for Data Access", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", "SubSection": "", - "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users from accessing underlying tables directly and enforcing consistent security policies.", + "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", "Applicability": [ - "tlp-red", - "tlp-amber" + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Logging.TH07" ] } ], @@ -856,14 +2459,490 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-4" + "PR.PS-04" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-2", + "AU-3" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_multi_region_enabled_logging_management_events", + "vpc_flow_logs_enabled" + ] + }, + { + "Id": "CCC.Logging.CN01.AR02", + "Description": "When a new cloud compute resource is deployed, it MUST be configured to forward all relevant logs (e.g., OS, application, service logs) to the central log sink.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", + "SubSection": "", + "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH07" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.PS-04" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-2", + "AU-3" + ] + } + ] + } + ], + "Checks": [ + "vpc_flow_logs_enabled", + "cloudtrail_cloudwatch_logging_enabled", + "apigateway_restapi_logging_enabled", + "apigatewayv2_api_access_logging_enabled", + "awslambda_function_invoke_api_operations_cloudtrail_logging_enabled" + ] + }, + { + "Id": "CCC.Logging.CN02.AR01", + "Description": "When a new log bucket or stream is created, its retention policy MUST be configured in accordance with organisation's data retention policy.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "GV.PO-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-11" + ] + } + ] + } + ], + "Checks": [ + "cloudwatch_log_group_retention_policy_specific_days_enabled" + ] + }, + { + "Id": "CCC.Logging.CN02.AR02", + "Description": "When a query is performed to retrieve log events older than the number of days defined in the organisation's data retention policy, it MUST return an empty result.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "GV.PO-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-11" + ] + } + ] + } + ], + "Checks": [ + "cloudwatch_log_group_retention_policy_specific_days_enabled" + ] + }, + { + "Id": "CCC.Logging.CN03.AR01", + "Description": "When an attempt is made to modify or delete data before the object lock period expires, then the action MUST be denied.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN03 Enable Object Lock On Log Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection using object lock on log storage buckets. This prevents logs from being modified or deleted during the defined retention period, supporting compliance and forensic integrity.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "Configure object lock policy.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.PS-04" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-9", + "AU-11" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_object_lock" + ] + }, + { + "Id": "CCC.Logging.CN04.AR01", + "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify logs.", + "Section": "CCC.Logging.CN04 Restrict Field And Log Type Access", + "SubSection": "", + "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "Review field level access controls on log data.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.PS-04" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6", + "AU-9", + "AC-3", + "PT-2", + "PT-3", + "PT-3" + ] + } + ] + } + ], + "Checks": [ + "cloudwatch_log_group_not_publicly_accessible", + "cloudtrail_logs_s3_bucket_is_not_publicly_accessible" + ] + }, + { + "Id": "CCC.Logging.CN05.AR01", + "Description": "When a log storage bucket is created, the bucket's access control settings MUST explicitly deny public read and write access.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify logs.", + "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", + "SubSection": "", + "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green" + ], + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ "AC-3", - "AC-6" + "SC-7" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", + "s3_bucket_public_access", + "s3_bucket_public_list_acl", + "s3_bucket_public_write_acl", + "s3_account_level_public_access_blocks", + "s3_bucket_level_public_access_block" + ] + }, + { + "Id": "CCC.Logging.CN05.AR02", + "Description": "When the URL of a log storage bucket's object is accessed publicly, the action MUST be denied by bucket policy.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify logs.", + "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", + "SubSection": "", + "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green" + ], + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "SC-7" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_public_access", + "s3_bucket_public_list_acl", + "s3_bucket_public_write_acl", + "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", + "s3_account_level_public_access_blocks" + ] + }, + { + "Id": "CCC.Logging.CN06.AR01", + "Description": "When a single principal executes an anomalously high number of log queries, an alert MUST be generated.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events.", + "Section": "CCC.Logging.CN06 Detect and Alert on Potential Log Exfiltration", + "SubSection": "", + "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt to exfiltrate log data.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-03", + "DE.CM-09" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "CA-7", + "AU-6" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_threat_detection_enumeration" + ] + }, + { + "Id": "CCC.Logging.CN07.AR01", + "Description": "When an audit log event is recorded that corresponds to a modification of the logging service configuration such as disabling a log trail, deleting a log sink, or altering a log forwarding rule, an alert MUST be generated.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events.", + "Section": "CCC.Logging.CN07 Detect and Alert on Log Service Tampering", + "SubSection": "", + "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified, or deleted, indicating a defense evasion attempt.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-03", + "DE.CM-09" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "CA-7", + "AU-6" + ] + } + ] + } + ], + "Checks": [ + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled" + ] + }, + { + "Id": "CCC.Monitor.CN01.AR01", + "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then Rate Limiting MUST be applied and an Audit Alert MUST be generated.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain events from other monitoring services.", + "Section": "CCC.Monitor.CN01 Rate Limiting on External Monitoring", + "SubSection": "", + "SubSectionObjective": "Prevent DoS attacks using External Monitoring tools.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Monitor.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IR-01", + "DE.CM-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-5", + "SC-7" ] } ] @@ -872,25 +2951,27 @@ "Checks": [] }, { - "Id": "CCC.DataWar.CN02.AR01", - "Description": "Attempt to query sensitive columns without the necessary permissions and verify that access is denied or data is masked.", + "Id": "CCC.Monitor.CN02.AR01", + "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied to reduce the network impact of traffic and an alert must triggered.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.CN02 Enforce Column-Level Security Policies", + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain events from other monitoring services.", + "Section": "CCC.Monitor.CN02 Rate Limiting on Metric Generation", "SubSection": "", - "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles, preventing unauthorized access to sensitive information.", + "SubSectionObjective": "Prevent Malicious Actor or misconfiguration from flooding services with metric data.", "Applicability": [ - "tlp-red", - "tlp-amber" + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Monitor.TH06" ] } ], @@ -898,14 +2979,15 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-4" + "DE.CM-01" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AC-3", - "AC-6" + "SC-5(2)", + "CA-7", + "SI-4" ] } ] @@ -914,25 +2996,27 @@ "Checks": [] }, { - "Id": "CCC.DataWar.CN03.AR01", - "Description": "Attempt to query data rows that the user should not have access to and verify that access is denied or data is not returned.", + "Id": "CCC.Monitor.CN03.AR01", + "Description": "When external systems have approved access to internal systems not normally available for public access then they MUST be secured to prevent unauthorised access jumping through to the internal systems and only allow access to specific internal services.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.CN03 Enforce Row-Level Security Policies", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", + "Section": "CCC.Monitor.CN03 Access External Monitoring", "SubSection": "", - "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes, preventing unauthorized access to specific subsets of data.", + "SubSectionObjective": "Control access to Synthetic monitoring solutions using API keys or Certificate based authentication to ensure they don't become an attack path, preventing monitoring systems from forging network requests to gain access to internal systems.", "Applicability": [ - "tlp-red", - "tlp-amber" + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Monitor.TH04" ] } ], @@ -940,14 +3024,111 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-4" + "DE.CM-06", + "PR.IR-01", + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AC-3", - "AC-6" + "AC-3" + ] + } + ] + } + ], + "Checks": [ + "apigateway_restapi_authorizers_enabled", + "apigateway_restapi_client_certificate_enabled", + "apigatewayv2_api_authorizers_enabled", + "apigateway_restapi_public_with_authorizer" + ] + }, + { + "Id": "CCC.Monitor.CN04.AR01", + "Description": "When monitoring dashboards display degraded services which may become potential targets then the dashboard MUST be protected from unauthorised access.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", + "Section": "CCC.Monitor.CN04 Restrict access to Monitoring Dashboards", + "SubSection": "", + "SubSectionObjective": "Control access to Monitoring Dashboards and reports to ensure they don't highlight an attack path.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Monitor.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-09", + "DE.AE-03" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "AC-3" + ] + } + ] + } + ], + "Checks": [ + "cloudwatch_log_group_not_publicly_accessible" + ] + }, + { + "Id": "CCC.Monitor.CN05.AR01", + "Description": "When monitoring services have generated an alert, the service MUST ensure only authorised responders silence or acknowledge the alert.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", + "Section": "CCC.Monitor.CN05 Restrict access to silence or acknowledge an alert", + "SubSection": "", + "SubSectionObjective": "Ensure only a subset of users can silence or acknowledge alerts to prevent attackers hiding their activity.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IR-01", + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3" ] } ] @@ -956,179 +3137,762 @@ "Checks": [] }, { - "Id": "CCC.KeyMgmt.CN01.AR01", - "Description": "When a key version is scheduled for deletion or disabled, an alert MUST be generated within five minutes.", + "Id": "CCC.Monitor.CN06.AR01", + "Description": "When systems push metrics or traces they MUST be authenticated for that particular type of metric or trace", "Attributes": [ { - "FamilyName": "Logging and Metrics Publication", - "FamilyDescription": "Controls that collect, alert, and retain key-management events.", - "Section": "CCC.KeyMgmt.CN01 Alert on Key-version Changes", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", + "Section": "CCC.Monitor.CN06 Metrics pushed for authorised services only", "SubSection": "", - "SubSectionObjective": "Generate near-real-time alerts when a KMS key version is disabled or scheduled for deletion, enabling rapid investigation and recovery.", + "SubSectionObjective": "Use IAM to control which types of metrics or traces can be pushed by different system to avoid a compromised system pushing fabricated metrics about a different service", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Monitor.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-5" + ] + } + ] + } + ], + "Checks": [ + "apigateway_restapi_authorizers_enabled", + "apigatewayv2_api_authorizers_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR01", + "Description": "When a request is made to read a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", "Applicability": [ "tlp-amber", "tlp-red" ], - "Recommendation": "Use native event services (e.g., CloudWatch Events, Azure Monitor, Cloud Audit Logs) to route notifications to an incident-response channel.", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.KeyMgmt.TH01" + "CCC.Core.TH01", + "CCC.Core.TH06" ] } ], "SectionGuidelineMappings": [ { - "ReferenceId": "NIST-CSF", + "ReferenceId": "CCM", "Identifiers": [ - "RS.AN-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IR-5" + "IAM-01", + "IAM-03", + "DSP-17" ] } ] } ], "Checks": [ - "kms_cmk_not_deleted_unintentionally" + "s3_bucket_kms_encryption", + "iam_policy_no_full_access_to_kms", + "iam_inline_policy_no_full_access_to_kms", + "kms_key_not_publicly_accessible" ] }, { - "Id": "CCC.KeyMgmt.CN02.AR01", - "Description": "When IAM roles and key policies are reviewed, Decrypt permission MUST be granted exclusively to documented authorised principals.", + "Id": "CCC.ObjStor.CN01.AR02", + "Description": "When a request is made to read an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_kms_encryption", + "iam_policy_no_full_access_to_kms", + "iam_inline_policy_no_full_access_to_kms", + "kms_key_not_publicly_accessible" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR03", + "Description": "When a request is made to write to a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_kms_encryption", + "iam_policy_no_full_access_to_kms", + "iam_inline_policy_no_full_access_to_kms", + "kms_key_not_publicly_accessible" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR04", + "Description": "When a request is made to write to an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_kms_encryption", + "iam_policy_no_full_access_to_kms", + "iam_inline_policy_no_full_access_to_kms", + "kms_key_not_publicly_accessible" + ] + }, + { + "Id": "CCC.ObjStor.CN03.AR01", + "Description": "When an object storage bucket deletion is attempted, the bucket MUST be fully recoverable for a set time-frame after deletion is requested.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", + "SubSection": "", + "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_object_lock", + "s3_bucket_object_versioning" + ] + }, + { + "Id": "CCC.ObjStor.CN03.AR02", + "Description": "When an attempt is made to modify the retention policy for an object storage bucket, the service MUST prevent the policy from being modified.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", + "SubSection": "", + "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_object_lock" + ] + }, + { + "Id": "CCC.ObjStor.CN04.AR01", + "Description": "When an object is uploaded to the object storage system, the object MUST automatically receive a default retention policy that prevents premature deletion or modification.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN04 Objects have an Effective Retention Policy by Default", + "SubSection": "", + "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + }, + { + "ReferenceId": "CCC.ObjStor", + "Identifiers": [ + "CCC.ObjStor.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_object_lock" + ] + }, + { + "Id": "CCC.ObjStor.CN04.AR02", + "Description": "When an attempt is made to delete or modify an object that is subject to an active retention policy, the service MUST prevent the action from being completed.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN04 Objects have an Effective Retention Policy by Default", + "SubSection": "", + "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + }, + { + "ReferenceId": "CCC.ObjStor", + "Identifiers": [ + "CCC.ObjStor.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_object_lock" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR01", + "Description": "When an object is uploaded to the object storage bucket, the object MUST be stored with a unique identifier.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_object_versioning" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR02", + "Description": "When an object is modified, the service MUST assign a new unique identifier to the modified object to differentiate it from the previous version.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_object_versioning" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR03", + "Description": "When an object is modified, the service MUST allow for recovery of previous versions of the object.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_object_versioning" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR04", + "Description": "When an object is deleted, the service MUST retain other versions of the object to allow for recovery of previous versions.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_object_versioning" + ] + }, + { + "Id": "CCC.ObjStor.CN07.AR01", + "Description": "The object storage service MUST support a configuration option that requires MFA to be successfully completed before any object deletion can be attempted, regardless of the request interface.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN07 Multi-Factor Authentication Is Required for Object Deletion", + "SubSection": "", + "SubSectionObjective": "Ensure that deletion of objects stored in the object storage system is protected by multi-factor authentication (MFA), reducing the risk of accidental, unauthorized, or compromised-credential–based data destruction.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06", + "CCC.Core.TH17" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "IAM-12" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_no_mfa_delete" + ] + }, + { + "Id": "CCC.ObjStor.CN07.AR02", + "Description": "When MFA deletion protection is enabled on a bucket or object namespace, the service MUST deny any deletion request from an identity that has not satisfied the MFA requirement at the time of the request.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN07 Multi-Factor Authentication Is Required for Object Deletion", + "SubSection": "", + "SubSectionObjective": "Ensure that deletion of objects stored in the object storage system is protected by multi-factor authentication (MFA), reducing the risk of accidental, unauthorized, or compromised-credential–based data destruction.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06", + "CCC.Core.TH17" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "IAM-12" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_no_mfa_delete" + ] + }, + { + "Id": "CCC.ObjStor.CN07.AR03", + "Description": "When an attempt is made to delete an object, the service's audit logs MUST clearly record each deletion attempt, including whether MFA was required and whether validation was met.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN07 Multi-Factor Authentication Is Required for Object Deletion", + "SubSection": "", + "SubSectionObjective": "Ensure that deletion of objects stored in the object storage system is protected by multi-factor authentication (MFA), reducing the risk of accidental, unauthorized, or compromised-credential–based data destruction.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06", + "CCC.Core.TH17" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "IAM-12" + ] + } + ] + } + ], + "Checks": [ + "s3_bucket_no_mfa_delete" + ] + }, + { + "Id": "CCC.ObjStor.CN02.AR01", + "Description": "When a permission set is allowed for an object in a bucket, the service MUST allow the same permission set to access all objects in the same bucket.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that enforce least-privilege use of KMS operations.", - "Section": "CCC.KeyMgmt.CN02 Limit Decrypt Permissions", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources.", + "Section": "CCC.ObjStor.CN02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", "SubSection": "", - "SubSectionObjective": "Restrict the Decrypt operation to authorised principals only, applying the principle of least privilege to protect sensitive data.", + "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", "Applicability": [ - "tlp-green" + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" ], - "Recommendation": "Periodically audit policy documents via automated tooling and report any deviations.", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.KeyMgmt.TH02" + "CCC.Core.TH01" ] } ], "SectionGuidelineMappings": [ { - "ReferenceId": "NIST-CSF", + "ReferenceId": "CCM", "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" + "IAM-08" ] } ] } ], "Checks": [ - "kms_cmk_not_deleted_unintentionally", - "kms_cmk_not_multi_region", - "kms_cmk_rotation_enabled", - "kms_cmk_are_used", - "iam_inline_policy_no_full_access_to_kms" + "s3_bucket_acl_prohibited" ] }, { - "Id": "CCC.KeyMgmt.CN03.AR01", - "Description": "When rotation settings are examined, rotation MUST be enabled with an interval not exceeding 365 days.", + "Id": "CCC.ObjStor.CN02.AR02", + "Description": "When a permission set is denied for an object in a bucket, the service MUST deny the same permission set to access all objects in the same bucket.", "Attributes": [ { - "FamilyName": "Key Lifecycle Management", - "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", - "Section": "CCC.KeyMgmt.CN03 Enforce Automatic Rotation", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources.", + "Section": "CCC.ObjStor.CN02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", "SubSection": "", - "SubSectionObjective": "Ensure symmetric keys rotate automatically within policy intervals to reduce exposure of key material.", + "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", "Applicability": [ - "tlp-green" + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" ], - "Recommendation": "Use cloud-provider rotation features and verify via configuration scanning.", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.KeyMgmt.TH03" + "CCC.Core.TH01" ] } ], "SectionGuidelineMappings": [ { - "ReferenceId": "NIST-CSF", + "ReferenceId": "CCM", "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12" + "IAM-08" ] } ] } ], "Checks": [ - "kms_cmk_rotation_enabled" - ] - }, - { - "Id": "CCC.KeyMgmt.CN04.AR01", - "Description": "When a key import request is processed, the key MUST use an approved algorithm (RSA-2048+, EC-P256+) and originate from a certified HSM.", - "Attributes": [ - { - "FamilyName": "Key Lifecycle Management", - "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", - "Section": "CCC.KeyMgmt.CN04 Validate Imported Keys", - "SubSection": "", - "SubSectionObjective": "Accept only externally generated keys that meet approved cryptographic strength and provenance requirements.", - "Applicability": [ - "tlp-green" - ], - "Recommendation": "Implement an approval workflow that validates attestation data before import.", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.KeyMgmt.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "kms_cmk_not_deleted_unintentionally", - "kms_cmk_rotation_enabled", - "kms_cmk_not_multi_region", - "kms_cmk_are_used" + "s3_bucket_acl_prohibited" ] }, { @@ -1136,8 +3900,8 @@ "Description": "When a single client sends more than 2000 requests within any 5-minute sliding window, the load balancer MUST throttle all subsequent requests from that client for at least 60 seconds.", "Attributes": [ { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.", "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", "SubSection": "", "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", @@ -1146,7 +3910,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Implement per-IP token-bucket limits with and verify via synthetic traffic tests. ", + "Recommendation": "Implement per-IP token-bucket limits with and verify via synthetic traffic tests.", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1177,9 +3941,9 @@ } ], "Checks": [ - "elbv2_logging_enabled", - "elb_logging_enabled", - "vpc_flow_logs_enabled" + "wafv2_webacl_with_rules", + "waf_regional_webacl_with_rules", + "waf_global_webacl_with_rules" ] }, { @@ -1187,8 +3951,8 @@ "Description": "When throttling is invoked, the load balancer MUST record the event in the access log within 5 minutes for alerting and trend analysis.", "Attributes": [ { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.", "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", "SubSection": "", "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", @@ -1197,7 +3961,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Enable access logging and configure metric filters on HTTP 429 counts to trigger alerts. ", + "Recommendation": "Enable access logging and configure metric filters on HTTP 429 counts to trigger alerts.", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1228,9 +3992,10 @@ } ], "Checks": [ + "wafv2_webacl_logging_enabled", + "waf_global_webacl_logging_enabled", "elbv2_logging_enabled", - "elb_logging_enabled", - "vpc_flow_logs_enabled" + "elb_logging_enabled" ] }, { @@ -1238,8 +4003,8 @@ "Description": "When more than 10 percent of targets change from healthy to unhealthy within five minutes, an alert MUST be issued.", "Attributes": [ { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.", "Section": "CCC.LB.CN06 Secure Health-Check Telemetry", "SubSection": "", "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on abnormal status changes.", @@ -1248,7 +4013,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Instrument metrics for health check results and target removal events. Configure monitoring alarms to alert on abnormal spikes in unhealthy targets. ", + "Recommendation": "Instrument metrics for health check results and target removal events. Configure monitoring alarms to alert on abnormal spikes in unhealthy targets.", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1276,9 +4041,7 @@ "Checks": [ "elbv2_logging_enabled", "elb_logging_enabled", - "cloudwatch_alarm_actions_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "vpc_flow_logs_enabled" + "cloudwatch_alarm_actions_enabled" ] }, { @@ -1287,7 +4050,7 @@ "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.", "Section": "CCC.LB.CN04 Enforce Distribution Policies", "SubSection": "", "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified only by trusted identities.", @@ -1296,7 +4059,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Define a list of trusted principals allowed to modify routing configurations. Enforce via conditional access policies, and log changes using audit logging. ", + "Recommendation": "Define a list of trusted principals allowed to modify routing configurations. Enforce via conditional access policies, and log changes using audit logging.", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1323,11 +4086,7 @@ ], "Checks": [ "cloudtrail_cloudwatch_logging_enabled", - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "iam_policy_attached_only_to_group_or_roles", - "iam_group_administrator_access_policy", - "iam_role_administratoraccess_policy", - "iam_user_administrator_access_policy" + "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled" ] }, { @@ -1336,7 +4095,7 @@ "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.", "Section": "CCC.LB.CN05 Validate Session Affinity", "SubSection": "", "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking risks.", @@ -1345,7 +4104,7 @@ "tlp-amber", "tlp-red" ], - "Recommendation": "Audit CCC.LB.F15 parameters via configuration scans.", + "Recommendation": "Audit CCC.LB.CP15 parameters via configuration scans.", "SectionThreatMappings": [ { "ReferenceId": "LB", @@ -1370,22 +4129,7 @@ ] } ], - "Checks": [ - "iam_user_administrator_access_policy", - "iam_group_administrator_access_policy", - "iam_role_administratoraccess_policy", - "iam_inline_policy_allows_privilege_escalation", - "iam_inline_policy_no_full_access_to_cloudtrail", - "iam_inline_policy_no_full_access_to_kms", - "iam_inline_policy_no_administrative_privileges", - "iam_policy_allows_privilege_escalation", - "iam_customer_attached_policy_no_administrative_privileges", - "iam_aws_attached_policy_no_administrative_privileges", - "iam_policy_no_full_access_to_cloudtrail", - "iam_policy_no_full_access_to_kms", - "iam_customer_unattached_policy_no_administrative_privileges", - "iam_policy_attached_only_to_group_or_roles" - ] + "Checks": [] }, { "Id": "CCC.LB.CN09.AR01", @@ -1393,7 +4137,7 @@ "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.", "Section": "CCC.LB.CN09 Restrict Management API Access", "SubSection": "", "SubSectionObjective": "Limit load-balancer API calls to authorised identities and trusted networks.", @@ -1429,8 +4173,7 @@ ], "Checks": [ "vpc_endpoint_services_allowed_principals_trust_boundaries", - "vpc_endpoint_connections_trust_boundaries", - "vpc_endpoint_for_ec2_enabled" + "vpc_endpoint_connections_trust_boundaries" ] }, { @@ -1439,7 +4182,7 @@ "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer.", "Section": "CCC.LB.CN02 Auto-Scale Load Balancer Capacity", "SubSection": "", "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic spikes.", @@ -1476,9 +4219,7 @@ "Checks": [ "autoscaling_group_capacity_rebalance_enabled", "autoscaling_group_elb_health_check_enabled", - "autoscaling_group_multiple_az", - "autoscaling_group_multiple_instance_types", - "autoscaling_group_using_ec2_launch_template" + "autoscaling_group_multiple_az" ] }, { @@ -1487,7 +4228,7 @@ "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer.", "Section": "CCC.LB.CN07 Scrub Sensitive Headers", "SubSection": "", "SubSectionObjective": "Remove headers that disclose internal details or software versions from HTTP responses.", @@ -1501,7 +4242,7 @@ { "ReferenceId": "LB", "Identifiers": [ - "CCC.TH15" + "CCC.Core.TH15" ] } ], @@ -1564,1733 +4305,7 @@ } ], "Checks": [ - "acm_certificates_expiration_check", - "acm_certificates_transparency_logs_enabled", - "acm_certificates_with_secure_key_algorithms" - ] - }, - { - "Id": "CCC.Logging.CN01.AR01", - "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be enabled by default and directed to the central sink.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", - "SubSection": "", - "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH07" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PS-04" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_kms_encryption_enabled", - "cloudtrail_log_file_validation_enabled", - "vpc_flow_logs_enabled", - "cloudtrail_logs_s3_bucket_access_logging_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "apigateway_restapi_logging_enabled", - "apigateway_restapi_authorizers_enabled", - "apigateway_restapi_public", - "apigatewayv2_api_access_logging_enabled" - ] - }, - { - "Id": "CCC.Logging.CN01.AR02", - "Description": "When a new cloud compute resource is deployed, it MUST be configured to forward all relevant logs (e.g., OS, application, service logs) to the central log sink.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", - "SubSection": "", - "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH07" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PS-04" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3" - ] - } - ] - } - ], - "Checks": [ - "vpc_flow_logs_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_multi_region_enabled", - "cloudtrail_kms_encryption_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "apigateway_restapi_logging_enabled", - "apigatewayv2_api_access_logging_enabled", - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", - "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible" - ] - }, - { - "Id": "CCC.Logging.CN02.AR01", - "Description": "When a new log bucket or stream is created, its retention policy MUST be configured in accordance with organisation's data retention policy.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", - "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "GV.PO-01" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-11" - ] - } - ] - } - ], - "Checks": [ - "cloudwatch_log_group_retention_policy_specific_days_enabled" - ] - }, - { - "Id": "CCC.Logging.CN02.AR02", - "Description": "When a query is performed to retrieve log events older than the number of days defined in the organisation's data retention policy, it MUST return an empty result.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", - "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "GV.PO-01" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-11" - ] - } - ] - } - ], - "Checks": [ - "cloudwatch_log_group_retention_policy_specific_days_enabled" - ] - }, - { - "Id": "CCC.AuditLog.CN08.AR01", - "Description": "When an attempt is made to modify or delete data before the object lock period expires, then the action MUST be denied.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN03 Enable Object Lock On Log Bucket", - "SubSection": "", - "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection using object lock on log storage buckets. This prevents logs from being modified or deleted during the defined retention period, supporting compliance and forensic integrity.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "Configure object lock policy. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH07" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PS-04" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-9", - "AU-11" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_object_lock" - ] - }, - { - "Id": "CCC.AuditLog.CN04.AR01", - "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs. ", - "Section": "CCC.Logging.CN04 Restrict Field And Log Type Access", - "SubSection": "", - "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "Review field level access controls on log data. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PS-04" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6", - "AU-9", - "AC-3", - "PT-2", - "PT-3", - "PT-3" - ] - } - ] - } - ], - "Checks": [ - "cloudwatch_log_group_not_publicly_accessible", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible" - ] - }, - { - "Id": "CCC.Logging.CN05.AR01", - "Description": "When a log storage bucket is created, the bucket's access control settings MUST explicitly deny public read and write access.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs. ", - "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", - "SubSection": "", - "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green" - ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AA-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_multi_region_enabled", - "cloudtrail_logs_s3_bucket_access_logging_enabled", - "cloudtrail_kms_encryption_enabled", - "s3_bucket_public_list_acl", - "s3_bucket_public_write_acl", - "s3_bucket_public_access", - "s3_account_level_public_access_blocks", - "s3_bucket_level_public_access_block" - ] - }, - { - "Id": "CCC.Logging.CN05.AR02", - "Description": "When the URL of a log storage bucket's object is accessed publicly, the action MUST be denied by bucket policy.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs. ", - "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", - "SubSection": "", - "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green" - ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AA-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_public_access", - "s3_bucket_public_list_acl", - "s3_bucket_public_write_acl", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "s3_bucket_cross_region_replication", - "s3_account_level_public_access_blocks" - ] - }, - { - "Id": "CCC.Logging.CN06.AR01", - "Description": "When a single principal executes an anomalously high number of log queries, an alert MUST be generated.", - "Attributes": [ - { - "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", - "Section": "CCC.Logging.CN06 Detect and Alert on Potential Log Exfiltration", - "SubSection": "", - "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt to exfiltrate log data.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-03", - "DE.CM-09" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-4", - "CA-7", - "AU-6" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_threat_detection_enumeration", - "cloudtrail_threat_detection_privilege_escalation" - ] - }, - { - "Id": "CCC.Logging.CN07.AR01", - "Description": "When an audit log event is recorded that corresponds to a modification of the logging service configuration such as disabling a log trail, deleting a log sink, or altering a log forwarding rule, an alert MUST be generated.", - "Attributes": [ - { - "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", - "Section": "CCC.Logging.CN07 Detect and Alert on Log Service Tampering", - "SubSection": "", - "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified, or deleted, indicating a defense evasion attempt.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-03", - "DE.CM-09" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-4", - "CA-7", - "AU-6" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_insights_exist", - "cloudtrail_log_file_validation_enabled", - "cloudtrail_kms_encryption_enabled", - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudtrail_multi_region_enabled_logging_management_events", - "cloudwatch_changes_to_network_acls_alarm_configured", - "cloudwatch_changes_to_network_gateways_alarm_configured", - "cloudwatch_changes_to_network_route_tables_alarm_configured", - "cloudwatch_changes_to_vpcs_alarm_configured" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR01", - "Description": "When a request is made to read a protected bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_kms_encryption", - "kms_key_not_publicly_accessible", - "iam_policy_no_full_access_to_kms", - "storagegateway_fileshare_encryption_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR02", - "Description": "When a request is made to read a protected object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "kms_key_not_publicly_accessible", - "kms_cmk_not_deleted_unintentionally", - "iam_policy_no_full_access_to_kms", - "iam_inline_policy_no_full_access_to_kms" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR03", - "Description": "When a request is made to write to a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_kms_encryption", - "iam_policy_no_full_access_to_kms", - "kms_key_not_publicly_accessible", - "kms_cmk_not_deleted_unintentionally", - "storagegateway_fileshare_encryption_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR04", - "Description": "When a request is made to write to an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "iam_policy_no_full_access_to_kms", - "iam_inline_policy_no_full_access_to_kms", - "kms_cmk_not_deleted_unintentionally", - "kms_key_not_publicly_accessible", - "kms_cmk_not_multi_region" - ] - }, - { - "Id": "CCC.ObjStor.CN03.AR01", - "Description": "When an object storage bucket deletion is attempted, the bucket MUST be fully recoverable for a set time-frame after deletion is requested.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", - "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_object_versioning", - "s3_bucket_object_lock", - "s3_bucket_lifecycle_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN03.AR02", - "Description": "When an attempt is made to modify the retention policy for an object storage bucket, the service MUST prevent the policy from being modified.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", - "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_object_versioning", - "s3_bucket_lifecycle_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN04.AR01", - "Description": "When an object is uploaded to the object storage system, the object MUST automatically receive a default retention policy that prevents premature deletion or modification.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN04 Log All Access and Changes", - "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", - "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "kinesis_stream_data_retention_period", - "s3_bucket_object_versioning", - "s3_bucket_object_lock" - ] - }, - { - "Id": "CCC.ObjStor.CN04.AR02", - "Description": "When an attempt is made to delete or modify an object that is subject to an active retention policy, the service MUST prevent the action from being completed.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN04 Log All Access and Changes", - "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", - "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "kinesis_stream_data_retention_period", - "dynamodb_table_deletion_protection_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN05.AR01", - "Description": "When an object is uploaded to the object storage bucket, the object MUST be stored with a unique identifier.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_object_versioning", - "s3_bucket_object_lock" - ] - }, - { - "Id": "CCC.ObjStor.CN05.AR02", - "Description": "When an object is modified, the service MUST assign a new unique identifier to the modified object to differentiate it from the previous version.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_object_versioning", - "s3_bucket_object_lock", - "iam_rotate_access_key_90_days", - "ecr_repositories_tag_immutability" - ] - }, - { - "Id": "CCC.ObjStor.CN05.AR03", - "Description": "When an object is modified, the service MUST allow for recovery of previous versions of the object.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_object_versioning", - "dynamodb_tables_pitr_enabled", - "backup_recovery_point_encrypted" - ] - }, - { - "Id": "CCC.ObjStor.CN05.AR04", - "Description": "When an object is deleted, the service MUST retain other versions of the object to allow for recovery of previous versions.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_object_versioning", - "kinesis_stream_data_retention_period", - "kms_cmk_not_deleted_unintentionally" - ] - }, - { - "Id": "CCC.ObjStor.CN06.AR01", - "Description": "When an object storage bucket is accessed, the service MUST store access logs in a separate data store.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN06 Prevent Deployment in Restricted Regions", - "SubSection": "CCC.ObjStor.C06 Access Logs are Stored in a Separate Data Store", - "SubSectionObjective": "Ensure that access logs for object storage buckets are stored in a separate data store to protect against unauthorized access, tampering, or deletion of logs (Logbuckets are exempt from this requirement, but must be tlp-red).", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH07", - "CCC.TH09" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-07", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.15.0" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-9", - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_logs_s3_bucket_access_logging_enabled", - "s3_bucket_server_access_logging_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN02.AR01", - "Description": "When a permission set is allowed for an object in a bucket, the service MUST allow the same permission set to access all objects in the same bucket.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", - "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", - "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-09" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_public_write_acl" - ] - }, - { - "Id": "CCC.ObjStor.CN02.AR02", - "Description": "When a permission set is denied for an object in a bucket, the service MUST deny the same permission set to access all objects in the same bucket.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", - "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", - "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-09" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_public_write_acl", - "s3_bucket_acl_prohibited", - "s3_bucket_public_access" - ] - }, - { - "Id": "CCC.Monitor.CN01.AR01", - "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then Rate Limiting MUST be applied and an Audit Alert MUST be generated.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain events from other monitoring services.", - "Section": "CCC.Monitor.CN01 Rate Limiting on External Monitoring", - "SubSection": "", - "SubSectionObjective": "Prevent DoS attacks using External Monitoring tools.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Monitor.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IR-01", - "DE.CM-01" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-5", - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_threat_detection_enumeration", - "cloudtrail_threat_detection_llm_jacking", - "cloudtrail_threat_detection_privilege_escalation", - "cloudtrail_cloudwatch_logging_enabled", - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudwatch_alarm_actions_alarm_state_configured", - "cloudtrail_multi_region_enabled_logging_management_events" - ] - }, - { - "Id": "CCC.Monitor.CN02.AR01", - "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied to reduce the network impact of traffic and an alert must triggered.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain events from other monitoring services.", - "Section": "CCC.Monitor.CN02 Rate Limiting on Metric Generation", - "SubSection": "", - "SubSectionObjective": "Prevent Malicious Actor or misconfiguration from flooding services with metric data.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Monitor.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-01" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-5(2)", - "CA-7", - "SI-4" - ] - } - ] - } - ], - "Checks": [ - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", - "cloudwatch_changes_to_network_acls_alarm_configured", - "cloudwatch_changes_to_network_gateways_alarm_configured", - "cloudwatch_changes_to_network_route_tables_alarm_configured", - "cloudwatch_changes_to_vpcs_alarm_configured", - "cloudwatch_alarm_actions_enabled", - "cloudwatch_alarm_actions_alarm_state_configured", - "cloudwatch_log_metric_filter_authentication_failures", - "cloudwatch_log_metric_filter_unauthorized_api_calls", - "cloudwatch_log_metric_filter_policy_changes", - "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", - "cloudwatch_log_metric_filter_security_group_changes" - ] - }, - { - "Id": "CCC.Monitor.CN03.AR01", - "Description": "When external systems have approved access to internal systems not normally available for public access then they MUST be secured to prevent unauthorised access jumping through to the internal systems and only allow access to specific internal services.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", - "Section": "CCC.Monitor.CN03 Access External Monitoring", - "SubSection": "", - "SubSectionObjective": "Control access to Synthetic monitoring solutions using API keys or Certificate based authentication to ensure they don't become an attack path, preventing monitoring systems from forging network requests to gain access to internal systems.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Monitor.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-06", - "PR.IR-01", - "PR.AA-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "awslambda_function_url_public", - "apigateway_restapi_public", - "apigateway_restapi_authorizers_enabled", - "apigateway_restapi_public_with_authorizer", - "apigateway_restapi_client_certificate_enabled", - "apigatewayv2_api_authorizers_enabled" - ] - }, - { - "Id": "CCC.Monitor.CN04.AR01", - "Description": "When monitoring dashboards display degraded services which may become potential targets then the dashboard MUST be protected from unauthorised access.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", - "Section": "CCC.Monitor.CN04 Restrict access to Monitoring Dashboards", - "SubSection": "", - "SubSectionObjective": "Control access to Monitoring Dashboards and reports to ensure they don't highlight an attack path.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Monitor.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-09", - "DE.AE-03" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-4", - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "cloudwatch_log_group_not_publicly_accessible", - "cloudwatch_log_group_kms_encryption_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_cloudwatch_logging_enabled" - ] - }, - { - "Id": "CCC.Monitor.CN05.AR01", - "Description": "When monitoring services have generated an alert, the service MUST ensure only authorised responders silence or acknowledge the alert.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", - "Section": "CCC.Monitor.CN05 Restrict access to silence or acknowledge an alert", - "SubSection": "", - "SubSectionObjective": "Ensure only a subset of users can silence or acknowledge alerts to prevent attackers hiding their activity.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IR-01", - "PR.AA-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "iam_administrator_access_with_mfa", - "iam_root_mfa_enabled", - "iam_group_administrator_access_policy", - "iam_policy_attached_only_to_group_or_roles", - "iam_inline_policy_allows_privilege_escalation", - "iam_inline_policy_no_full_access_to_cloudtrail" - ] - }, - { - "Id": "CCC.Monitor.CN06.AR01", - "Description": "When systems push metrics or traces they MUST be authenticated for that particular type of metric or trace", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", - "Section": "CCC.Monitor.CN06 Metrics pushed for authorised services only", - "SubSection": "", - "SubSectionObjective": "Use IAM to control which types of metrics or traces can be pushed by different system to avoid a compromised system pushing fabricated metrics about a different service", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Monitor.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AA-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-5" - ] - } - ] - } - ], - "Checks": [ - "awslambda_function_url_public", - "awslambda_function_not_publicly_accessible", - "apigateway_restapi_authorizers_enabled", - "apigatewayv2_api_authorizers_enabled", - "apigateway_restapi_public_with_authorizer", - "apigateway_restapi_public", - "cloudwatch_log_group_not_publicly_accessible" + "acm_certificates_expiration_check" ] }, { @@ -3345,11 +4360,61 @@ } ], "Checks": [ - "ec2_securitygroup_default_restrict_traffic", - "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", - "ec2_securitygroup_allow_ingress_from_internet_to_any_port" + "ec2_securitygroup_default_restrict_traffic" ] }, + { + "Id": "CCC.VPC.CN02.AR01", + "Description": "When a resource is created in a public subnet, that resource MUST NOT be assigned an external IP address by default.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.VPC.CN02 Limit Resource Creation in Public Subnet", + "SubSection": "", + "SubSectionObjective": "Restrict the creation of resources in the public subnet with direct access to the internet to minimize attack surfaces.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.VPC.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "SEF-05" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.1.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-4" + ] + } + ] + } + ], + "Checks": [] + }, { "Id": "CCC.VPC.CN03.AR01", "Description": "When a VPC peering connection is requested, the service MUST prevent connections from VPCs that are not explicitly allowed.", @@ -3461,6 +4526,390 @@ "vpc_flow_logs_enabled" ] }, + { + "Id": "CCC.KeyMgmt.CN01.AR01", + "Description": "When a key version is scheduled for deletion or disabled, an alert MUST be generated within five minutes.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain key-management events.", + "Section": "CCC.KeyMgmt.CN01 Alert on Key-version Changes", + "SubSection": "", + "SubSectionObjective": "Generate near-real-time alerts when a KMS key version is disabled or scheduled for deletion, enabling rapid investigation and recovery.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Use native event services (e.g., CloudWatch Events, Azure Monitor, Cloud Audit Logs) to route notifications to an incident-response channel.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.KeyMgmt.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "RS.AN-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "IR-5" + ] + } + ] + } + ], + "Checks": [ + "kms_cmk_not_deleted_unintentionally", + "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk" + ] + }, + { + "Id": "CCC.KeyMgmt.CN02.AR01", + "Description": "When IAM roles and key policies are reviewed, Decrypt permission MUST be granted exclusively to documented authorised principals.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that enforce least-privilege use of KMS operations.", + "Section": "CCC.KeyMgmt.CN02 Limit Decrypt Permissions", + "SubSection": "", + "SubSectionObjective": "Restrict the Decrypt operation to authorised principals only, applying the principle of least privilege to protect sensitive data.", + "Applicability": [ + "tlp-green" + ], + "Recommendation": "Periodically audit policy documents via automated tooling and report any deviations.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.KeyMgmt.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_inline_policy_no_full_access_to_kms", + "iam_policy_no_full_access_to_kms" + ] + }, + { + "Id": "CCC.KeyMgmt.CN03.AR01", + "Description": "When rotation settings are examined, rotation MUST be enabled with an interval not exceeding 365 days.", + "Attributes": [ + { + "FamilyName": "Key Lifecycle Management", + "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", + "Section": "CCC.KeyMgmt.CN03 Enforce Automatic Rotation", + "SubSection": "", + "SubSectionObjective": "Ensure symmetric keys rotate automatically within policy intervals to reduce exposure of key material.", + "Applicability": [ + "tlp-green" + ], + "Recommendation": "Use cloud-provider rotation features and verify via configuration scanning.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.KeyMgmt.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-12" + ] + } + ] + } + ], + "Checks": [ + "kms_cmk_rotation_enabled" + ] + }, + { + "Id": "CCC.KeyMgmt.CN04.AR01", + "Description": "When a key import request is processed, the key MUST use an approved algorithm (RSA-2048+, EC-P256+) and originate from a certified HSM.", + "Attributes": [ + { + "FamilyName": "Key Lifecycle Management", + "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", + "Section": "CCC.KeyMgmt.CN04 Validate Imported Keys", + "SubSection": "", + "SubSectionObjective": "Accept only externally generated keys that meet approved cryptographic strength and provenance requirements.", + "Applicability": [ + "tlp-green" + ], + "Recommendation": "Implement an approval workflow that validates attestation data before import.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.KeyMgmt.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-28" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.SecMgmt.CN01.AR01", + "Description": "Attempt to use an outdated version of a secret after its rotation period has passed and verify that access is denied.", + "Attributes": [ + { + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SecMgmt.CN01 Enforce Automatic Secret Rotation", + "SubSection": "", + "SubSectionObjective": "Ensure that secrets are automatically rotated on a defined schedule to reduce the risk of secret compromise and unauthorized access.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH14" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-6" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-12", + "SC-28" + ] + } + ] + } + ], + "Checks": [ + "secretsmanager_automatic_rotation_enabled", + "secretsmanager_secret_rotated_periodically" + ] + }, + { + "Id": "CCC.SecMgmt.CN02.AR01", + "Description": "Attempt to retrieve a secret from an unauthorized region and verify that access is denied.", + "Attributes": [ + { + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SecMgmt.CN02 Enforce Secret Replication Policies", + "SubSection": "", + "SubSectionObjective": "Ensure that secrets are replicated only to authorized locations as per organizational data residency and compliance requirements.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH03", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-5" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "SC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.DataWar.CN01.AR01", + "Description": "Attempt to access underlying database tables directly without using managed views and verify that access is denied.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.DataWar.CN01 Enforce Use of Managed Views for Data Access", + "SubSection": "", + "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users from accessing underlying tables directly and enforcing consistent security policies.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.DataWar.CN02.AR01", + "Description": "Attempt to query sensitive columns without the necessary permissions and verify that access is denied or data is masked.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.DataWar.CN02 Enforce Column-Level Security Policies", + "SubSection": "", + "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles, preventing unauthorized access to sensitive information.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.DataWar.CN03.AR01", + "Description": "Attempt to query data rows that the user should not have access to and verify that access is denied or data is not returned.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.DataWar.CN03 Enforce Row-Level Security Policies", + "SubSection": "", + "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes, preventing unauthorized access to specific subsets of data.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, { "Id": "CCC.Vector.CN01.AR01", "Description": "When a vector embedding is submitted for indexing, the system MUST validate that it matches expected schema, dimension, and format profiles.", @@ -3484,7 +4933,7 @@ "Identifiers": [ "CCC.Vector.TH02", "CCC.Vector.TH05", - "CCC.TH12" + "CCC.Core.TH12" ] } ], @@ -3523,7 +4972,7 @@ "Identifiers": [ "CCC.Vector.TH02", "CCC.Vector.TH04", - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -3538,17 +4987,9 @@ } ], "Checks": [ - "iam_group_administrator_access_policy", - "iam_user_administrator_access_policy", - "iam_role_administratoraccess_policy", - "iam_administrator_access_with_mfa", - "iam_inline_policy_allows_privilege_escalation", - "iam_inline_policy_no_full_access_to_cloudtrail", - "iam_inline_policy_no_full_access_to_kms", - "iam_policy_attached_only_to_group_or_roles", - "iam_customer_attached_policy_no_administrative_privileges", - "iam_customer_unattached_policy_no_administrative_privileges", - "iam_no_custom_policy_permissive_role_assumption" + "opensearch_service_domains_access_control_enabled", + "opensearch_service_domains_internal_user_database_enabled", + "iam_role_administratoraccess_policy" ] }, { @@ -3571,7 +5012,7 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH03", - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -3610,7 +5051,7 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH02", - "CCC.TH12" + "CCC.Core.TH12" ] } ], @@ -3646,8 +5087,8 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH04", - "CCC.TH09", - "CCC.TH04" + "CCC.Core.TH09", + "CCC.Core.TH04" ] } ], @@ -3685,7 +5126,7 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH05", - "CCC.TH06" + "CCC.Core.TH06" ] } ], @@ -3730,457 +5171,25 @@ "Checks": [] }, { - "Id": "CCC.Core.CN01.AR01", - "Description": "When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", + "Id": "CCC.RDMS.CN01.AR02", + "Description": "When an attempt is made to authenticate to the database using known default credentials, the authentication attempt must fail and no access should be granted.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN01 Password Management", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not already set, ensure that your services are configured or updated accordingly. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "cloudfront_distributions_origin_traffic_encrypted", - "cloudfront_distributions_https_enabled", - "cloudfront_distributions_https_sni_enabled", - "s3_bucket_secure_transport_policy", - "dms_endpoint_redis_in_transit_encryption_enabled", - "kafka_cluster_in_transit_encryption_enabled", - "transfer_server_in_transit_encryption_enabled", - "redshift_cluster_in_transit_encryption_enabled", - "rds_instance_transport_encrypted" - ] - }, - { - "Id": "CCC.Core.CN01.AR02", - "Description": "When a port is exposed for SSH network traffic, all traffic MUST include a SSH handshake AND be encrypted using SSHv2 or higher.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Any time port 22 is exposed, ensure that it has a properly implemented SSH server with SSHv2 enabled and configured with strong ciphers. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "ec2_instance_port_ssh_exposed_to_internet", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", - "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", - "ec2_networkacl_allow_ingress_tcp_port_22" - ] - }, - { - "Id": "CCC.Core.CN01.AR03", - "Description": "When the service receives unencrypted traffic, then it MUST either block the request or automatically redirect it to the secure equivalent.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Review firewall, load balancer, and application configurations to ensure insecure protocols such as HTTP, FTP, and Telnet are not exposed. Where possible, implement automatic redirection to secure protocols such as HTTPS, SFTP, SSH, and regularly scan for protocol drift. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "cloudfront_distributions_https_enabled", - "cloudfront_distributions_https_sni_enabled", - "cloudfront_distributions_origin_traffic_encrypted", - "opensearch_service_domains_https_communications_enforced", - "transfer_server_in_transit_encryption_enabled", - "s3_bucket_secure_transport_policy", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_ftp_20_21", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_telnet_23" - ] - }, - { - "Id": "CCC.Core.CN01.AR07", - "Description": "When a port is exposed, the service MUST ensure that the protocol and service officially assigned to that port number by the IANA Service Name and Transport Protocol Port Number Registry, and no other, is run on that port.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number Registry for more information about correct protocol-to-port assignments. Avoid running non-standard services on well-known ports. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "cloudfront_distributions_origin_traffic_encrypted", - "rds_instance_transport_encrypted", - "redshift_cluster_in_transit_encryption_enabled", - "kafka_cluster_in_transit_encryption_enabled", - "transfer_server_in_transit_encryption_enabled", - "dms_endpoint_redis_in_transit_encryption_enabled", - "dms_endpoint_ssl_enabled", - "s3_bucket_secure_transport_policy" - ] - }, - { - "Id": "CCC.Core.CN01.AR08", - "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be implemented to require both client and server certificate authentication for all connections.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Configure mTLS for all endpoints that process or transmit sensitive data. Ensure both client and server certificates are validated and managed securely. Regularly review certificate authorities and automate certificate rotation where possible. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "apigateway_restapi_client_certificate_enabled", - "cloudfront_distributions_custom_ssl_certificate", - "cloudfront_distributions_https_sni_enabled", - "cloudfront_distributions_origin_traffic_encrypted", - "acm_certificates_expiration_check", - "acm_certificates_with_secure_key_algorithms", - "acm_certificates_transparency_logs_enabled", - "s3_bucket_secure_transport_policy", - "dms_endpoint_ssl_enabled", - "dms_endpoint_redis_in_transit_encryption_enabled", - "transfer_server_in_transit_encryption_enabled", - "kafka_cluster_in_transit_encryption_enabled", - "kafka_cluster_mutual_tls_authentication_enabled" - ] - }, - { - "Id": "CCC.Core.CN13.AR01", - "Description": "When a port is exposed that uses certificate-based encryption, the service MUST only use valid, unexpired certificates issued by a trusted certificate authority.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH18" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "acm_certificates_expiration_check", - "acm_certificates_transparency_logs_enabled", - "acm_certificates_with_secure_key_algorithms" - ] - }, - { - "Id": "CCC.Core.CN13.AR02", - "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 180 days of issuance.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "SubSectionObjective": "Ensure default vendor-supplied DB administrator credentials are replaced with strong, unique passwords and that these credentials are properly managed using a secure password or secrets management solution.", "Applicability": [ + "tlp-red", "tlp-amber" ], - "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH18" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "acm_certificates_expiration_check" - ] - }, - { - "Id": "CCC.Core.CN13.AR03", - "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 90 days of issuance.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH18" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "acm_certificates_expiration_check" - ] - }, - { - "Id": "CCC.Core.CN06.AR01", - "Description": "When the service is running, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", - "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate the service's deployment location is included in this list. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH03" + "CCC.RDMS.TH01" ] } ], @@ -4188,19 +5197,92 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.AA-01" ] }, { - "ReferenceId": "CCM", + "ReferenceId": "NIST_800_53", "Identifiers": [ - "DSP-19" + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "rds_cluster_default_admin", + "rds_instance_default_admin" + ] + }, + { + "Id": "CCC.RDMS.CN02.AR01", + "Description": "When repeated failed login attempts are made in a short timeframe, the account must be locked out or rate-limited to prevent further login attempts.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN02 Account Lockout and Rate-Limiting", + "SubSection": "", + "SubSectionObjective": "Ensure the database enforces lockouts or rate-limiting after a specified number of failed authentication attempts. This prevents brute force or password-guessing attacks from succeeding.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.RDMS.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-1" ] }, { - "ReferenceId": "ISO_27001", + "ReferenceId": "NIST_800_53", "Identifiers": [ - "2013 A.11.1.1" + "AC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.RDMS.CN04.AR01", + "Description": "When there is an attempt to perform a backup or restore, then the attempt must fail with an access denied message if credentials or roles that are not explicitly authorized for backup/restore functions.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN04 Access Control for Backup and Restore Operations", + "SubSection": "", + "SubSectionObjective": "Restrict who can initiate, manage, and validate database backup or restore operations through strict role-based or least-privilege access. Prevents accidental or malicious restorations, protecting data integrity and availability.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.RDMS.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" ] }, { @@ -4213,33 +5295,30 @@ } ], "Checks": [ - "organizations_scp_check_deny_regions", - "vpc_endpoint_connections_trust_boundaries", - "vpc_endpoint_services_allowed_principals_trust_boundaries" + "iam_inline_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges" ] }, { - "Id": "CCC.Core.CN06.AR02", - "Description": "When a child resource is deployed, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", + "Id": "CCC.RDMS.CN05.AR01", + "Description": "When an attempt is made to share a snapshot with an unauthorized account, the sharing request must be denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN05 Restrict Snapshot Sharing to Authorized Accounts", "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "SubSectionObjective": "Ensure database snapshots can only be shared with explicitly authorized accounts, thereby minimizing the risk of data exposure or exfiltration.", "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-red", + "tlp-amber" ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate that child resources can only be deployed to locations included in this list. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH03" + "CCC.RDMS.TH05" ] } ], @@ -4247,24 +5326,105 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-19" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.11.1.1" + "PR.DS-10" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ + "AC-4" + ] + } + ] + } + ], + "Checks": [ + "rds_snapshots_public_access", + "neptune_cluster_public_snapshot", + "documentdb_cluster_public_snapshot", + "ec2_ami_public" + ] + }, + { + "Id": "CCC.RDMS.CN03.AR01", + "Description": "When backups are disabled, paused, or fail to run as scheduled, an alert must be triggered and logged.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN03 Enforce and Monitor Automated Backups", + "SubSection": "", + "SubSectionObjective": "Ensure database backups are automatically scheduled, actively monitored, and promptly reported if any disruptions occur. This helps maintain data integrity, facilitates disaster recovery, and supports business continuity when a system failure or breach occurs.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.RDMS.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "CP-9" + ] + } + ] + } + ], + "Checks": [ + "rds_instance_backup_enabled", + "rds_cluster_critical_event_subscription", + "neptune_cluster_backup_enabled", + "documentdb_cluster_backup_enabled" + ] + }, + { + "Id": "CCC.Build.CN01.AR01", + "Description": "Attempt to initiate a build using an unauthorized build agent and verify that the build is rejected.", + "Attributes": [ + { + "FamilyName": "Access Control", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Build.CN01 Restrict Allowed Build Agents", + "SubSection": "", + "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain control over the build environment and prevent unauthorized code execution.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", "AC-6" ] } @@ -4272,92 +5432,31 @@ } ], "Checks": [ - "organizations_scp_check_deny_regions", - "vpc_endpoint_services_allowed_principals_trust_boundaries", - "vpc_endpoint_connections_trust_boundaries" + "codebuild_project_uses_allowed_github_organizations", + "codebuild_project_user_controlled_buildspec", + "codebuild_project_no_secrets_in_variables" ] }, { - "Id": "CCC.Core.CN08.AR01", - "Description": "When data is created or modified, the data MUST have a complete and recoverable duplicate that is stored in a physically separate data center.", + "Id": "CCC.Build.CN02.AR01", + "Description": "Attempt to trigger a build from an unauthorized external service or repository and verify that the build does not start.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", + "FamilyName": "Access Control", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Build.CN02 Restrict Allowed External Services for Build Triggers", "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", + "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or repositories to prevent unauthorized code execution or tampering.", "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement automated data replication processes to ensure that data is consistently duplicated in another region or availability zone. Regularly test data recovery from the replicated location to ensure integrity and availability. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PT-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "BCR-08", - "BCR-10", - "BCR-11" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CP-2", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_cross_region_replication", - "cloudtrail_multi_region_enabled", - "backup_plans_exist", - "backup_vaults_exist", - "backup_vaults_encrypted", - "dynamodb_table_protected_by_backup_plan", - "rds_cluster_protected_by_backup_plan", - "rds_instance_protected_by_backup_plan" - ] - }, - { - "Id": "CCC.Core.CN08.AR02", - "Description": "When data is replicated into a second location, the service MUST be able to accurately represent the replication locations, replication status, and data synchronization status.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", - "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-red", + "tlp-amber" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06" + "CCC.Core.TH01" ] } ], @@ -4365,41 +5464,165 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.PT-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "BCR-08", - "BCR-10", - "BCR-11" + "PR.AC-4" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "CP-2", - "CP-10" + "AC-3", + "AC-6" ] } ] } ], "Checks": [ - "s3_bucket_cross_region_replication" + "codebuild_project_uses_allowed_github_organizations", + "codebuild_project_source_repo_url_no_sensitive_credentials" ] }, { - "Id": "CCC.Core.CN09.AR01", - "Description": "When the service is operational, its logs and any child resource logs MUST NOT be accessible from the resource they record access to.", + "Id": "CCC.Build.CN03.AR01", + "Description": "Attempt to access the build environment from an external network and verify that access is denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Build.CN03 Deny External Network Access for Build Environments", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "SubSectionObjective": "Ensure that build environments do not have external network access to prevent unauthorized external access and data exfiltration.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02", + "CCC.Core.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-5" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7", + "SC-5" + ] + } + ] + } + ], + "Checks": [ + "codebuild_project_not_publicly_accessible" + ] + }, + { + "Id": "CCC.CntrReg.CN01.AR01", + "Description": "Attempt to push an artifact with known vulnerabilities to the registry and observe if it is flagged or rejected by the vulnerability scanning process.", + "Attributes": [ + { + "FamilyName": "Risk Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.CntrReg.CN01 Implement Vulnerability Scanning for Artifacts", + "SubSection": "", + "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for vulnerabilities to identify and remediate security issues before deployment.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.CntrReg.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "ID.RA-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "RA-5", + "SI-5" + ] + } + ] + } + ], + "Checks": [ + "ecr_registry_scan_images_on_push_enabled", + "ecr_repositories_scan_vulnerabilities_in_latest_image" + ] + }, + { + "Id": "CCC.CntrReg.CN02.AR01", + "Description": "Confirm that artifacts older than the specified retention period are automatically deleted from the registry.", + "Attributes": [ + { + "FamilyName": "Data Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.CntrReg.CN02 Implement Cleanup Policies for Artifacts", + "SubSection": "", + "SubSectionObjective": "Ensure that unused or outdated artifacts are cleaned up according to defined policies to manage storage effectively and reduce security risks associated with outdated versions.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH14" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-6" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-12" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.IAM.CN01.AR01", + "Description": "When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials or generating temporary session tokens.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN01 Restrict IAM User Credentials Creation", + "SubSection": "", + "SubSectionObjective": "Prevent non-administrative principals from creating new long-lived credentials like access keys or generating temporary session tokens. This blocks a common privilege escalation and persistence vector.", "Applicability": [ "tlp-clear", "tlp-green", @@ -4411,9 +5634,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07", - "CCC.TH09", - "CCC.TH04" + "CCC.IAM.TH03" ] } ], @@ -4421,61 +5642,52 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-6" + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AU-9" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-02", - "LOG-04", - "LOG-09" + "AC-2", + "AC-3", + "AC-5", + "AC-6" ] } ] } ], "Checks": [ - "cloudtrail_bucket_requires_mfa_delete", - "cloudtrail_log_file_validation_enabled", - "cloudtrail_kms_encryption_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_multi_region_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled" + "iam_no_root_access_key", + "iam_user_no_setup_initial_access_key", + "iam_user_two_active_access_key", + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_inline_policy_no_administrative_privileges" ] }, { - "Id": "CCC.Core.CN09.AR02", - "Description": "When the service is operational, disabling the logs for the service or its child resources MUST NOT be possible without also disabling the corresponding resource.", + "Id": "CCC.IAM.CN01.AR02", + "Description": "When a non-administrative principal attempts to create new credentials or a temporary session token, the service MUST deny the action.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN01 Restrict IAM User Credentials Creation", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "SubSectionObjective": "Prevent non-administrative principals from creating new long-lived credentials like access keys or generating temporary session tokens. This blocks a common privilege escalation and persistence vector.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should disable logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging mechanisms are tightly integrated with service operations, so that logging cannot be disabled without stopping the service itself. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07", - "CCC.TH09", - "CCC.TH04" + "CCC.IAM.TH03" ] } ], @@ -4483,63 +5695,52 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-6" + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AU-9" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-02", - "LOG-04", - "LOG-09" + "AC-2", + "AC-3", + "AC-5", + "AC-6" ] } ] } ], "Checks": [ - "cloudtrail_kms_encryption_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_logs_s3_bucket_access_logging_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "cloudtrail_log_file_validation_enabled", - "vpc_flow_logs_enabled", - "apigateway_restapi_logging_enabled", - "apigatewayv2_api_access_logging_enabled", - "cloudwatch_log_group_not_publicly_accessible", - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled" + "iam_no_root_access_key", + "iam_user_no_setup_initial_access_key", + "iam_user_two_active_access_key", + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_inline_policy_no_administrative_privileges" ] }, { - "Id": "CCC.Core.CN09.AR03", - "Description": "When the service is operational, any attempt to redirect logs for the service or its child resources MUST NOT be possible without halting operation of the corresponding resource and publishing corresponding events to monitored channels.", + "Id": "CCC.IAM.CN02.AR01", + "Description": "When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating, updating, or attaching policies.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN02 Restrict IAM Policies Modification", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "SubSectionObjective": "Ensure that only designated administrative accounts have the ability to create, modify, or attach policies that define permissions for other identities.", "Applicability": [ + "tlp-clear", + "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should result in the redirection of logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging configurations are immutable during service operation so that any changes require stopping the service and publishing corresponding events to monitored channels. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07", - "CCC.TH09", - "CCC.TH04" + "CCC.IAM.TH06" ] } ], @@ -4547,55 +5748,91 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-6" + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AU-9" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-02", - "LOG-04", - "LOG-09" + "AC-2", + "AC-3", + "AC-5", + "AC-6" ] } ] } ], "Checks": [ - "cloudtrail_insights_exist", - "cloudtrail_logs_s3_bucket_access_logging_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "cloudtrail_kms_encryption_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_log_file_validation_enabled", - "cloudwatch_log_group_kms_encryption_enabled", - "cloudwatch_log_group_not_publicly_accessible", - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudwatch_changes_to_network_acls_alarm_configured", - "cloudwatch_changes_to_network_gateways_alarm_configured", - "cloudwatch_changes_to_network_route_tables_alarm_configured", - "cloudwatch_changes_to_vpcs_alarm_configured", - "s3_bucket_server_access_logging_enabled" + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_customer_unattached_policy_no_administrative_privileges", + "iam_policy_allows_privilege_escalation", + "iam_inline_policy_allows_privilege_escalation" ] }, { - "Id": "CCC.Core.CN10.AR01", - "Description": "When data is replicated, the service MUST ensure that replication only occurs to destinations that are explicitly included within the defined trust perimeter.", + "Id": "CCC.IAM.CN02.AR02", + "Description": "When a non-administrative principal attempts to create, update, or attach policies, the service MUST deny the action.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN10 Restrict Data Replication to Trust Perimeter", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN02 Restrict IAM Policies Modification", "SubSection": "", - "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "SubSectionObjective": "Ensure that only designated administrative accounts have the ability to create, modify, or attach policies that define permissions for other identities.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-5", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_customer_unattached_policy_no_administrative_privileges", + "iam_policy_allows_privilege_escalation", + "iam_inline_policy_allows_privilege_escalation" + ] + }, + { + "Id": "CCC.IAM.CN03.AR01", + "Description": "When a policy is created or updated that grants a principal permission to assume a role or impersonate a service identity, the principal MUST NOT contain a wildcard or be public/anonymous.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN03 Restrict Role Assumption / Delegation", + "SubSection": "", + "SubSectionObjective": "Limit which principals can assume a role or impersonate a service identity to only those required. This prevents unintended cross-account or public access by securing the \"who can act as this identity\" boundary.", "Applicability": [ "tlp-green", "tlp-amber", @@ -4606,7 +5843,1511 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH04" + "CCC.IAM.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3", + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_role_cross_account_readonlyaccess_policy", + "iam_role_cross_service_confused_deputy_prevention", + "iam_no_custom_policy_permissive_role_assumption" + ] + }, + { + "Id": "CCC.IAM.CN03.AR02", + "Description": "When an external or unauthenticated principal tries to assume a role or impersonate a service identity, the service MUST deny the action.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN03 Restrict Role Assumption / Delegation", + "SubSection": "", + "SubSectionObjective": "Limit which principals can assume a role or impersonate a service identity to only those required. This prevents unintended cross-account or public access by securing the \"who can act as this identity\" boundary.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3", + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_role_cross_account_readonlyaccess_policy", + "iam_role_cross_service_confused_deputy_prevention", + "iam_no_custom_policy_permissive_role_assumption" + ] + }, + { + "Id": "CCC.IAM.CN04.AR01", + "Description": "When an IAM policy is created or updated, it MUST NOT contain allow statements with wildcard permissions, unless the statement is restricted by a condition.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN04 Restrict Wildcard Usage in IAM Policies", + "SubSection": "", + "SubSectionObjective": "Limit the use of wildcard permissions in IAM policies to prevent overly broad access from being granted by default.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01", + "CCC.IAM.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-6" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_aws_attached_policy_no_administrative_privileges", + "iam_customer_attached_policy_no_administrative_privileges", + "iam_customer_unattached_policy_no_administrative_privileges", + "iam_inline_policy_no_administrative_privileges", + "iam_policy_no_full_access_to_kms", + "iam_policy_no_full_access_to_cloudtrail", + "iam_inline_policy_no_full_access_to_kms", + "iam_inline_policy_no_full_access_to_cloudtrail" + ] + }, + { + "Id": "CCC.IAM.CN05.AR01", + "Description": "When a new cloud account is provisioned, a password policy MUST be configured for IAM users following the minimum PCI DSS v4.0.1 configurations.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN05 Strong Password Policies for IAM Users", + "SubSection": "", + "SubSectionObjective": "Ensure that the password policies for IAM users have strong configurations.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "When a new cloud account is provisioned, a password policy must be configured for all IAM users to align with the minimum requirements defined in PCI DSS v4.0.1. This includes, at a minimum: strength: 0 # Not yet specified - reference-id: A password length of at least 12 characters. strength: 0 # Not yet specified - reference-id: A mix of upper- and lower-case letters, numbers, and special characters. strength: 0 # Not yet specified - reference-id: Prevention of the use of previously used passwords (password history). strength: 0 # Not yet specified - reference-id: Password expiration at a defined interval (e.g., every 90 days). strength: 0 # Not yet specified - reference-id: Account lockout after a defined number of failed login attempts.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "IA-5" + ] + }, + { + "ReferenceId": "PCI-DSS", + "Identifiers": [ + "8.3.9", + "8.6.3" + ] + } + ] + } + ], + "Checks": [ + "iam_password_policy_minimum_length_14", + "iam_password_policy_uppercase", + "iam_password_policy_lowercase", + "iam_password_policy_symbol", + "iam_password_policy_number", + "iam_password_policy_expires_passwords_within_90_days_or_less", + "iam_password_policy_reuse_24" + ] + }, + { + "Id": "CCC.IAM.CN06.AR01", + "Description": "When a static credential such as an access key has existed for 90 days or more, it MUST be rotated.", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN06 Maximum Age for Long-Term Static Credentials", + "SubSection": "", + "SubSectionObjective": "Ensure that long-lived static credentials like access keys are programmatically rotated within a defined time period to limit the window of opportunity if compromised.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "When a static credential such as an access key has existed for 90 days or more, it must be automatically rotated to reduce the risk of compromise due to long-term exposure. Organizations should implement automated checks to identify aging credentials and enforce rotation policies. Additionally, access key usage should be regularly monitored, and credentials that are no longer in use should be deactivated or deleted promptly. Where possible, prefer temporary, short-lived credentials over long-lived static ones to further minimize risk.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH09", + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "iam_rotate_access_key_90_days" + ] + }, + { + "Id": "CCC.IAM.CN07.AR01", + "Description": "When a user account is disabled or deleted in the organization's IdP, the corresponding cloud identity and its access policies MUST be disabled or deleted within 24 hours.", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN07 Automate Identity De-provisioning", + "SubSection": "", + "SubSectionObjective": "Ensure that when an identity is terminated in the central Identity Provider (IdP), ts corresponding access to cloud resources is revoked automatically.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH10", + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "iam_user_console_access_unused", + "iam_user_accesskey_unused" + ] + }, + { + "Id": "CCC.IAM.CN08.AR01", + "Description": "When an IAM user has credentials, such as passwords or access keys, that have not been used for 90 days or more, the unused credentials MUST be removed or deactivated.", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN08 Maximum Age for Unused Credentials", + "SubSection": "", + "SubSectionObjective": "Ensure that unused IAM credentals are removed to reduce exposure in the event of potential compromise.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "IAM user credentials (such as passwords or access keys) that have not been used for 90 days or more must be automatically removed or deactivated.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH11", + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "iam_user_accesskey_unused", + "iam_user_console_access_unused" + ] + }, + { + "Id": "CCC.IAM.CN09.AR01", + "Description": "When a human user accesses the cloud environment, they MUST authenticate through the organization's federated IdP via a standard protocol (e.g., SAML, OIDC).", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN09 Enforce Federated Single Sign-On (SSO) for Human Users", + "SubSection": "", + "SubSectionObjective": "Ensure that all human users must authenticate through a central, federated Identity Provider (IdP) to access the cloud environment. This eliminates cloud-native user accounts with long-lived passwords, centralizes authentication controls, and simplifies lifecycle management.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01", + "CCC.IAM.TH09" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "IA-2" + ] + } + ] + } + ], + "Checks": [ + "iam_check_saml_providers_sts" + ] + }, + { + "Id": "CCC.IAM.CN10.AR01", + "Description": "When suspicious API requests are detected, real time alerts MUST be generated to notify security personnel.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain IAM-related events.", + "Section": "CCC.IAM.CN10 Alert On Anomalous Behaviour", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when anomalous API requests are made by a single identity, such as API requests commonly associated with privilege escalation tactics, originating from an external or malicious IP address or performed by a previously dormant identity, which may indicate that credentals may be compromised, as well as for password brute-force attempts and account lockouts.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-03", + "DE.CM-06", + "DE.CM-09" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "SI-5", + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_unauthorized_api_calls", + "cloudwatch_log_metric_filter_root_usage", + "cloudwatch_log_metric_filter_sign_in_without_mfa", + "guardduty_is_enabled" + ] + }, + { + "Id": "CCC.IAM.CN10.AR02", + "Description": "When suspicious API requests are detected, the associated events MUST be logged, including the source details, time, and nature of the activity.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain IAM-related events.", + "Section": "CCC.IAM.CN10 Alert On Anomalous Behaviour", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when anomalous API requests are made by a single identity, such as API requests commonly associated with privilege escalation tactics, originating from an external or malicious IP address or performed by a previously dormant identity, which may indicate that credentals may be compromised, as well as for password brute-force attempts and account lockouts.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-03", + "DE.CM-06", + "DE.CM-09" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "SI-5", + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "cloudtrail_multi_region_enabled", + "cloudtrail_multi_region_enabled_logging_management_events", + "cloudwatch_log_metric_filter_authentication_failures", + "cloudwatch_log_metric_filter_unauthorized_api_calls" + ] + }, + { + "Id": "CCC.IAM.CN11.AR01", + "Description": "When a cloud account or organization is provisioned, the native automated access and usage analysis services MUST be enabled to continuously monitor for external or public access to resources, and unused access.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain IAM-related events.", + "Section": "CCC.IAM.CN11 Enable Continuous IAM Access and Usage Analysis", + "SubSection": "", + "SubSectionObjective": "Enable and configure the cloud provider's native access and usage analysis services to continuously monitor for external access paths and internal unused access.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH02", + "CCC.IAM.TH10", + "CCC.IAM.TH11" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "ID.RA-01", + "ID.IM-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "CA-7", + "RA-5" + ] + } + ] + } + ], + "Checks": [ + "accessanalyzer_enabled", + "accessanalyzer_enabled_without_findings" + ] + }, + { + "Id": "CCC.GenAI.CN01.AR01", + "Description": "Untrusted input such as user queries, RAG data or tool output MUST be validated before it is passed to a GenAI model.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN01 Model Input Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI model in order to filter or sanitise adversarial queries and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Input Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0021", + "AML.M0015" + ] + } + ] + } + ], + "Checks": [ + "bedrock_guardrail_prompt_attack_filter_enabled", + "bedrock_guardrail_sensitive_information_filter_enabled", + "bedrock_agent_guardrail_enabled" + ] + }, + { + "Id": "CCC.GenAI.CN01.AR02", + "Description": "If malicious patterns such as prompt injection or sensitive data are detected during input validation, the input MUST be blocked or sanitised.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN01 Model Input Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI model in order to filter or sanitise adversarial queries and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Input Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0021", + "AML.M0015" + ] + } + ] + } + ], + "Checks": [ + "bedrock_guardrail_prompt_attack_filter_enabled", + "bedrock_guardrail_sensitive_information_filter_enabled", + "bedrock_agent_guardrail_enabled" + ] + }, + { + "Id": "CCC.GenAI.CN02.AR01", + "Description": "GenAI model output MUST be validated for format conformance, malicious patterns, sensitive data and inapropriate content before being passed to users, application or plugins.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN02 Model Output Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate GenAI model output before passing it to users, applications or plugins in order to filter or sanitise insecure or unreliable output and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03", + "CCC.GenAI.TH04", + "CCC.GenAI.TH05", + "CCC.GenAI.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Output Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0002" + ] + } + ] + } + ], + "Checks": [ + "bedrock_guardrail_sensitive_information_filter_enabled", + "bedrock_agent_guardrail_enabled" + ] + }, + { + "Id": "CCC.GenAI.CN02.AR02", + "Description": "In the event of policy violations, the AI-generated content MUST be redacted, encoded or rejected.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN02 Model Output Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate GenAI model output before passing it to users, applications or plugins in order to filter or sanitise insecure or unreliable output and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03", + "CCC.GenAI.TH04", + "CCC.GenAI.TH05", + "CCC.GenAI.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Output Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0002" + ] + } + ] + } + ], + "Checks": [ + "bedrock_guardrail_sensitive_information_filter_enabled", + "bedrock_agent_guardrail_enabled" + ] + }, + { + "Id": "CCC.GenAI.CN03.AR01", + "Description": "When data is designated for model training or RAG ingestion, then its source MUST be explicitly approved and its provenance documented.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN03 Data Provenance and Source Vetting", + "SubSection": "", + "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes from trusted, approved sources and is authorised for the intended purposes in order to prevent the initial introduction of malicious content or leaked sensitive data.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-006" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Management" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0025" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN03.AR02", + "Description": "Data from unvetted sources MUST NOT be used in production systems.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN03 Data Provenance and Source Vetting", + "SubSection": "", + "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes from trusted, approved sources and is authorised for the intended purposes in order to prevent the initial introduction of malicious content or leaked sensitive data.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-006" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Management" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0025" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN04.AR01", + "Description": "When data is ingested for training, fine-tuning or conversion to vector embeddings, it MUST be validated for sensitive information or malicious content.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN04 Sanitisation of Ingested Data", + "SubSection": "", + "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems from extenal sources or internal knowledge bases, whether for training, conversion to vector embeddings, or real-time retireval, in order to remove or redact poisoned or sensitive data before further processing.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-002" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0007" + ] + } + ] + } + ], + "Checks": [ + "bedrock_guardrail_sensitive_information_filter_enabled" + ] + }, + { + "Id": "CCC.GenAI.CN04.AR02", + "Description": "If sensitive data or malicious content is detected, it must be rejected, redacted or flagged for manual review.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN04 Sanitisation of Ingested Data", + "SubSection": "", + "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems from extenal sources or internal knowledge bases, whether for training, conversion to vector embeddings, or real-time retireval, in order to remove or redact poisoned or sensitive data before further processing.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-002" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0007" + ] + } + ] + } + ], + "Checks": [ + "bedrock_guardrail_sensitive_information_filter_enabled" + ] + }, + { + "Id": "CCC.GenAI.CN05.AR01", + "Description": "When a RAG-enabled system generates a response containing information retrieved from its knowledge base, then the response MUST include a verifiable citation that links back to the specific source document.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN05 Citations and Source Traceability", + "SubSection": "", + "SubSectionObjective": "Require the GenAI system to provide citations or direct links back to the source documents used to generate a response, in to enhance the transparency, trustworthiness, and verifiability of AI-generated content.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH09", + "CCC.GenAI.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-DET-013" + ] + } + ] + } + ], + "Checks": [ + "bedrock_model_invocation_logging_enabled", + "bedrock_model_invocation_logs_encryption_enabled" + ] + }, + { + "Id": "CCC.GenAI.CN06.AR01", + "Description": "When an LLM invokes an external tool (e.g., an API, a plugin), then the tool MUST operate with the least privileges required for performing its intended functionality.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.GenAI.CN06 Least Privilege for Plugins", + "SubSection": "", + "SubSectionObjective": "Restricts the permissions of any external tools the GenAI system can call to limit the potential damage if an agent is coerced to perform unintended actions or vulnerabilities in the tools are exploited.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH07", + "CCC.GenAI.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Agent Permissions" + ] + } + ] + } + ], + "Checks": [ + "bedrock_api_key_no_administrative_privileges", + "bedrock_api_key_no_long_term_credentials" + ] + }, + { + "Id": "CCC.GenAI.CN07.AR01", + "Description": "When an application makes an API call to a foundational model in a production environment, then it MUST specify an explicit version identifier.", + "Attributes": [ + { + "FamilyName": "Configuration Management", + "FamilyDescription": "The Configuration Management control family involves establishing, maintaining and monitoring the configuration of the service and related applications and infrastructure to ensure consistency, secure defaults and compliance.", + "Section": "CCC.GenAI.CN07 Model Version Pinning", + "SubSection": "", + "SubSectionObjective": "Mandate that applications are locked (\"pinned\") to a specific, tested version of a foundational model to prevent unexpected behaviour changes introduced by provider-side updates.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-010" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN08.AR01", + "Description": "When a new AI model is considered for production deployment, it MUST undergo a formal red teaming and quality assurance review.", + "Attributes": [ + { + "FamilyName": "Model Assurance and Evaluation", + "FamilyDescription": "The Model Assurance and Evaluation control family encompasses the proactiveand continuous processes of testing and validating the AI model's behavior to ensure it aligns with safety, ethical, and quality standards.", + "Section": "CCC.GenAI.CN08 Quality Control and Red Teaming", + "SubSection": "", + "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial testing (red teaming) to ensure GenAI system meet all business, quality, security and compliance requirements before getting deployed into production environments.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH02", + "CCC.GenAI.TH04", + "CCC.GenAI.TH08", + "CCC.GenAI.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-005" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Adversarial Training and Testing", + "Red Teaming", + "Product Governance" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0008" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN08.AR02", + "Description": "If model quality review or red teaming identifies an issue that exceeds the organization's risk tolerance, the model MUST NOT be deployed until the issue is remediated.", + "Attributes": [ + { + "FamilyName": "Model Assurance and Evaluation", + "FamilyDescription": "The Model Assurance and Evaluation control family encompasses the proactiveand continuous processes of testing and validating the AI model's behavior to ensure it aligns with safety, ethical, and quality standards.", + "Section": "CCC.GenAI.CN08 Quality Control and Red Teaming", + "SubSection": "", + "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial testing (red teaming) to ensure GenAI system meet all business, quality, security and compliance requirements before getting deployed into production environments.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH02", + "CCC.GenAI.TH04", + "CCC.GenAI.TH08", + "CCC.GenAI.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-005" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Adversarial Training and Testing", + "Red Teaming", + "Product Governance" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0008" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN01.AR01", + "Description": "Verify that only authorized users can access MLDE resources, and that access modes are properly defined and enforced.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN01 Define Access Mode for ML Development Environments", + "SubSection": "", + "SubSectionObjective": "Ensure that access to Machine Learning Development Environment (MLDE) resources is strictly defined and controlled. Only authorized users with appropriate permissions can access these environments, mitigating the risk of unauthorized access, data leakage, or service disruption.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01", + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.1.1", + "2013 A.9.2.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-02" + ] + } + ] + } + ], + "Checks": [ + "sagemaker_notebook_instance_root_access_disabled", + "sagemaker_notebook_instance_vpc_settings_configured" + ] + }, + { + "Id": "CCC.MLDE.CN03.AR01", + "Description": "Verify that root access is disabled on MLDE instances containing sensitive data.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the risk of unauthorized system modifications and potential security breaches.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08", + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [ + "sagemaker_notebook_instance_root_access_disabled" + ] + }, + { + "Id": "CCC.MLDE.CN03.AR02", + "Description": "For MLDE instances without sensitive data, ensure that root access is only enabled when necessary and properly authorized.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the risk of unauthorized system modifications and potential security breaches.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08", + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [ + "sagemaker_notebook_instance_root_access_disabled" + ] + }, + { + "Id": "CCC.MLDE.CN04.AR01", + "Description": "Verify that terminal access is disabled on MLDE instances containing sensitive data.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of unauthorized commands and potential system compromise.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [ + "sagemaker_notebook_instance_root_access_disabled" + ] + }, + { + "Id": "CCC.MLDE.CN04.AR02", + "Description": "For MLDE instances without sensitive data, ensure that terminal access is only enabled when necessary and properly authorized.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of unauthorized commands and potential system compromise.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [ + "sagemaker_notebook_instance_root_access_disabled" + ] + }, + { + "Id": "CCC.MLDE.CN02.AR01", + "Description": "Confirm that file download functionality is disabled on MLDE instances containing sensitive data.", + "Attributes": [ + { + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH02", + "CCC.Core.TH02" ] } ], @@ -4620,166 +7361,98 @@ { "ReferenceId": "CCM", "Identifiers": [ - "DSP-10", - "DSP-19" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-4" - ] - } - ] - } - ], - "Checks": [ - "vpc_endpoint_services_allowed_principals_trust_boundaries", - "vpc_endpoint_connections_trust_boundaries", - "s3_bucket_cross_account_access" - ] - }, - { - "Id": "CCC.Core.CN02.AR01", - "Description": "When data is stored, it MUST be encrypted using the latest industry-standard encryption methods.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN02 Encrypt Data for Storage", - "SubSection": "", - "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong encryption algorithms.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "UEM-08", - "DSP-17" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-13", - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "s3_bucket_default_encryption", - "s3_bucket_kms_encryption", - "firehose_stream_encrypted_at_rest", - "backup_vaults_encrypted", - "backup_recovery_point_encrypted", - "cloudtrail_kms_encryption_enabled", - "cloudwatch_log_group_kms_encryption_enabled", - "opensearch_service_domains_encryption_at_rest_enabled", - "opensearch_service_domains_node_to_node_encryption_enabled", - "kafka_cluster_encryption_at_rest_uses_cmk", - "kinesis_stream_encrypted_at_rest", - "dynamodb_tables_kms_cmk_encryption_enabled", - "dynamodb_accelerator_cluster_encryption_enabled", - "ec2_ebs_default_encryption", - "ec2_ebs_volume_encryption", - "storagegateway_fileshare_encryption_enabled" - ] - }, - { - "Id": "CCC.Core.CN11.AR01", - "Description": "When encryption keys are used, the service MUST verify that all encryption keys use the latest industry-standard cryptographic algorithms.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" + "DSI-05", + "DSI-07" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.10.1.2" + "2013 A.13.2.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-12", - "SC-17" + "SC-7", + "SC-8" ] } ] } ], - "Checks": [ - "kms_cmk_rotation_enabled", - "kms_cmk_not_deleted_unintentionally", - "kms_cmk_not_multi_region", - "kms_cmk_are_used" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR02", - "Description": "When encryption keys are used, the service MUST rotate active keys within 180 days of issuance.", + "Id": "CCC.MLDE.CN02.AR02", + "Description": "For MLDE instances without sensitive data, ensure that file downloads are monitored and logged.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH02", + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-5" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSI-05", + "DSI-07" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.2.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7", + "SC-8" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN05.AR01", + "Description": "Verify that only approved VM and container images can be selected when creating MLDE instances.", + "Attributes": [ + { + "FamilyName": "Configuration Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual machine and container image options available when creating new MLDE instances to approved and secure configurations.", + "Applicability": [ + "tlp-red", "tlp-amber" ], "Recommendation": "", @@ -4787,7 +7460,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" + "CCC.MLDE.TH04" ] } ], @@ -4795,52 +7468,43 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.IP-1" ] }, { "ReferenceId": "CCM", "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" + "TVM-02" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.10.1.2" + "2013 A.12.5.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-12", - "SC-17" + "CM-2" ] } ] } ], - "Checks": [ - "kms_cmk_rotation_enabled", - "kms_cmk_not_deleted_unintentionally", - "kms_cmk_not_multi_region", - "kms_cmk_are_used" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR03", - "Description": "When encrypting data, the service MUST verify that customer-managed encryption keys (CMEKs) are used.", + "Id": "CCC.MLDE.CN05.AR02", + "Description": "Attempt to create an MLDE instance with an unapproved image and confirm that it is denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "", - "SubSection": "CCC.Core.CN11 Protect Encryption Keys", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "FamilyName": "Configuration Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual machine and container image options available when creating new MLDE instances to approved and secure configurations.", "Applicability": [ - "tlp-amber", "tlp-red" ], "Recommendation": "", @@ -4848,7 +7512,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" + "CCC.MLDE.TH04" ] } ], @@ -4856,62 +7520,109 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.IP-1" ] }, { "ReferenceId": "CCM", "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" + "TVM-02" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.10.1.2" + "2013 A.12.5.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-12", - "SC-17" + "CM-2" ] } ] } ], - "Checks": [ - "kms_cmk_not_deleted_unintentionally", - "kms_cmk_rotation_enabled", - "kms_cmk_not_multi_region", - "kms_cmk_are_used" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR04", - "Description": "When encryption keys are accessed, the service MUST verify that access to encryption keys is restricted to authorized personnel and services, following the principle of least privilege.", + "Id": "CCC.MLDE.CN06.AR01", + "Description": "Verify that automatic scheduled upgrades are enabled on user-managed MLDE instances containing sensitive data.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", + "FamilyName": "Vulnerability Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the latest security patches by enforcing automatic scheduled upgrades.", "Applicability": [ - "tlp-clear", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH04", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-12" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "TVM-01", + "TVM-02" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.12.6.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-2" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN06.AR02", + "Description": "Ensure that the upgrade schedule is appropriately configured and does not interfere with critical operations.", + "Attributes": [ + { + "FamilyName": "Vulnerability Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the latest security patches by enforcing automatic scheduled upgrades.", + "Applicability": [ + "tlp-red", + "tlp-amber", "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-clear" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" + "CCC.MLDE.TH04", + "CCC.Core.TH06" ] } ], @@ -4919,109 +7630,43 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.IP-12" ] }, { "ReferenceId": "CCM", "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" + "TVM-01", + "TVM-02" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.10.1.2" + "2013 A.12.6.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-12", - "SC-17" + "SI-2" ] } ] } ], - "Checks": [ - "kms_cmk_rotation_enabled", - "kms_cmk_not_deleted_unintentionally", - "kms_cmk_not_multi_region", - "kms_cmk_are_used" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR05", - "Description": "When encryption keys are used, the service MUST rotate active keys within 365 days of issuance.", + "Id": "CCC.MLDE.CN07.AR01", + "Description": "Verify that MLDE instances containing sensitive data cannot be accessed via public IP addresses.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", - "Applicability": [ - "tlp-clear", - "tlp-green" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-17" - ] - } - ] - } - ], - "Checks": [ - "kms_cmk_rotation_enabled", - "kms_cmk_not_multi_region" - ] - }, - { - "Id": "CCC.Core.CN11.AR06", - "Description": "When encryption keys are used, the service MUST rotate active keys within 90 days of issuance.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", "Applicability": [ "tlp-red" ], @@ -5030,444 +7675,8 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-17" - ] - } - ] - } - ], - "Checks": [ - "kms_cmk_rotation_enabled" - ] - }, - { - "Id": "CCC.Core.CN14.AR01", - "Description": "When backups are created for disaster recovery purposes, the storage mechanism MUST NOT allow modification or deletion within 30 days of creation.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN14 Maintain Recent Backups", - "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Use immutable storage solutions where possible. Implement backup retention policies that enforce a minimum retention period of 30 days. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "neptune_cluster_backup_enabled" - ] - }, - { - "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 30 days.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN14 Maintain Recent Backups", - "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber" - ], - "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 30 days. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "backup_vaults_exist", - "backup_plans_exist", - "backup_reportplans_exist", - "backup_vaults_encrypted", - "backup_recovery_point_encrypted", - "neptune_cluster_backup_enabled", - "rds_instance_backup_enabled", - "rds_instance_protected_by_backup_plan", - "rds_cluster_protected_by_backup_plan", - "dynamodb_table_protected_by_backup_plan" - ] - }, - { - "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 14 days.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN14 Maintain Recent Backups", - "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 14 days. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "backup_vaults_exist", - "backup_vaults_encrypted", - "backup_plans_exist", - "backup_reportplans_exist", - "backup_recovery_point_encrypted", - "neptune_cluster_backup_enabled" - ] - }, - { - "Id": "CCC.Core.CN03.AR01", - "Description": "When an entity attempts to modify the service through a user interface, the authentication process MUST require multiple identifying factors for authentication.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IA-2" - ] - } - ] - } - ], - "Checks": [ - "iam_root_mfa_enabled", - "iam_root_hardware_mfa_enabled", - "iam_user_mfa_enabled_console_access", - "iam_administrator_access_with_mfa", - "cognito_user_pool_mfa_enabled" - ] - }, - { - "Id": "CCC.Core.CN03.AR02", - "Description": "When an entity attempts to modify the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IA-2" - ] - } - ] - } - ], - "Checks": [ - "iam_root_mfa_enabled", - "iam_root_hardware_mfa_enabled", - "iam_user_mfa_enabled_console_access", - "iam_administrator_access_with_mfa", - "cognito_user_pool_mfa_enabled" - ] - }, - { - "Id": "CCC.Core.CN03.AR03", - "Description": "When an entity attempts to view information on the service through a user interface, the authentication process MUST require multiple identifying factors from the user.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IA-2" - ] - } - ] - } - ], - "Checks": [ - "cognito_user_pool_mfa_enabled", - "iam_root_mfa_enabled", - "iam_root_hardware_mfa_enabled", - "iam_user_mfa_enabled_console_access", - "iam_user_hardware_mfa_enabled", - "iam_administrator_access_with_mfa" - ] - }, - { - "Id": "CCC.Core.CN03.AR04", - "Description": "When an entity attempts to view information on the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IA-2" - ] - } - ] - } - ], - "Checks": [ - "iam_user_mfa_enabled_console_access", - "iam_root_mfa_enabled", - "iam_root_hardware_mfa_enabled", - "iam_user_hardware_mfa_enabled", - "iam_administrator_access_with_mfa", - "apigateway_restapi_authorizers_enabled", - "apigateway_restapi_public_with_authorizer" - ] - }, - { - "Id": "CCC.Core.CN05.AR01", - "Description": "When an attempt is made to modify data on the service or a child resource, the service MUST block requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" + "CCC.MLDE.TH02", + "CCC.VPC.TH02" ] } ], @@ -5481,75 +7690,317 @@ { "ReferenceId": "CCM", "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" + "SEF-05" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.13.1.3" + "2013 A.13.1.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AC-3" + "SC-7" ] } ] } ], "Checks": [ - "apigateway_restapi_authorizers_enabled", - "apigateway_restapi_public", - "apigateway_restapi_public_with_authorizer", - "apigatewayv2_api_authorizers_enabled", - "awslambda_function_url_public", + "sagemaker_notebook_instance_without_direct_internet_access_configured" + ] + }, + { + "Id": "CCC.MLDE.CN07.AR02", + "Description": "For MLDE instances without sensitive data requiring public access, ensure that appropriate security controls are in place and access is approved.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH02", + "CCC.VPC.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "SEF-05" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.1.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7" + ] + } + ] + } + ], + "Checks": [ + "sagemaker_notebook_instance_without_direct_internet_access_configured" + ] + }, + { + "Id": "CCC.MLDE.CN08.AR01", + "Description": "Verify that MLDE instances containing sensitive data can only be deployed in approved virtual networks with appropriate security controls.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to ensure they are deployed within approved and secure network environments.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01", + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.1.2" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "sagemaker_notebook_instance_vpc_settings_configured", + "sagemaker_models_vpc_settings_configured", + "sagemaker_training_jobs_vpc_settings_configured" + ] + }, + { + "Id": "CCC.MLDE.CN08.AR02", + "Description": "Ensure that MLDE instances without sensitive data are deployed in networks that meet organizational security standards.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to ensure they are deployed within approved and secure network environments.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01", + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.1.2" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "sagemaker_notebook_instance_vpc_settings_configured", + "sagemaker_models_vpc_settings_configured", + "sagemaker_training_jobs_vpc_settings_configured" + ] + }, + { + "Id": "CCC.Message.CN01.AR01", + "Description": "Attempt to publish a message without using a customer-managed encryption key and verify that the message is rejected or not stored.", + "Attributes": [ + { + "FamilyName": "Encryption", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Message.CN01 Use Customer-Managed Encryption Keys (CMEK) for Messages", + "SubSection": "", + "SubSectionObjective": "Ensure that messages are encrypted using customer-managed encryption keys (CMEK) to provide enhanced control over encryption processes and keys, meeting compliance and security requirements.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-12", + "SC-13" + ] + } + ] + } + ], + "Checks": [ + "sns_topics_kms_encryption_at_rest_enabled", + "sqs_queues_server_side_encryption_enabled", + "kafka_cluster_encryption_at_rest_uses_cmk" + ] + }, + { + "Id": "CCC.SvlsComp.CN01.AR01", + "Description": "Attempt to access the serverless function over the public internet and verify that access is denied.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SvlsComp.CN01 Enforce Use of Private Endpoints for Serverless Function", + "SubSection": "", + "SubSectionObjective": "Ensure that the serverless function is accessible only through a private endpoint, allowing it to communicate securely within a virtual private network and preventing unauthorized external access.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-5" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7", + "SC-8" + ] + } + ] + } + ], + "Checks": [ + "awslambda_function_inside_vpc", "awslambda_function_not_publicly_accessible", - "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", - "s3_bucket_public_access", - "s3_bucket_public_list_acl", - "s3_bucket_public_write_acl", - "s3_bucket_cross_account_access", - "s3_account_level_public_access_blocks", - "iam_policy_no_full_access_to_cloudtrail", - "iam_policy_no_full_access_to_kms", - "iam_inline_policy_no_full_access_to_cloudtrail", - "iam_inline_policy_no_full_access_to_kms", - "iam_role_administratoraccess_policy", - "iam_group_administrator_access_policy", - "iam_user_administrator_access_policy", - "iam_policy_attached_only_to_group_or_roles", - "iam_role_cross_account_readonlyaccess_policy", - "iam_role_cross_service_confused_deputy_prevention" + "awslambda_function_url_public" ] }, { - "Id": "CCC.Core.CN05.AR02", - "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST refuse requests from unauthorized entities.", + "Id": "CCC.SvlsComp.CN02.AR01", + "Description": "Send requests to invoke the function up to the allowed threshold and confirm they are successful; then send additional requests exceeding the threshold from the same entity and verify that they are denied.", "Attributes": [ { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "FamilyName": "Availability", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SvlsComp.CN02 Implement Function Invocation Rate Limits", "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "SubSectionObjective": "Ensure that function invocation is limited to a specified threshold from any single entity, preventing resource exhaustion and denial of service attacks.", "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-red", + "tlp-amber" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH12" ] } ], @@ -5557,650 +8008,19 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" + "PR.DS-4" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AC-3" + "SC-5" ] } ] } ], - "Checks": [ - "iam_root_mfa_enabled", - "iam_root_hardware_mfa_enabled", - "iam_avoid_root_usage", - "iam_user_mfa_enabled_console_access", - "iam_administrator_access_with_mfa", - "iam_group_administrator_access_policy", - "iam_role_administratoraccess_policy", - "iam_inline_policy_no_full_access_to_cloudtrail", - "iam_inline_policy_no_full_access_to_kms", - "iam_policy_no_full_access_to_cloudtrail", - "iam_policy_no_full_access_to_kms", - "iam_policy_allows_privilege_escalation", - "iam_inline_policy_allows_privilege_escalation", - "iam_customer_attached_policy_no_administrative_privileges", - "iam_customer_unattached_policy_no_administrative_privileges", - "iam_aws_attached_policy_no_administrative_privileges", - "iam_password_policy_minimum_length_14", - "iam_password_policy_uppercase", - "iam_password_policy_lowercase", - "iam_password_policy_symbol", - "iam_password_policy_number", - "iam_password_policy_expires_passwords_within_90_days_or_less", - "iam_password_policy_reuse_24", - "iam_check_saml_providers_sts", - "iam_policy_attached_only_to_group_or_roles" - ] - }, - { - "Id": "CCC.Core.CN05.AR03", - "Description": "When administrative access or configuration change is attempted on the service or a child resource in a multi-tenant environment, the service MUST refuse requests across tenant boundaries unless the origin is explicitly included in a pre-approved allowlist.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "vpc_endpoint_services_allowed_principals_trust_boundaries", - "vpc_endpoint_connections_trust_boundaries", - "iam_role_cross_service_confused_deputy_prevention", - "iam_role_cross_account_readonlyaccess_policy", - "s3_bucket_cross_account_access", - "eventbridge_bus_cross_account_access", - "eventbridge_schema_registry_cross_account_access" - ] - }, - { - "Id": "CCC.Core.CN05.AR04", - "Description": "When data is requested from outside the trust perimeter, the service MUST refuse requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "", - "SubSection": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "accessanalyzer_enabled", - "accessanalyzer_enabled_without_findings", - "vpc_endpoint_connections_trust_boundaries", - "vpc_endpoint_services_allowed_principals_trust_boundaries", - "s3_bucket_cross_account_access", - "s3_bucket_public_access", - "iam_administrator_access_with_mfa", - "iam_group_administrator_access_policy", - "iam_user_administrator_access_policy", - "iam_inline_policy_allows_privilege_escalation", - "iam_inline_policy_no_full_access_to_kms", - "iam_inline_policy_no_full_access_to_cloudtrail", - "iam_policy_no_full_access_to_kms", - "iam_policy_no_full_access_to_cloudtrail", - "iam_policy_attached_only_to_group_or_roles", - "iam_user_mfa_enabled_console_access" - ] - }, - { - "Id": "CCC.Core.CN05.AR05", - "Description": "When any request is made from outside the trust perimeter, the service MUST NOT provide any response that may indicate the service exists.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "awslambda_function_url_public", - "apigateway_restapi_public", - "apigateway_restapi_public_with_authorizer", - "s3_bucket_public_list_acl", - "s3_bucket_public_write_acl", - "s3_bucket_public_access", - "s3_bucket_policy_public_write_access", - "sns_topics_not_publicly_accessible", - "ec2_securitygroup_allow_ingress_from_internet_to_all_ports", - "ec2_networkacl_allow_ingress_any_port", - "ec2_networkacl_allow_ingress_tcp_port_22", - "ec2_networkacl_allow_ingress_tcp_port_3389", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_22", - "ec2_securitygroup_allow_ingress_from_internet_to_tcp_port_3389", - "ec2_securitygroup_default_restrict_traffic", - "vpc_endpoint_services_allowed_principals_trust_boundaries", - "vpc_endpoint_connections_trust_boundaries", - "vpc_endpoint_for_ec2_enabled" - ] - }, - { - "Id": "CCC.Core.CN05.AR06", - "Description": "When any request is made to the service or a child resource, the service MUST refuse requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "vpc_endpoint_connections_trust_boundaries", - "vpc_endpoint_services_allowed_principals_trust_boundaries", - "iam_role_cross_service_confused_deputy_prevention", - "iam_role_cross_account_readonlyaccess_policy", - "iam_policy_attached_only_to_group_or_roles", - "iam_group_administrator_access_policy", - "iam_user_mfa_enabled_console_access", - "iam_root_mfa_enabled", - "iam_root_hardware_mfa_enabled", - "iam_no_root_access_key", - "iam_administrator_access_with_mfa", - "iam_root_credentials_management_enabled", - "iam_check_saml_providers_sts", - "iam_user_hardware_mfa_enabled" - ] - }, - { - "Id": "CCC.Core.CN04.AR01", - "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN04 Log All Access and Changes", - "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-08" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3", - "AU-12" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_log_file_validation_enabled", - "awslambda_function_invoke_api_operations_cloudtrail_logging_enabled", - "cloudwatch_log_metric_filter_authentication_failures", - "cloudwatch_log_metric_filter_unauthorized_api_calls", - "cloudwatch_log_metric_filter_root_usage", - "cloudwatch_log_metric_filter_sign_in_without_mfa", - "cloudwatch_log_metric_filter_for_s3_bucket_policy_changes", - "cloudwatch_log_metric_filter_policy_changes", - "cloudwatch_log_metric_filter_security_group_changes", - "cloudwatch_log_metric_filter_disable_or_scheduled_deletion_of_kms_cmk", - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudwatch_log_metric_filter_and_alarm_for_aws_config_configuration_changes_enabled", - "cloudwatch_changes_to_network_acls_alarm_configured", - "cloudwatch_changes_to_network_gateways_alarm_configured", - "cloudwatch_changes_to_network_route_tables_alarm_configured", - "cloudwatch_changes_to_vpcs_alarm_configured", - "vpc_flow_logs_enabled", - "apigateway_restapi_logging_enabled", - "apigatewayv2_api_access_logging_enabled" - ] - }, - { - "Id": "CCC.Core.CN04.AR02", - "Description": "When any attempt is made to modify data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN04 Log All Access and Changes", - "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-08" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3", - "AU-12" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "awslambda_function_invoke_api_operations_cloudtrail_logging_enabled", - "cloudtrail_multi_region_enabled_logging_management_events", - "cloudtrail_cloudwatch_logging_enabled", - "vpc_flow_logs_enabled", - "apigateway_restapi_logging_enabled", - "apigatewayv2_api_access_logging_enabled", - "cloudtrail_threat_detection_enumeration", - "cloudtrail_threat_detection_privilege_escalation", - "cloudtrail_threat_detection_llm_jacking" - ] - }, - { - "Id": "CCC.Core.CN04.AR03", - "Description": "When any attempt is made to read data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN04 Log All Access and Changes", - "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-08" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3", - "AU-12" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_insights_exist", - "cloudtrail_multi_region_enabled", - "cloudtrail_cloudwatch_logging_enabled", - "cloudtrail_kms_encryption_enabled", - "cloudtrail_log_file_validation_enabled", - "cloudtrail_logs_s3_bucket_is_not_publicly_accessible", - "cloudtrail_s3_dataevents_read_enabled", - "cloudtrail_s3_dataevents_write_enabled", - "cloudwatch_log_group_kms_encryption_enabled", - "cloudwatch_log_group_not_publicly_accessible", - "cloudwatch_log_metric_filter_and_alarm_for_cloudtrail_configuration_changes_enabled", - "cloudwatch_log_metric_filter_authentication_failures", - "apigateway_restapi_logging_enabled", - "apigatewayv2_api_access_logging_enabled", - "vpc_flow_logs_enabled" - ] - }, - { - "Id": "CCC.Core.CN07.AR01", - "Description": "When enumeration activities are detected, the service MUST publish an event to a monitored channel which includes the client identity, time, and nature of the activity.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", - "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement event publication mechanisms and alerts for patterns indicative of enumeration activities, such as repeated access attempts, requests, or liveness probes. Configure alerts to notify security teams of any activities that merit further investigation. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH15" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-05", - "SEF-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-6" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_threat_detection_enumeration" - ] - }, - { - "Id": "CCC.Core.CN07.AR02", - "Description": "When enumeration activities are detected, the service MUST log the client identity, time, and nature of the activity.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", - "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement logging mechanisms to capture details of enumeration activities, including client identity, timestamps, and activity nature. Retain logs according to organizational policies, and occasionally review them for patterns that may indicate reconnaissance activities. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH15" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-05", - "SEF-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-6" - ] - } - ] - } - ], - "Checks": [ - "cloudtrail_threat_detection_enumeration" - ] + "Checks": [] } ] } diff --git a/prowler/compliance/azure/ccc_azure.json b/prowler/compliance/azure/ccc_azure.json index 004137ad53..cb87346d13 100644 --- a/prowler/compliance/azure/ccc_azure.json +++ b/prowler/compliance/azure/ccc_azure.json @@ -1,10 +1,1698 @@ { "Framework": "CCC", - "Version": "", + "Version": "v2025.10", "Provider": "Azure", "Name": "Common Cloud Controls Catalog (CCC)", "Description": "Common Cloud Controls Catalog (CCC) for Azure", "Requirements": [ + { + "Id": "CCC.Core.CN01.AR01", + "Description": "When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not already set, ensure that your services are configured or updated accordingly.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "storage_secure_transfer_required_is_enabled", + "storage_ensure_minimum_tls_version_12", + "storage_smb_channel_encryption_with_secure_algorithm", + "storage_smb_protocol_version_is_latest", + "postgresql_flexible_server_enforce_ssl_enabled", + "mysql_flexible_server_ssl_connection_enabled", + "mysql_flexible_server_minimum_tls_version_12", + "sqlserver_recommended_minimal_tls_version", + "app_minimum_tls_version_12", + "app_ensure_http_is_redirected_to_https", + "app_ensure_using_http20", + "app_ftp_deployment_disabled", + "app_function_ftps_deployment_disabled" + ] + }, + { + "Id": "CCC.Core.CN01.AR02", + "Description": "When a port is exposed for SSH network traffic, all traffic MUST include a SSH handshake AND be encrypted using SSHv2 or higher.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Any time port 22 is exposed, ensure that it has a properly implemented SSH server with SSHv2 enabled and configured with strong ciphers.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "network_ssh_internet_access_restricted", + "vm_linux_enforce_ssh_authentication" + ] + }, + { + "Id": "CCC.Core.CN01.AR03", + "Description": "When the service receives unencrypted traffic, then it MUST either block the request or automatically redirect it to the secure equivalent.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Review firewall, load balancer, and application configurations to ensure insecure protocols such as HTTP, FTP, and Telnet are not exposed. Where possible, implement automatic redirection to secure protocols such as HTTPS, SFTP, SSH, and regularly scan for protocol drift.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "app_ensure_http_is_redirected_to_https", + "storage_secure_transfer_required_is_enabled", + "app_ftp_deployment_disabled", + "app_function_ftps_deployment_disabled" + ] + }, + { + "Id": "CCC.Core.CN01.AR07", + "Description": "When a port is exposed, the service MUST ensure that the protocol and service officially assigned to that port number by the IANA Service Name and Transport Protocol Port Number Registry, and no other, is run on that port.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number Registry for more information about correct protocol-to-port assignments. Avoid running non-standard services on well-known ports.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN01.AR08", + "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be implemented to require both client and server certificate authentication for all connections.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Configure mTLS for all endpoints that process or transmit sensitive data. Ensure both client and server certificates are validated and managed securely. Regularly review certificate authorities and automate certificate rotation where possible.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "app_client_certificates_on" + ] + }, + { + "Id": "CCC.Core.CN13.AR01", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST only use valid, unexpired certificates issued by a trusted certificate authority.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH18" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "keyvault_key_expiration_set_in_non_rbac", + "keyvault_rbac_key_expiration_set", + "keyvault_non_rbac_secret_expiration_set", + "keyvault_rbac_secret_expiration_set" + ] + }, + { + "Id": "CCC.Core.CN13.AR02", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 180 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "Applicability": [ + "tlp-amber" + ], + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH18" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "keyvault_key_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN13.AR03", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 90 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH18" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "keyvault_key_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN06.AR01", + "Description": "When the service is running, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", + "SubSection": "", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate the service's deployment location is included in this list.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-19" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN06.AR02", + "Description": "When a child resource is deployed, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", + "SubSection": "", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate that child resources can only be deployed to locations included in this list.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-19" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN08.AR01", + "Description": "When data is created or modified, the data MUST have a complete and recoverable duplicate that is stored in a physically separate data center.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", + "SubSection": "", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement automated data replication processes to ensure that data is consistently duplicated in another region or availability zone. Regularly test data recovery from the replicated location to ensure integrity and availability.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "BCR-08", + "BCR-10", + "BCR-11" + ] + } + ] + } + ], + "Checks": [ + "storage_geo_redundant_enabled", + "vm_backup_enabled" + ] + }, + { + "Id": "CCC.Core.CN08.AR02", + "Description": "When data is replicated into a second location, the service MUST be able to accurately represent the replication locations, replication status, and data synchronization status.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", + "SubSection": "", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "BCR-08", + "BCR-10", + "BCR-11" + ] + } + ] + } + ], + "Checks": [ + "storage_geo_redundant_enabled" + ] + }, + { + "Id": "CCC.Core.CN09.AR01", + "Description": "When the service is operational, its logs and any child resource logs MUST NOT be accessible from the resource they record access to.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07", + "CCC.Core.TH09", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-02", + "LOG-04", + "LOG-09" + ] + } + ] + } + ], + "Checks": [ + "monitor_storage_account_with_activity_logs_is_private", + "monitor_storage_account_with_activity_logs_cmk_encrypted" + ] + }, + { + "Id": "CCC.Core.CN09.AR02", + "Description": "When the service is operational, disabling the logs for the service or its child resources MUST NOT be possible without also disabling the corresponding resource.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "No normal business operations should disable logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging mechanisms are tightly integrated with service operations, so that logging cannot be disabled without stopping the service itself.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07", + "CCC.Core.TH09", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-02", + "LOG-04", + "LOG-09" + ] + } + ] + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists", + "monitor_diagnostic_setting_with_appropriate_categories" + ] + }, + { + "Id": "CCC.Core.CN09.AR03", + "Description": "When the service is operational, any attempt to redirect logs for the service or its child resources MUST NOT be possible without halting operation of the corresponding resource and publishing corresponding events to monitored channels.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "No normal business operations should result in the redirection of logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging configurations are immutable during service operation so that any changes require stopping the service and publishing corresponding events to monitored channels.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07", + "CCC.Core.TH09", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-02", + "LOG-04", + "LOG-09" + ] + } + ] + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists" + ] + }, + { + "Id": "CCC.Core.CN10.AR01", + "Description": "When data is replicated, the service MUST ensure that replication only occurs to destinations that are explicitly included within the defined trust perimeter.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN10 Restrict Data Replication to Trust Perimeter", + "SubSection": "", + "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-10", + "DSP-19" + ] + } + ] + } + ], + "Checks": [ + "storage_cross_tenant_replication_disabled" + ] + }, + { + "Id": "CCC.Core.CN02.AR01", + "Description": "When data is stored, it MUST be encrypted using the latest industry-standard encryption methods.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN02 Encrypt Data for Storage", + "SubSection": "", + "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong encryption algorithms.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "UEM-08", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_infrastructure_encryption_is_enabled", + "storage_ensure_encryption_with_customer_managed_keys", + "vm_ensure_attached_disks_encrypted_with_cmk", + "vm_ensure_unattached_disks_encrypted_with_cmk", + "sqlserver_tde_encryption_enabled", + "sqlserver_tde_encrypted_with_cmk", + "databricks_workspace_cmk_encryption_enabled", + "monitor_storage_account_with_activity_logs_cmk_encrypted" + ] + }, + { + "Id": "CCC.Core.CN11.AR01", + "Description": "When encryption keys are used, the service MUST verify that all encryption keys use the latest industry-standard cryptographic algorithms.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "storage_smb_channel_encryption_with_secure_algorithm" + ] + }, + { + "Id": "CCC.Core.CN11.AR02", + "Description": "When encryption keys are used, the service MUST rotate active keys within 180 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "keyvault_key_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN11.AR03", + "Description": "When encrypting data, the service MUST verify that customer-managed encryption keys (CMEKs) are used.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "storage_ensure_encryption_with_customer_managed_keys", + "vm_ensure_attached_disks_encrypted_with_cmk", + "vm_ensure_unattached_disks_encrypted_with_cmk", + "sqlserver_tde_encrypted_with_cmk", + "databricks_workspace_cmk_encryption_enabled" + ] + }, + { + "Id": "CCC.Core.CN11.AR04", + "Description": "When encryption keys are accessed, the service MUST verify that access to encryption keys is restricted to authorized personnel and services, following the principle of least privilege.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "keyvault_rbac_enabled", + "keyvault_private_endpoints", + "keyvault_access_only_through_private_endpoints", + "keyvault_logging_enabled", + "keyvault_recoverable" + ] + }, + { + "Id": "CCC.Core.CN11.AR05", + "Description": "When encryption keys are used, the service MUST rotate active keys within 365 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-clear", + "tlp-green" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "keyvault_key_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN11.AR06", + "Description": "When encryption keys are used, the service MUST rotate active keys within 90 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "storage_key_rotation_90_days", + "keyvault_key_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN14.AR01", + "Description": "When backups are created for disaster recovery purposes, the storage mechanism MUST NOT allow modification or deletion within 30 days of creation.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN14 Maintain Recent Backups", + "SubSection": "", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Use immutable storage solutions where possible. Implement backup retention policies that enforce a minimum retention period of 30 days.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "vm_backup_enabled", + "vm_sufficient_daily_backup_retention_period" + ] + }, + { + "Id": "CCC.Core.CN14.AR02", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 30 days.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN14 Maintain Recent Backups", + "SubSection": "", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber" + ], + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 30 days.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "vm_backup_enabled", + "vm_sufficient_daily_backup_retention_period" + ] + }, + { + "Id": "CCC.Core.CN14.AR03", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 14 days.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN14 Maintain Recent Backups", + "SubSection": "", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 14 days.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "vm_backup_enabled", + "vm_sufficient_daily_backup_retention_period" + ] + }, + { + "Id": "CCC.Core.CN03.AR01", + "Description": "When an entity attempts to modify the service through a user interface, the authentication process MUST require multiple identifying factors for authentication.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "entra_privileged_user_has_mfa", + "entra_non_privileged_user_has_mfa", + "entra_user_with_vm_access_has_mfa", + "entra_security_defaults_enabled" + ] + }, + { + "Id": "CCC.Core.CN03.AR02", + "Description": "When an entity attempts to modify the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "entra_conditional_access_policy_require_mfa_for_management_api", + "app_function_access_keys_configured", + "app_function_identity_is_configured" + ] + }, + { + "Id": "CCC.Core.CN03.AR03", + "Description": "When an entity attempts to view information on the service through a user interface, the authentication process MUST require multiple identifying factors from the user.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "entra_privileged_user_has_mfa", + "entra_non_privileged_user_has_mfa", + "entra_user_with_vm_access_has_mfa", + "entra_security_defaults_enabled" + ] + }, + { + "Id": "CCC.Core.CN03.AR04", + "Description": "When an entity attempts to view information on the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "entra_conditional_access_policy_require_mfa_for_management_api", + "app_function_access_keys_configured" + ] + }, + { + "Id": "CCC.Core.CN05.AR01", + "Description": "When an attempt is made to modify data on the service or a child resource, the service MUST block requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "aks_cluster_rbac_enabled", + "aks_clusters_public_access_disabled", + "app_ensure_auth_is_set_up", + "app_register_with_identity", + "app_function_identity_is_configured", + "app_function_identity_without_admin_privileges", + "app_function_not_publicly_accessible", + "iam_role_user_access_admin_restricted", + "iam_subscription_roles_owner_custom_not_created", + "keyvault_rbac_enabled", + "keyvault_private_endpoints", + "keyvault_access_only_through_private_endpoints", + "entra_global_admin_in_less_than_five_users", + "entra_non_privileged_user_has_mfa", + "entra_privileged_user_has_mfa", + "vm_jit_access_enabled" + ] + }, + { + "Id": "CCC.Core.CN05.AR02", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST refuse requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "iam_role_user_access_admin_restricted", + "iam_subscription_roles_owner_custom_not_created", + "iam_custom_role_has_permissions_to_administer_resource_locks", + "entra_global_admin_in_less_than_five_users", + "entra_privileged_user_has_mfa", + "entra_non_privileged_user_has_mfa", + "entra_conditional_access_policy_require_mfa_for_management_api", + "aks_cluster_rbac_enabled", + "containerregistry_admin_user_disabled", + "keyvault_rbac_enabled" + ] + }, + { + "Id": "CCC.Core.CN05.AR03", + "Description": "When administrative access or configuration change is attempted on the service or a child resource in a multi-tenant environment, the service MUST refuse requests across tenant boundaries unless the origin is explicitly included in a pre-approved allowlist.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_cross_tenant_replication_disabled", + "storage_default_to_entra_authorization_enabled", + "entra_trusted_named_locations_exists" + ] + }, + { + "Id": "CCC.Core.CN05.AR04", + "Description": "When data is requested from outside the trust perimeter, the service MUST refuse requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_default_network_access_rule_is_denied", + "storage_ensure_private_endpoints_in_storage_accounts", + "storage_account_key_access_disabled", + "storage_default_to_entra_authorization_enabled", + "containerregistry_not_publicly_accessible", + "containerregistry_uses_private_link", + "keyvault_private_endpoints", + "keyvault_access_only_through_private_endpoints", + "cosmosdb_account_use_private_endpoints", + "cosmosdb_account_firewall_use_selected_networks", + "sqlserver_unrestricted_inbound_access", + "network_http_internet_access_restricted" + ] + }, + { + "Id": "CCC.Core.CN05.AR05", + "Description": "When any request is made from outside the trust perimeter, the service MUST NOT provide any response that may indicate the service exists.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "aks_clusters_created_with_private_nodes", + "aks_clusters_public_access_disabled", + "app_function_not_publicly_accessible", + "containerregistry_not_publicly_accessible", + "keyvault_private_endpoints", + "storage_ensure_private_endpoints_in_storage_accounts", + "network_http_internet_access_restricted", + "network_rdp_internet_access_restricted", + "network_ssh_internet_access_restricted" + ] + }, + { + "Id": "CCC.Core.CN05.AR06", + "Description": "When any request is made to the service or a child resource, the service MUST refuse requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "entra_privileged_user_has_mfa", + "entra_non_privileged_user_has_mfa", + "entra_global_admin_in_less_than_five_users", + "entra_conditional_access_policy_require_mfa_for_management_api", + "iam_role_user_access_admin_restricted", + "iam_subscription_roles_owner_custom_not_created", + "keyvault_rbac_enabled", + "vm_jit_access_enabled" + ] + }, + { + "Id": "CCC.Core.CN04.AR01", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN04 Log All Access and Changes", + "SubSection": "", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-08" + ] + } + ] + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists", + "monitor_diagnostic_setting_with_appropriate_categories", + "monitor_alert_create_update_nsg", + "monitor_alert_delete_nsg", + "monitor_alert_create_update_public_ip_address_rule", + "monitor_alert_delete_public_ip_address_rule", + "monitor_alert_create_update_security_solution", + "monitor_alert_delete_security_solution", + "monitor_alert_create_policy_assignment", + "monitor_alert_create_update_sqlserver_fr" + ] + }, + { + "Id": "CCC.Core.CN04.AR02", + "Description": "When any attempt is made to modify data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN04 Log All Access and Changes", + "SubSection": "", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-08" + ] + } + ] + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists", + "monitor_diagnostic_setting_with_appropriate_categories", + "keyvault_logging_enabled", + "app_http_logs_enabled" + ] + }, + { + "Id": "CCC.Core.CN04.AR03", + "Description": "When any attempt is made to read data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN04 Log All Access and Changes", + "SubSection": "", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-08" + ] + } + ] + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists", + "monitor_diagnostic_setting_with_appropriate_categories", + "keyvault_logging_enabled", + "app_http_logs_enabled" + ] + }, + { + "Id": "CCC.Core.CN07.AR01", + "Description": "When enumeration activities are detected, the service MUST publish an event to a monitored channel which includes the client identity, time, and nature of the activity.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement event publication mechanisms and alerts for patterns indicative of enumeration activities, such as repeated access attempts, requests, or liveness probes. Configure alerts to notify security teams of any activities that merit further investigation.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH15" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-05", + "SEF-05" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN07.AR02", + "Description": "When enumeration activities are detected, the service MUST log the client identity, time, and nature of the activity.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement logging mechanisms to capture details of enumeration activities, including client identity, timestamps, and activity nature. Retain logs according to organizational policies, and occasionally review them for patterns that may indicate reconnaissance activities.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH15" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-05", + "SEF-05" + ] + } + ] + } + ], + "Checks": [] + }, { "Id": "CCC.AuditLog.CN01.AR01", "Description": "When the signature validation process is performed, then it MUST detect any modification of data.", @@ -18,13 +1706,13 @@ "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure hash of data is included in digital signature. ", + "Recommendation": "Ensure hash of data is included in digital signature.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -59,13 +1747,13 @@ "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure verification process includes a chained hash function. ", + "Recommendation": "Ensure verification process includes a chained hash function.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -106,7 +1794,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06" + "CCC.Core.TH06" ] } ], @@ -130,9 +1818,7 @@ ], "Checks": [ "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted" + "monitor_diagnostic_setting_with_appropriate_categories" ] }, { @@ -149,12 +1835,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured ", + "Recommendation": "Ensure alerting is correctly configured", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -175,12 +1861,7 @@ ] } ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "keyvault_logging_enabled", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_storage_account_with_activity_logs_is_private" - ] + "Checks": [] }, { "Id": "CCC.AuditLog.CN03.AR02", @@ -196,12 +1877,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured ", + "Recommendation": "Ensure alerting is correctly configured", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -222,22 +1903,7 @@ ] } ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_alert_service_health_exists", - "monitor_alert_create_update_sqlserver_fr", - "monitor_alert_delete_nsg", - "monitor_alert_delete_public_ip_address_rule", - "monitor_alert_delete_security_solution", - "monitor_alert_create_update_nsg", - "monitor_alert_create_update_public_ip_address_rule", - "monitor_alert_create_update_security_solution", - "monitor_alert_create_policy_assignment", - "monitor_alert_delete_policy_assignment" - ] + "Checks": [] }, { "Id": "CCC.AuditLog.CN04.AR01", @@ -253,13 +1919,13 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure the audit log bucket to enable server access logging. Ensure the target logging bucket is configured for appropriate security, including restricted access and immutability. ", + "Recommendation": "Configure the audit log bucket to enable server access logging. Ensure the target logging bucket is configured for appropriate security, including restricted access and immutability.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01", - "CCC.TH09" + "CCC.Core.TH01", + "CCC.Core.TH09" ] } ], @@ -281,11 +1947,7 @@ } ], "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_storage_account_with_activity_logs_is_private", - "storage_blob_public_access_level_is_disabled" + "monitor_diagnostic_settings_exists" ] }, { @@ -302,12 +1964,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure audit log exporting. ", + "Recommendation": "Configure audit log exporting.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -331,9 +1993,7 @@ ], "Checks": [ "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted" + "monitor_diagnostic_setting_with_appropriate_categories" ] }, { @@ -351,13 +2011,13 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce the required data retention period. ", + "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce the required data retention period.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -378,10 +2038,7 @@ ] } ], - "Checks": [ - "storage_ensure_soft_delete_is_enabled", - "monitor_diagnostic_settings_exists" - ] + "Checks": [] }, { "Id": "CCC.AuditLog.CN07.AR01", @@ -398,13 +2055,13 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations) on the audit log bucket. ", + "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations) on the audit log bucket.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -441,12 +2098,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy. ", + "Recommendation": "Configure object lock policy.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -467,12 +2124,7 @@ ] } ], - "Checks": [ - "storage_ensure_soft_delete_is_enabled", - "monitor_diagnostic_settings_exists", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_storage_account_with_activity_logs_is_private" - ] + "Checks": [] }, { "Id": "CCC.AuditLog.CN09.AR01", @@ -488,12 +2140,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Review field level access controls on audit data. ", + "Recommendation": "Review field level access controls on audit data.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -519,9 +2171,7 @@ } ], "Checks": [ - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_diagnostic_settings_exists" + "monitor_storage_account_with_activity_logs_is_private" ] }, { @@ -539,12 +2189,12 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -568,7 +2218,6 @@ "Checks": [ "storage_blob_public_access_level_is_disabled", "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted", "storage_ensure_private_endpoints_in_storage_accounts" ] }, @@ -587,12 +2236,12 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -616,944 +2265,16 @@ "Checks": [ "storage_blob_public_access_level_is_disabled", "storage_default_network_access_rule_is_denied", - "storage_ensure_private_endpoints_in_storage_accounts", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_storage_account_with_activity_logs_is_private" + "storage_ensure_private_endpoints_in_storage_accounts" ] }, - { - "Id": "CCC.Build.CN01.AR01", - "Description": "Attempt to initiate a build using an unauthorized build agent and verify that the build is rejected.", - "Attributes": [ - { - "FamilyName": "Access Control", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.CN01 Restrict Allowed Build Agents", - "SubSection": "", - "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain control over the build environment and prevent unauthorized code execution.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.Build.CN02.AR01", - "Description": "Attempt to trigger a build from an unauthorized external service or repository and verify that the build does not start.", - "Attributes": [ - { - "FamilyName": "Access Control", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.CN02 Restrict Allowed External Services for Build Triggers", - "SubSection": "", - "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or repositories to prevent unauthorized code execution or tampering.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.Build.CN03.AR01", - "Description": "Attempt to access the build environment from an external network and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.Build.CN03 Deny External Network Access for Build Environments", - "SubSection": "", - "SubSectionObjective": "Ensure that build environments do not have external network access to prevent unauthorized external access and data exfiltration.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02", - "CCC.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7", - "SC-5" - ] - } - ] - } - ], - "Checks": [ - "aks_clusters_public_access_disabled", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "app_function_not_publicly_accessible", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted" - ] - }, - { - "Id": "CCC.CntrReg.CN01.AR01", - "Description": "Attempt to push an artifact with known vulnerabilities to the registry and observe if it is flagged or rejected by the vulnerability scanning process.", - "Attributes": [ - { - "FamilyName": "Risk Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CntrReg.CN01 Implement Vulnerability Scanning for Artifacts", - "SubSection": "", - "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for vulnerabilities to identify and remediate security issues before deployment.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.CntrReg.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "ID.RA-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "RA-5", - "SI-5" - ] - } - ] - } - ], - "Checks": [ - "defender_container_images_scan_enabled", - "defender_container_images_resolved_vulnerabilities" - ] - }, - { - "Id": "CCC.DataWar.CN01.AR01", - "Description": "Attempt to access underlying database tables directly without using managed views and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.CN01 Enforce Use of Managed Views for Data Access", - "SubSection": "", - "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users from accessing underlying tables directly and enforcing consistent security policies.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.DataWar.CN02.AR01", - "Description": "Attempt to query sensitive columns without the necessary permissions and verify that access is denied or data is masked.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.CN02 Enforce Column-Level Security Policies", - "SubSection": "", - "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles, preventing unauthorized access to sensitive information.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.DataWar.CN03.AR01", - "Description": "Attempt to query data rows that the user should not have access to and verify that access is denied or data is not returned.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.CN03 Enforce Row-Level Security Policies", - "SubSection": "", - "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes, preventing unauthorized access to specific subsets of data.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - } - ] - } - ], - "Checks": [ - "cosmosdb_account_use_aad_and_rbac", - "cosmosdb_account_firewall_use_selected_networks", - "cosmosdb_account_use_private_endpoints", - "sqlserver_unrestricted_inbound_access", - "postgresql_flexible_server_allow_access_services_disabled" - ] - }, - { - "Id": "CCC.KeyMgmt.CN01.AR01", - "Description": "When a key version is scheduled for deletion or disabled, an alert MUST be generated within five minutes.", - "Attributes": [ - { - "FamilyName": "Logging and Metrics Publication", - "FamilyDescription": "Controls that collect, alert, and retain key-management events.", - "Section": "CCC.KeyMgmt.CN01 Alert on Key-version Changes", - "SubSection": "", - "SubSectionObjective": "Generate near-real-time alerts when a KMS key version is disabled or scheduled for deletion, enabling rapid investigation and recovery.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Use native event services (e.g., CloudWatch Events, Azure Monitor, Cloud Audit Logs) to route notifications to an incident-response channel.", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.KeyMgmt.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "RS.AN-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IR-5" - ] - } - ] - } - ], - "Checks": [ - "keyvault_logging_enabled", - "monitor_diagnostic_settings_exists", - "monitor_alert_create_update_nsg", - "monitor_alert_create_update_public_ip_address_rule", - "monitor_alert_create_update_security_solution", - "monitor_alert_create_policy_assignment", - "monitor_alert_service_health_exists" - ] - }, - { - "Id": "CCC.KeyMgmt.CN02.AR01", - "Description": "When IAM roles and key policies are reviewed, Decrypt permission MUST be granted exclusively to documented authorised principals.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that enforce least-privilege use of KMS operations.", - "Section": "CCC.KeyMgmt.CN02 Limit Decrypt Permissions", - "SubSection": "", - "SubSectionObjective": "Restrict the Decrypt operation to authorised principals only, applying the principle of least privilege to protect sensitive data.", - "Applicability": [ - "tlp-green" - ], - "Recommendation": "Periodically audit policy documents via automated tooling and report any deviations.", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.KeyMgmt.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6" - ] - } - ] - } - ], - "Checks": [ - "keyvault_rbac_enabled", - "keyvault_rbac_key_expiration_set", - "keyvault_rbac_secret_expiration_set", - "keyvault_key_expiration_set_in_non_rbac" - ] - }, - { - "Id": "CCC.KeyMgmt.CN03.AR01", - "Description": "When rotation settings are examined, rotation MUST be enabled with an interval not exceeding 365 days.", - "Attributes": [ - { - "FamilyName": "Key Lifecycle Management", - "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", - "Section": "CCC.KeyMgmt.CN03 Enforce Automatic Rotation", - "SubSection": "", - "SubSectionObjective": "Ensure symmetric keys rotate automatically within policy intervals to reduce exposure of key material.", - "Applicability": [ - "tlp-green" - ], - "Recommendation": "Use cloud-provider rotation features and verify via configuration scanning.", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.KeyMgmt.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12" - ] - } - ] - } - ], - "Checks": [ - "keyvault_key_rotation_enabled" - ] - }, - { - "Id": "CCC.KeyMgmt.CN04.AR01", - "Description": "When a key import request is processed, the key MUST use an approved algorithm (RSA-2048+, EC-P256+) and originate from a certified HSM.", - "Attributes": [ - { - "FamilyName": "Key Lifecycle Management", - "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", - "Section": "CCC.KeyMgmt.CN04 Validate Imported Keys", - "SubSection": "", - "SubSectionObjective": "Accept only externally generated keys that meet approved cryptographic strength and provenance requirements.", - "Applicability": [ - "tlp-green" - ], - "Recommendation": "Implement an approval workflow that validates attestation data before import.", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.KeyMgmt.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "keyvault_key_rotation_enabled", - "keyvault_rbac_enabled", - "keyvault_key_expiration_set_in_non_rbac", - "keyvault_rbac_key_expiration_set", - "keyvault_rbac_secret_expiration_set", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "keyvault_recoverable", - "keyvault_logging_enabled", - "keyvault_non_rbac_secret_expiration_set" - ] - }, - { - "Id": "CCC.LB.CN01.AR01", - "Description": "When a single client sends more than 2000 requests within any 5-minute sliding window, the load balancer MUST throttle all subsequent requests from that client for at least 60 seconds.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", - "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", - "SubSection": "", - "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement per-IP token-bucket limits with and verify via synthetic traffic tests. ", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH01", - "CCC.LB.TH09" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-1", - "PR.AC-7", - "PR.PT-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-6", - "SC-5", - "AC-7" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.LB.CN01.AR02", - "Description": "When throttling is invoked, the load balancer MUST record the event in the access log within 5 minutes for alerting and trend analysis.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", - "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", - "SubSection": "", - "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Enable access logging and configure metric filters on HTTP 429 counts to trigger alerts. ", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH01", - "CCC.LB.TH09" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-1", - "PR.AC-7", - "PR.PT-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-6", - "SC-5", - "AC-7" - ] - } - ] - } - ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "network_flow_log_captured_sent", - "network_watcher_enabled", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted" - ] - }, - { - "Id": "CCC.LB.CN06.AR01", - "Description": "When more than 10 percent of targets change from healthy to unhealthy within five minutes, an alert MUST be issued.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", - "Section": "CCC.LB.CN06 Secure Health-Check Telemetry", - "SubSection": "", - "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on abnormal status changes.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Instrument metrics for health check results and target removal events. Configure monitoring alarms to alert on abnormal spikes in unhealthy targets. ", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-4" - ] - } - ] - } - ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "monitor_alert_service_health_exists", - "monitor_alert_create_update_nsg", - "monitor_alert_create_update_public_ip_address_rule", - "monitor_alert_create_update_security_solution", - "monitor_alert_create_policy_assignment", - "network_flow_log_captured_sent", - "network_watcher_enabled", - "vm_scaleset_associated_with_load_balancer" - ] - }, - { - "Id": "CCC.LB.CN04.AR01", - "Description": "When routing weights change, the request MUST originate from an explicitly defined and trusted identity and MUST be logged.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", - "Section": "CCC.LB.CN04 Enforce Distribution Policies", - "SubSection": "", - "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified only by trusted identities.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Define a list of trusted principals allowed to modify routing configurations. Enforce via conditional access policies, and log changes using audit logging. ", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_alert_create_update_public_ip_address_rule", - "monitor_alert_create_update_nsg", - "monitor_alert_create_policy_assignment", - "monitor_alert_create_update_security_solution", - "entra_global_admin_in_less_than_five_users", - "entra_non_privileged_user_has_mfa", - "iam_role_user_access_admin_restricted", - "iam_custom_role_has_permissions_to_administer_resource_locks" - ] - }, - { - "Id": "CCC.LB.CN05.AR01", - "Description": "When stickiness is enabled, session cookies MUST expire within 30 minutes of inactivity.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", - "Section": "CCC.LB.CN05 Validate Session Affinity", - "SubSection": "", - "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking risks.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Audit CCC.LB.F15 parameters via configuration scans.", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-23" - ] - } - ] - } - ], - "Checks": [ - "iam_role_user_access_admin_restricted", - "iam_subscription_roles_owner_custom_not_created", - "iam_custom_role_has_permissions_to_administer_resource_locks" - ] - }, - { - "Id": "CCC.LB.CN09.AR01", - "Description": "When an API call originates outside the approved CIDR set, the request MUST be denied.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", - "Section": "CCC.LB.CN09 Restrict Management API Access", - "SubSection": "", - "SubSectionObjective": "Limit load-balancer API calls to authorised identities and trusted networks.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Combine VPC endpoints with IAM condition-key filters for protected APIs.", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH08" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "entra_conditional_access_policy_require_mfa_for_management_api", - "entra_global_admin_in_less_than_five_users", - "entra_privileged_user_has_mfa", - "iam_role_user_access_admin_restricted", - "iam_custom_role_has_permissions_to_administer_resource_locks" - ] - }, - { - "Id": "CCC.LB.CN02.AR01", - "Description": "When concurrent connections reach 80 percent of capacity, the autoscaling group MUST add at least one instance within five minutes.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", - "Section": "CCC.LB.CN02 Auto-Scale Load Balancer Capacity", - "SubSection": "", - "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic spikes.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Enable autoscaling policies.", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH09" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "ID.BE-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CP-10" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.LB.CN07.AR01", - "Description": "When responses pass through the load balancer, the \"Server\" header MUST be replaced with \"lb\".", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", - "Section": "CCC.LB.CN07 Scrub Sensitive Headers", - "SubSection": "", - "SubSectionObjective": "Remove headers that disclose internal details or software versions from HTTP responses.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Configure header-transformation rules.", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.TH15" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-13" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.LB.CN08.AR01", - "Description": "When a certificate is within 30 days of expiry, automated renewal MUST complete and deploy a new certificate within 24 hours.", - "Attributes": [ - { - "FamilyName": "Encryption", - "FamilyDescription": "Controls that ensure trustworthy TLS certificates and ciphers.", - "Section": "CCC.LB.CN08 Automate Certificate Renewal", - "SubSection": "", - "SubSectionObjective": "Maintain valid TLS certificates by automating renewal and deployment before expiry.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Use certificate-manager auto-renewal workflows.", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH07" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-6" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-17" - ] - } - ] - } - ], - "Checks": [] - }, { "Id": "CCC.Logging.CN01.AR01", "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be enabled by default and directed to the central sink.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", "SubSection": "", "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", @@ -1591,10 +2312,7 @@ ], "Checks": [ "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "network_flow_log_captured_sent", - "app_http_logs_enabled", - "appinsights_ensure_is_configured" + "network_flow_log_captured_sent" ] }, { @@ -1603,7 +2321,7 @@ "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", "SubSection": "", "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", @@ -1643,8 +2361,7 @@ "monitor_diagnostic_settings_exists", "monitor_diagnostic_setting_with_appropriate_categories", "app_http_logs_enabled", - "keyvault_logging_enabled", - "network_flow_log_captured_sent" + "keyvault_logging_enabled" ] }, { @@ -1653,52 +2370,7 @@ "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", - "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "GV.PO-01" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-11" - ] - } - ] - } - ], - "Checks": [ - "network_flow_log_more_than_90_days" - ] - }, - { - "Id": "CCC.Logging.CN02.AR02", - "Description": "When a query is performed to retrieve log events older than the number of days defined in the organisation's data retention policy, it MUST return an empty result.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", "SubSection": "", "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", @@ -1740,12 +2412,59 @@ ] }, { - "Id": "CCC.AuditLog.CN08.AR01", + "Id": "CCC.Logging.CN02.AR02", + "Description": "When a query is performed to retrieve log events older than the number of days defined in the organisation's data retention policy, it MUST return an empty result.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "GV.PO-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-11" + ] + } + ] + } + ], + "Checks": [ + "network_flow_log_more_than_90_days", + "sqlserver_auditing_retention_90_days", + "postgresql_flexible_server_log_retention_days_greater_3" + ] + }, + { + "Id": "CCC.Logging.CN03.AR01", "Description": "When an attempt is made to modify or delete data before the object lock period expires, then the action MUST be denied.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", "Section": "CCC.Logging.CN03 Enable Object Lock On Log Bucket", "SubSection": "", "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection using object lock on log storage buckets. This prevents logs from being modified or deleted during the defined retention period, supporting compliance and forensic integrity.", @@ -1753,12 +2472,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy. ", + "Recommendation": "Configure object lock policy.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -1782,12 +2501,12 @@ "Checks": [] }, { - "Id": "CCC.AuditLog.CN04.AR01", + "Id": "CCC.Logging.CN04.AR01", "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "FamilyDescription": "Controls that restrict who can access and modify logs.", "Section": "CCC.Logging.CN04 Restrict Field And Log Type Access", "SubSection": "", "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", @@ -1795,7 +2514,7 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Review field level access controls on log data. ", + "Recommendation": "Review field level access controls on log data.", "SectionThreatMappings": [ { "ReferenceId": "CCC", @@ -1826,10 +2545,7 @@ } ], "Checks": [ - "monitor_diagnostic_settings_exists", - "keyvault_logging_enabled", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted" + "monitor_storage_account_with_activity_logs_is_private" ] }, { @@ -1838,7 +2554,7 @@ "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "FamilyDescription": "Controls that restrict who can access and modify logs.", "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", "SubSection": "", "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", @@ -1847,12 +2563,12 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -1875,8 +2591,6 @@ ], "Checks": [ "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_diagnostic_settings_exists", "storage_blob_public_access_level_is_disabled" ] }, @@ -1886,7 +2600,7 @@ "Attributes": [ { "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs. ", + "FamilyDescription": "Controls that restrict who can access and modify logs.", "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", "SubSection": "", "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", @@ -1895,12 +2609,12 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -1923,10 +2637,7 @@ ], "Checks": [ "storage_blob_public_access_level_is_disabled", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_diagnostic_settings_exists", - "storage_geo_redundant_enabled" + "monitor_storage_account_with_activity_logs_is_private" ] }, { @@ -1935,7 +2646,7 @@ "Attributes": [ { "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events.", "Section": "CCC.Logging.CN06 Detect and Alert on Potential Log Exfiltration", "SubSection": "", "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt to exfiltrate log data.", @@ -1972,9 +2683,7 @@ ] } ], - "Checks": [ - "apim_threat_detection_llm_jacking" - ] + "Checks": [] }, { "Id": "CCC.Logging.CN07.AR01", @@ -1982,7 +2691,7 @@ "Attributes": [ { "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events.", "Section": "CCC.Logging.CN07 Detect and Alert on Log Service Tampering", "SubSection": "", "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified, or deleted, indicating a defense evasion attempt.", @@ -1997,7 +2706,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" + "CCC.Core.TH16" ] } ], @@ -2020,921 +2729,14 @@ ] } ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_alert_create_update_nsg", - "monitor_alert_delete_nsg", - "monitor_alert_create_update_public_ip_address_rule", - "monitor_alert_delete_public_ip_address_rule", - "monitor_alert_create_update_security_solution", - "monitor_alert_delete_security_solution", - "monitor_alert_create_policy_assignment", - "monitor_alert_delete_policy_assignment", - "monitor_alert_service_health_exists", - "monitor_alert_create_update_sqlserver_fr", - "monitor_alert_delete_sqlserver_fr" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR01", - "Description": "When a request is made to read a protected bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "storage_ensure_encryption_with_customer_managed_keys", - "databricks_workspace_cmk_encryption_enabled", - "vm_ensure_attached_disks_encrypted_with_cmk", - "vm_ensure_unattached_disks_encrypted_with_cmk" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR02", - "Description": "When a request is made to read a protected object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "storage_ensure_encryption_with_customer_managed_keys", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "keyvault_rbac_enabled", - "keyvault_private_endpoints", - "keyvault_rbac_key_expiration_set", - "keyvault_rbac_secret_expiration_set" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR03", - "Description": "When a request is made to write to a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "storage_ensure_encryption_with_customer_managed_keys", - "storage_ensure_private_endpoints_in_storage_accounts", - "keyvault_rbac_enabled", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR04", - "Description": "When a request is made to write to an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "storage_ensure_encryption_with_customer_managed_keys", - "databricks_workspace_cmk_encryption_enabled", - "monitor_storage_account_with_activity_logs_cmk_encrypted" - ] - }, - { - "Id": "CCC.ObjStor.CN03.AR01", - "Description": "When an object storage bucket deletion is attempted, the bucket MUST be fully recoverable for a set time-frame after deletion is requested.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", - "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "storage_blob_versioning_is_enabled", - "storage_ensure_soft_delete_is_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN03.AR02", - "Description": "When an attempt is made to modify the retention policy for an object storage bucket, the service MUST prevent the policy from being modified.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", - "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], "Checks": [] }, - { - "Id": "CCC.ObjStor.CN04.AR01", - "Description": "When an object is uploaded to the object storage system, the object MUST automatically receive a default retention policy that prevents premature deletion or modification.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN04 Log All Access and Changes", - "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", - "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "storage_blob_versioning_is_enabled", - "storage_ensure_soft_delete_is_enabled", - "storage_ensure_file_shares_soft_delete_is_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN04.AR02", - "Description": "When an attempt is made to delete or modify an object that is subject to an active retention policy, the service MUST prevent the action from being completed.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN04 Log All Access and Changes", - "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", - "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "storage_ensure_soft_delete_is_enabled", - "storage_ensure_file_shares_soft_delete_is_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN05.AR01", - "Description": "When an object is uploaded to the object storage bucket, the object MUST be stored with a unique identifier.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [ - "storage_blob_versioning_is_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN05.AR02", - "Description": "When an object is modified, the service MUST assign a new unique identifier to the modified object to differentiate it from the previous version.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [ - "storage_blob_versioning_is_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN05.AR03", - "Description": "When an object is modified, the service MUST allow for recovery of previous versions of the object.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [ - "storage_blob_versioning_is_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN05.AR04", - "Description": "When an object is deleted, the service MUST retain other versions of the object to allow for recovery of previous versions.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [ - "storage_blob_versioning_is_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN06.AR01", - "Description": "When an object storage bucket is accessed, the service MUST store access logs in a separate data store.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN06 Prevent Deployment in Restricted Regions", - "SubSection": "CCC.ObjStor.C06 Access Logs are Stored in a Separate Data Store", - "SubSectionObjective": "Ensure that access logs for object storage buckets are stored in a separate data store to protect against unauthorized access, tampering, or deletion of logs (Logbuckets are exempt from this requirement, but must be tlp-red).", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH07", - "CCC.TH09" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-07", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.15.0" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-9", - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted" - ] - }, - { - "Id": "CCC.ObjStor.CN02.AR01", - "Description": "When a permission set is allowed for an object in a bucket, the service MUST allow the same permission set to access all objects in the same bucket.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", - "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", - "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-09" - ] - } - ] - } - ], - "Checks": [ - "storage_blob_public_access_level_is_disabled", - "storage_default_network_access_rule_is_denied", - "storage_ensure_private_endpoints_in_storage_accounts" - ] - }, - { - "Id": "CCC.ObjStor.CN02.AR02", - "Description": "When a permission set is denied for an object in a bucket, the service MUST deny the same permission set to access all objects in the same bucket.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", - "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", - "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-09" - ] - } - ] - } - ], - "Checks": [ - "storage_blob_public_access_level_is_disabled", - "storage_account_key_access_disabled", - "storage_default_to_entra_authorization_enabled", - "storage_ensure_private_endpoints_in_storage_accounts" - ] - }, { "Id": "CCC.Monitor.CN01.AR01", "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then Rate Limiting MUST be applied and an Audit Alert MUST be generated.", "Attributes": [ { - "FamilyName": "Logging & Monitoring", + "FamilyName": "Logging and Monitoring", "FamilyDescription": "Controls that collect, alert, and retain events from other monitoring services.", "Section": "CCC.Monitor.CN01 Rate Limiting on External Monitoring", "SubSection": "", @@ -2972,25 +2774,14 @@ ] } ], - "Checks": [ - "monitor_alert_create_update_sqlserver_fr", - "monitor_alert_delete_nsg", - "monitor_alert_delete_public_ip_address_rule", - "monitor_alert_delete_security_solution", - "monitor_alert_create_policy_assignment", - "monitor_alert_create_update_public_ip_address_rule", - "monitor_alert_create_update_security_solution", - "monitor_alert_create_update_nsg", - "monitor_alert_service_health_exists", - "monitor_diagnostic_settings_exists" - ] + "Checks": [] }, { "Id": "CCC.Monitor.CN02.AR01", "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied to reduce the network impact of traffic and an alert must triggered.", "Attributes": [ { - "FamilyName": "Logging & Monitoring", + "FamilyName": "Logging and Monitoring", "FamilyDescription": "Controls that collect, alert, and retain events from other monitoring services.", "Section": "CCC.Monitor.CN02 Rate Limiting on Metric Generation", "SubSection": "", @@ -3028,9 +2819,7 @@ ] } ], - "Checks": [ - "apim_threat_detection_llm_jacking" - ] + "Checks": [] }, { "Id": "CCC.Monitor.CN03.AR01", @@ -3075,10 +2864,7 @@ ] } ], - "Checks": [ - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted" - ] + "Checks": [] }, { "Id": "CCC.Monitor.CN04.AR01", @@ -3146,7 +2932,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH10" + "CCC.Core.TH10" ] } ], @@ -3212,11 +2998,1111 @@ ], "Checks": [ "app_function_identity_is_configured", - "app_function_identity_without_admin_privileges", - "app_function_access_keys_configured", - "app_function_not_publicly_accessible", - "app_register_with_identity", - "app_ensure_auth_is_set_up" + "app_function_access_keys_configured" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR01", + "Description": "When a request is made to read a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_ensure_encryption_with_customer_managed_keys", + "keyvault_rbac_enabled", + "keyvault_private_endpoints" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR02", + "Description": "When a request is made to read an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_ensure_encryption_with_customer_managed_keys", + "keyvault_rbac_enabled", + "keyvault_private_endpoints" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR03", + "Description": "When a request is made to write to a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_ensure_encryption_with_customer_managed_keys", + "keyvault_rbac_enabled", + "keyvault_private_endpoints" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR04", + "Description": "When a request is made to write to an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_ensure_encryption_with_customer_managed_keys", + "keyvault_rbac_enabled", + "keyvault_private_endpoints" + ] + }, + { + "Id": "CCC.ObjStor.CN03.AR01", + "Description": "When an object storage bucket deletion is attempted, the bucket MUST be fully recoverable for a set time-frame after deletion is requested.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", + "SubSection": "", + "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_ensure_soft_delete_is_enabled", + "storage_blob_versioning_is_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN03.AR02", + "Description": "When an attempt is made to modify the retention policy for an object storage bucket, the service MUST prevent the policy from being modified.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", + "SubSection": "", + "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_ensure_soft_delete_is_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN04.AR01", + "Description": "When an object is uploaded to the object storage system, the object MUST automatically receive a default retention policy that prevents premature deletion or modification.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN04 Objects have an Effective Retention Policy by Default", + "SubSection": "", + "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + }, + { + "ReferenceId": "CCC.ObjStor", + "Identifiers": [ + "CCC.ObjStor.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_ensure_soft_delete_is_enabled", + "storage_ensure_file_shares_soft_delete_is_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN04.AR02", + "Description": "When an attempt is made to delete or modify an object that is subject to an active retention policy, the service MUST prevent the action from being completed.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN04 Objects have an Effective Retention Policy by Default", + "SubSection": "", + "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + }, + { + "ReferenceId": "CCC.ObjStor", + "Identifiers": [ + "CCC.ObjStor.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_ensure_soft_delete_is_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR01", + "Description": "When an object is uploaded to the object storage bucket, the object MUST be stored with a unique identifier.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_blob_versioning_is_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR02", + "Description": "When an object is modified, the service MUST assign a new unique identifier to the modified object to differentiate it from the previous version.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_blob_versioning_is_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR03", + "Description": "When an object is modified, the service MUST allow for recovery of previous versions of the object.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_blob_versioning_is_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR04", + "Description": "When an object is deleted, the service MUST retain other versions of the object to allow for recovery of previous versions.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "storage_blob_versioning_is_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN07.AR01", + "Description": "The object storage service MUST support a configuration option that requires MFA to be successfully completed before any object deletion can be attempted, regardless of the request interface.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN07 Multi-Factor Authentication Is Required for Object Deletion", + "SubSection": "", + "SubSectionObjective": "Ensure that deletion of objects stored in the object storage system is protected by multi-factor authentication (MFA), reducing the risk of accidental, unauthorized, or compromised-credential–based data destruction.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06", + "CCC.Core.TH17" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "IAM-12" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.ObjStor.CN07.AR02", + "Description": "When MFA deletion protection is enabled on a bucket or object namespace, the service MUST deny any deletion request from an identity that has not satisfied the MFA requirement at the time of the request.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN07 Multi-Factor Authentication Is Required for Object Deletion", + "SubSection": "", + "SubSectionObjective": "Ensure that deletion of objects stored in the object storage system is protected by multi-factor authentication (MFA), reducing the risk of accidental, unauthorized, or compromised-credential–based data destruction.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06", + "CCC.Core.TH17" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "IAM-12" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.ObjStor.CN07.AR03", + "Description": "When an attempt is made to delete an object, the service's audit logs MUST clearly record each deletion attempt, including whether MFA was required and whether validation was met.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN07 Multi-Factor Authentication Is Required for Object Deletion", + "SubSection": "", + "SubSectionObjective": "Ensure that deletion of objects stored in the object storage system is protected by multi-factor authentication (MFA), reducing the risk of accidental, unauthorized, or compromised-credential–based data destruction.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06", + "CCC.Core.TH17" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "IAM-12" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.ObjStor.CN02.AR01", + "Description": "When a permission set is allowed for an object in a bucket, the service MUST allow the same permission set to access all objects in the same bucket.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources.", + "Section": "CCC.ObjStor.CN02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", + "SubSection": "", + "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08" + ] + } + ] + } + ], + "Checks": [ + "storage_account_key_access_disabled", + "storage_default_to_entra_authorization_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN02.AR02", + "Description": "When a permission set is denied for an object in a bucket, the service MUST deny the same permission set to access all objects in the same bucket.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources.", + "Section": "CCC.ObjStor.CN02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", + "SubSection": "", + "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08" + ] + } + ] + } + ], + "Checks": [ + "storage_account_key_access_disabled", + "storage_default_to_entra_authorization_enabled" + ] + }, + { + "Id": "CCC.LB.CN01.AR01", + "Description": "When a single client sends more than 2000 requests within any 5-minute sliding window, the load balancer MUST throttle all subsequent requests from that client for at least 60 seconds.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.", + "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", + "SubSection": "", + "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement per-IP token-bucket limits with and verify via synthetic traffic tests.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH01", + "CCC.LB.TH09" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-1", + "PR.AC-7", + "PR.PT-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-6", + "SC-5", + "AC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.LB.CN01.AR02", + "Description": "When throttling is invoked, the load balancer MUST record the event in the access log within 5 minutes for alerting and trend analysis.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.", + "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", + "SubSection": "", + "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Enable access logging and configure metric filters on HTTP 429 counts to trigger alerts.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH01", + "CCC.LB.TH09" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-1", + "PR.AC-7", + "PR.PT-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-6", + "SC-5", + "AC-7" + ] + } + ] + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists" + ] + }, + { + "Id": "CCC.LB.CN06.AR01", + "Description": "When more than 10 percent of targets change from healthy to unhealthy within five minutes, an alert MUST be issued.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.", + "Section": "CCC.LB.CN06 Secure Health-Check Telemetry", + "SubSection": "", + "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on abnormal status changes.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Instrument metrics for health check results and target removal events. Configure monitoring alarms to alert on abnormal spikes in unhealthy targets.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.AE-2" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4" + ] + } + ] + } + ], + "Checks": [ + "monitor_alert_service_health_exists" + ] + }, + { + "Id": "CCC.LB.CN04.AR01", + "Description": "When routing weights change, the request MUST originate from an explicitly defined and trusted identity and MUST be logged.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.", + "Section": "CCC.LB.CN04 Enforce Distribution Policies", + "SubSection": "", + "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified only by trusted identities.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Define a list of trusted principals allowed to modify routing configurations. Enforce via conditional access policies, and log changes using audit logging.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3" + ] + } + ] + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists" + ] + }, + { + "Id": "CCC.LB.CN05.AR01", + "Description": "When stickiness is enabled, session cookies MUST expire within 30 minutes of inactivity.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.", + "Section": "CCC.LB.CN05 Validate Session Affinity", + "SubSection": "", + "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking risks.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Audit CCC.LB.CP15 parameters via configuration scans.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-7" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-23" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.LB.CN09.AR01", + "Description": "When an API call originates outside the approved CIDR set, the request MUST be denied.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.", + "Section": "CCC.LB.CN09 Restrict Management API Access", + "SubSection": "", + "SubSectionObjective": "Limit load-balancer API calls to authorised identities and trusted networks.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Combine VPC endpoints with IAM condition-key filters for protected APIs.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH08" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-5" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7" + ] + } + ] + } + ], + "Checks": [ + "entra_conditional_access_policy_require_mfa_for_management_api" + ] + }, + { + "Id": "CCC.LB.CN02.AR01", + "Description": "When concurrent connections reach 80 percent of capacity, the autoscaling group MUST add at least one instance within five minutes.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer.", + "Section": "CCC.LB.CN02 Auto-Scale Load Balancer Capacity", + "SubSection": "", + "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic spikes.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Enable autoscaling policies.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH09" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "ID.BE-5" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "CP-10" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.LB.CN07.AR01", + "Description": "When responses pass through the load balancer, the \"Server\" header MUST be replaced with \"lb\".", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer.", + "Section": "CCC.LB.CN07 Scrub Sensitive Headers", + "SubSection": "", + "SubSectionObjective": "Remove headers that disclose internal details or software versions from HTTP responses.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Configure header-transformation rules.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.Core.TH15" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-2" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-13" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.LB.CN08.AR01", + "Description": "When a certificate is within 30 days of expiry, automated renewal MUST complete and deploy a new certificate within 24 hours.", + "Attributes": [ + { + "FamilyName": "Encryption", + "FamilyDescription": "Controls that ensure trustworthy TLS certificates and ciphers.", + "Section": "CCC.LB.CN08 Automate Certificate Renewal", + "SubSection": "", + "SubSectionObjective": "Maintain valid TLS certificates by automating renewal and deployment before expiry.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Use certificate-manager auto-renewal workflows.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH07" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-6" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-17" + ] + } + ] + } + ], + "Checks": [ + "keyvault_key_rotation_enabled" ] }, { @@ -3272,6 +4158,58 @@ ], "Checks": [] }, + { + "Id": "CCC.VPC.CN02.AR01", + "Description": "When a resource is created in a public subnet, that resource MUST NOT be assigned an external IP address by default.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.VPC.CN02 Limit Resource Creation in Public Subnet", + "SubSection": "", + "SubSectionObjective": "Restrict the creation of resources in the public subnet with direct access to the internet to minimize attack surfaces.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.VPC.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "SEF-05" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.1.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-4" + ] + } + ] + } + ], + "Checks": [] + }, { "Id": "CCC.VPC.CN03.AR01", "Description": "When a VPC peering connection is requested, the service MUST prevent connections from VPCs that are not explicitly allowed.", @@ -3379,10 +4317,393 @@ ], "Checks": [ "network_flow_log_captured_sent", - "network_flow_log_more_than_90_days", "network_watcher_enabled" ] }, + { + "Id": "CCC.KeyMgmt.CN01.AR01", + "Description": "When a key version is scheduled for deletion or disabled, an alert MUST be generated within five minutes.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain key-management events.", + "Section": "CCC.KeyMgmt.CN01 Alert on Key-version Changes", + "SubSection": "", + "SubSectionObjective": "Generate near-real-time alerts when a KMS key version is disabled or scheduled for deletion, enabling rapid investigation and recovery.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Use native event services (e.g., CloudWatch Events, Azure Monitor, Cloud Audit Logs) to route notifications to an incident-response channel.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.KeyMgmt.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "RS.AN-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "IR-5" + ] + } + ] + } + ], + "Checks": [ + "keyvault_logging_enabled", + "keyvault_recoverable" + ] + }, + { + "Id": "CCC.KeyMgmt.CN02.AR01", + "Description": "When IAM roles and key policies are reviewed, Decrypt permission MUST be granted exclusively to documented authorised principals.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that enforce least-privilege use of KMS operations.", + "Section": "CCC.KeyMgmt.CN02 Limit Decrypt Permissions", + "SubSection": "", + "SubSectionObjective": "Restrict the Decrypt operation to authorised principals only, applying the principle of least privilege to protect sensitive data.", + "Applicability": [ + "tlp-green" + ], + "Recommendation": "Periodically audit policy documents via automated tooling and report any deviations.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.KeyMgmt.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "keyvault_rbac_enabled", + "keyvault_access_only_through_private_endpoints" + ] + }, + { + "Id": "CCC.KeyMgmt.CN03.AR01", + "Description": "When rotation settings are examined, rotation MUST be enabled with an interval not exceeding 365 days.", + "Attributes": [ + { + "FamilyName": "Key Lifecycle Management", + "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", + "Section": "CCC.KeyMgmt.CN03 Enforce Automatic Rotation", + "SubSection": "", + "SubSectionObjective": "Ensure symmetric keys rotate automatically within policy intervals to reduce exposure of key material.", + "Applicability": [ + "tlp-green" + ], + "Recommendation": "Use cloud-provider rotation features and verify via configuration scanning.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.KeyMgmt.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-12" + ] + } + ] + } + ], + "Checks": [ + "keyvault_key_rotation_enabled" + ] + }, + { + "Id": "CCC.KeyMgmt.CN04.AR01", + "Description": "When a key import request is processed, the key MUST use an approved algorithm (RSA-2048+, EC-P256+) and originate from a certified HSM.", + "Attributes": [ + { + "FamilyName": "Key Lifecycle Management", + "FamilyDescription": "Controls that govern creation, rotation, import, and retirement of cryptographic keys.", + "Section": "CCC.KeyMgmt.CN04 Validate Imported Keys", + "SubSection": "", + "SubSectionObjective": "Accept only externally generated keys that meet approved cryptographic strength and provenance requirements.", + "Applicability": [ + "tlp-green" + ], + "Recommendation": "Implement an approval workflow that validates attestation data before import.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.KeyMgmt.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-28" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.SecMgmt.CN01.AR01", + "Description": "Attempt to use an outdated version of a secret after its rotation period has passed and verify that access is denied.", + "Attributes": [ + { + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SecMgmt.CN01 Enforce Automatic Secret Rotation", + "SubSection": "", + "SubSectionObjective": "Ensure that secrets are automatically rotated on a defined schedule to reduce the risk of secret compromise and unauthorized access.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH14" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-6" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-12", + "SC-28" + ] + } + ] + } + ], + "Checks": [ + "keyvault_rbac_secret_expiration_set", + "keyvault_non_rbac_secret_expiration_set" + ] + }, + { + "Id": "CCC.SecMgmt.CN02.AR01", + "Description": "Attempt to retrieve a secret from an unauthorized region and verify that access is denied.", + "Attributes": [ + { + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SecMgmt.CN02 Enforce Secret Replication Policies", + "SubSection": "", + "SubSectionObjective": "Ensure that secrets are replicated only to authorized locations as per organizational data residency and compliance requirements.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH03", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-5" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "SC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.DataWar.CN01.AR01", + "Description": "Attempt to access underlying database tables directly without using managed views and verify that access is denied.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.DataWar.CN01 Enforce Use of Managed Views for Data Access", + "SubSection": "", + "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users from accessing underlying tables directly and enforcing consistent security policies.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.DataWar.CN02.AR01", + "Description": "Attempt to query sensitive columns without the necessary permissions and verify that access is denied or data is masked.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.DataWar.CN02 Enforce Column-Level Security Policies", + "SubSection": "", + "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles, preventing unauthorized access to sensitive information.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.DataWar.CN03.AR01", + "Description": "Attempt to query data rows that the user should not have access to and verify that access is denied or data is not returned.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.DataWar.CN03 Enforce Row-Level Security Policies", + "SubSection": "", + "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes, preventing unauthorized access to specific subsets of data.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, { "Id": "CCC.Vector.CN01.AR01", "Description": "When a vector embedding is submitted for indexing, the system MUST validate that it matches expected schema, dimension, and format profiles.", @@ -3406,7 +4727,7 @@ "Identifiers": [ "CCC.Vector.TH02", "CCC.Vector.TH05", - "CCC.TH12" + "CCC.Core.TH12" ] } ], @@ -3445,7 +4766,7 @@ "Identifiers": [ "CCC.Vector.TH02", "CCC.Vector.TH04", - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -3459,13 +4780,7 @@ ] } ], - "Checks": [ - "iam_role_user_access_admin_restricted", - "iam_custom_role_has_permissions_to_administer_resource_locks", - "iam_subscription_roles_owner_custom_not_created", - "app_function_identity_without_admin_privileges", - "app_function_identity_is_configured" - ] + "Checks": [] }, { "Id": "CCC.Vector.CN03.AR01", @@ -3487,7 +4802,7 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH03", - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -3526,7 +4841,7 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH02", - "CCC.TH12" + "CCC.Core.TH12" ] } ], @@ -3562,8 +4877,8 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH04", - "CCC.TH09", - "CCC.TH04" + "CCC.Core.TH09", + "CCC.Core.TH04" ] } ], @@ -3601,7 +4916,7 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH05", - "CCC.TH06" + "CCC.Core.TH06" ] } ], @@ -3646,451 +4961,25 @@ "Checks": [] }, { - "Id": "CCC.Core.CN01.AR01", - "Description": "When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", + "Id": "CCC.RDMS.CN01.AR02", + "Description": "When an attempt is made to authenticate to the database using known default credentials, the authentication attempt must fail and no access should be granted.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN01 Password Management", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not already set, ensure that your services are configured or updated accordingly. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "app_minimum_tls_version_12", - "app_ensure_http_is_redirected_to_https", - "storage_secure_transfer_required_is_enabled", - "storage_ensure_minimum_tls_version_12" - ] - }, - { - "Id": "CCC.Core.CN01.AR02", - "Description": "When a port is exposed for SSH network traffic, all traffic MUST include a SSH handshake AND be encrypted using SSHv2 or higher.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Any time port 22 is exposed, ensure that it has a properly implemented SSH server with SSHv2 enabled and configured with strong ciphers. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "network_ssh_internet_access_restricted", - "vm_linux_enforce_ssh_authentication", - "app_minimum_tls_version_12", - "storage_secure_transfer_required_is_enabled", - "storage_ensure_minimum_tls_version_12", - "app_ensure_http_is_redirected_to_https" - ] - }, - { - "Id": "CCC.Core.CN01.AR03", - "Description": "When the service receives unencrypted traffic, then it MUST either block the request or automatically redirect it to the secure equivalent.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Review firewall, load balancer, and application configurations to ensure insecure protocols such as HTTP, FTP, and Telnet are not exposed. Where possible, implement automatic redirection to secure protocols such as HTTPS, SFTP, SSH, and regularly scan for protocol drift. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "app_ensure_http_is_redirected_to_https", - "app_minimum_tls_version_12", - "storage_secure_transfer_required_is_enabled", - "storage_ensure_minimum_tls_version_12", - "app_ftp_deployment_disabled" - ] - }, - { - "Id": "CCC.Core.CN01.AR07", - "Description": "When a port is exposed, the service MUST ensure that the protocol and service officially assigned to that port number by the IANA Service Name and Transport Protocol Port Number Registry, and no other, is run on that port.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number Registry for more information about correct protocol-to-port assignments. Avoid running non-standard services on well-known ports. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "app_minimum_tls_version_12", - "app_ensure_http_is_redirected_to_https", - "app_ensure_using_http20", - "storage_smb_channel_encryption_with_secure_algorithm", - "storage_secure_transfer_required_is_enabled", - "storage_ensure_minimum_tls_version_12", - "storage_smb_protocol_version_is_latest" - ] - }, - { - "Id": "CCC.Core.CN01.AR08", - "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be implemented to require both client and server certificate authentication for all connections.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Configure mTLS for all endpoints that process or transmit sensitive data. Ensure both client and server certificates are validated and managed securely. Regularly review certificate authorities and automate certificate rotation where possible. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "app_client_certificates_on", - "app_minimum_tls_version_12", - "app_ensure_http_is_redirected_to_https" - ] - }, - { - "Id": "CCC.Core.CN13.AR01", - "Description": "When a port is exposed that uses certificate-based encryption, the service MUST only use valid, unexpired certificates issued by a trusted certificate authority.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH18" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "app_client_certificates_on", - "app_minimum_tls_version_12", - "keyvault_key_expiration_set_in_non_rbac", - "keyvault_key_rotation_enabled", - "keyvault_rbac_key_expiration_set", - "keyvault_rbac_secret_expiration_set", - "keyvault_non_rbac_secret_expiration_set" - ] - }, - { - "Id": "CCC.Core.CN13.AR02", - "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 180 days of issuance.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "SubSectionObjective": "Ensure default vendor-supplied DB administrator credentials are replaced with strong, unique passwords and that these credentials are properly managed using a secure password or secrets management solution.", "Applicability": [ + "tlp-red", "tlp-amber" ], - "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH18" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "keyvault_key_rotation_enabled", - "keyvault_key_expiration_set_in_non_rbac", - "keyvault_rbac_key_expiration_set", - "keyvault_non_rbac_secret_expiration_set", - "keyvault_rbac_secret_expiration_set", - "storage_ensure_encryption_with_customer_managed_keys" - ] - }, - { - "Id": "CCC.Core.CN13.AR03", - "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 90 days of issuance.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH18" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "app_client_certificates_on", - "app_ensure_http_is_redirected_to_https", - "app_minimum_tls_version_12" - ] - }, - { - "Id": "CCC.Core.CN06.AR01", - "Description": "When the service is running, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", - "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate the service's deployment location is included in this list. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH03" + "CCC.RDMS.TH01" ] } ], @@ -4098,19 +4987,89 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.AA-01" ] }, { - "ReferenceId": "CCM", + "ReferenceId": "NIST_800_53", "Identifiers": [ - "DSP-19" + "AC-2" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.RDMS.CN02.AR01", + "Description": "When repeated failed login attempts are made in a short timeframe, the account must be locked out or rate-limited to prevent further login attempts.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN02 Account Lockout and Rate-Limiting", + "SubSection": "", + "SubSectionObjective": "Ensure the database enforces lockouts or rate-limiting after a specified number of failed authentication attempts. This prevents brute force or password-guessing attacks from succeeding.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.RDMS.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-1" ] }, { - "ReferenceId": "ISO_27001", + "ReferenceId": "NIST_800_53", "Identifiers": [ - "2013 A.11.1.1" + "AC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.RDMS.CN04.AR01", + "Description": "When there is an attempt to perform a backup or restore, then the attempt must fail with an access denied message if credentials or roles that are not explicitly authorized for backup/restore functions.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN04 Access Control for Backup and Restore Operations", + "SubSection": "", + "SubSectionObjective": "Restrict who can initiate, manage, and validate database backup or restore operations through strict role-based or least-privilege access. Prevents accidental or malicious restorations, protecting data integrity and availability.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.RDMS.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" ] }, { @@ -4122,44 +5081,28 @@ ] } ], - "Checks": [ - "aks_clusters_public_access_disabled", - "aks_clusters_created_with_private_nodes", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "storage_ensure_private_endpoints_in_storage_accounts", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted", - "network_watcher_enabled", - "entra_trusted_named_locations_exists" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN06.AR02", - "Description": "When a child resource is deployed, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", + "Id": "CCC.RDMS.CN05.AR01", + "Description": "When an attempt is made to share a snapshot with an unauthorized account, the sharing request must be denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN05 Restrict Snapshot Sharing to Authorized Accounts", "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "SubSectionObjective": "Ensure database snapshots can only be shared with explicitly authorized accounts, thereby minimizing the risk of data exposure or exfiltration.", "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-red", + "tlp-amber" ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate that child resources can only be deployed to locations included in this list. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH03" + "CCC.RDMS.TH05" ] } ], @@ -4167,24 +5110,312 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-19" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.11.1.1" + "PR.DS-10" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ + "AC-4" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.RDMS.CN03.AR01", + "Description": "When backups are disabled, paused, or fail to run as scheduled, an alert must be triggered and logged.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN03 Enforce and Monitor Automated Backups", + "SubSection": "", + "SubSectionObjective": "Ensure database backups are automatically scheduled, actively monitored, and promptly reported if any disruptions occur. This helps maintain data integrity, facilitates disaster recovery, and supports business continuity when a system failure or breach occurs.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.RDMS.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "CP-9" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Build.CN01.AR01", + "Description": "Attempt to initiate a build using an unauthorized build agent and verify that the build is rejected.", + "Attributes": [ + { + "FamilyName": "Access Control", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Build.CN01 Restrict Allowed Build Agents", + "SubSection": "", + "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain control over the build environment and prevent unauthorized code execution.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Build.CN02.AR01", + "Description": "Attempt to trigger a build from an unauthorized external service or repository and verify that the build does not start.", + "Attributes": [ + { + "FamilyName": "Access Control", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Build.CN02 Restrict Allowed External Services for Build Triggers", + "SubSection": "", + "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or repositories to prevent unauthorized code execution or tampering.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Build.CN03.AR01", + "Description": "Attempt to access the build environment from an external network and verify that access is denied.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Build.CN03 Deny External Network Access for Build Environments", + "SubSection": "", + "SubSectionObjective": "Ensure that build environments do not have external network access to prevent unauthorized external access and data exfiltration.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02", + "CCC.Core.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-5" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7", + "SC-5" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.CntrReg.CN01.AR01", + "Description": "Attempt to push an artifact with known vulnerabilities to the registry and observe if it is flagged or rejected by the vulnerability scanning process.", + "Attributes": [ + { + "FamilyName": "Risk Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.CntrReg.CN01 Implement Vulnerability Scanning for Artifacts", + "SubSection": "", + "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for vulnerabilities to identify and remediate security issues before deployment.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.CntrReg.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "ID.RA-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "RA-5", + "SI-5" + ] + } + ] + } + ], + "Checks": [ + "defender_container_images_scan_enabled", + "defender_container_images_resolved_vulnerabilities" + ] + }, + { + "Id": "CCC.CntrReg.CN02.AR01", + "Description": "Confirm that artifacts older than the specified retention period are automatically deleted from the registry.", + "Attributes": [ + { + "FamilyName": "Data Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.CntrReg.CN02 Implement Cleanup Policies for Artifacts", + "SubSection": "", + "SubSectionObjective": "Ensure that unused or outdated artifacts are cleaned up according to defined policies to manage storage effectively and reduce security risks associated with outdated versions.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH14" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-6" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-12" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.IAM.CN01.AR01", + "Description": "When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials or generating temporary session tokens.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN01 Restrict IAM User Credentials Creation", + "SubSection": "", + "SubSectionObjective": "Prevent non-administrative principals from creating new long-lived credentials like access keys or generating temporary session tokens. This blocks a common privilege escalation and persistence vector.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-5", "AC-6" ] } @@ -4192,127 +5423,20 @@ } ], "Checks": [ - "entra_trusted_named_locations_exists" + "entra_policy_default_users_cannot_create_security_groups", + "entra_policy_ensure_default_user_cannot_create_apps" ] }, { - "Id": "CCC.Core.CN08.AR01", - "Description": "When data is created or modified, the data MUST have a complete and recoverable duplicate that is stored in a physically separate data center.", + "Id": "CCC.IAM.CN01.AR02", + "Description": "When a non-administrative principal attempts to create new credentials or a temporary session token, the service MUST deny the action.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN01 Restrict IAM User Credentials Creation", "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement automated data replication processes to ensure that data is consistently duplicated in another region or availability zone. Regularly test data recovery from the replicated location to ensure integrity and availability. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PT-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "BCR-08", - "BCR-10", - "BCR-11" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CP-2", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "storage_geo_redundant_enabled", - "vm_backup_enabled", - "vm_sufficient_daily_backup_retention_period" - ] - }, - { - "Id": "CCC.Core.CN08.AR02", - "Description": "When data is replicated into a second location, the service MUST be able to accurately represent the replication locations, replication status, and data synchronization status.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", - "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PT-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "BCR-08", - "BCR-10", - "BCR-11" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CP-2", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "storage_geo_redundant_enabled" - ] - }, - { - "Id": "CCC.Core.CN09.AR01", - "Description": "When the service is operational, its logs and any child resource logs MUST NOT be accessible from the resource they record access to.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", - "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "SubSectionObjective": "Prevent non-administrative principals from creating new long-lived credentials like access keys or generating temporary session tokens. This blocks a common privilege escalation and persistence vector.", "Applicability": [ "tlp-clear", "tlp-green", @@ -4324,9 +5448,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07", - "CCC.TH09", - "CCC.TH04" + "CCC.IAM.TH03" ] } ], @@ -4334,57 +5456,48 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-6" + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AU-9" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-02", - "LOG-04", - "LOG-09" + "AC-2", + "AC-3", + "AC-5", + "AC-6" ] } ] } ], "Checks": [ - "monitor_diagnostic_settings_exists", - "network_flow_log_captured_sent", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted" + "entra_policy_default_users_cannot_create_security_groups", + "entra_policy_ensure_default_user_cannot_create_apps" ] }, { - "Id": "CCC.Core.CN09.AR02", - "Description": "When the service is operational, disabling the logs for the service or its child resources MUST NOT be possible without also disabling the corresponding resource.", + "Id": "CCC.IAM.CN02.AR01", + "Description": "When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating, updating, or attaching policies.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN02 Restrict IAM Policies Modification", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "SubSectionObjective": "Ensure that only designated administrative accounts have the ability to create, modify, or attach policies that define permissions for other identities.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should disable logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging mechanisms are tightly integrated with service operations, so that logging cannot be disabled without stopping the service itself. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07", - "CCC.TH09", - "CCC.TH04" + "CCC.IAM.TH06" ] } ], @@ -4392,57 +5505,49 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-6" + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AU-9" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-02", - "LOG-04", - "LOG-09" + "AC-2", + "AC-3", + "AC-5", + "AC-6" ] } ] } ], "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_storage_account_with_activity_logs_is_private", - "keyvault_logging_enabled", - "app_http_logs_enabled" + "iam_role_user_access_admin_restricted", + "iam_subscription_roles_owner_custom_not_created", + "iam_custom_role_has_permissions_to_administer_resource_locks" ] }, { - "Id": "CCC.Core.CN09.AR03", - "Description": "When the service is operational, any attempt to redirect logs for the service or its child resources MUST NOT be possible without halting operation of the corresponding resource and publishing corresponding events to monitored channels.", + "Id": "CCC.IAM.CN02.AR02", + "Description": "When a non-administrative principal attempts to create, update, or attach policies, the service MUST deny the action.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN02 Restrict IAM Policies Modification", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "SubSectionObjective": "Ensure that only designated administrative accounts have the ability to create, modify, or attach policies that define permissions for other identities.", "Applicability": [ + "tlp-clear", + "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should result in the redirection of logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging configurations are immutable during service operation so that any changes require stopping the service and publishing corresponding events to monitored channels. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07", - "CCC.TH09", - "CCC.TH04" + "CCC.IAM.TH06" ] } ], @@ -4450,46 +5555,37 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-6" + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AU-9" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-02", - "LOG-04", - "LOG-09" + "AC-2", + "AC-3", + "AC-5", + "AC-6" ] } ] } ], "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "network_flow_log_captured_sent", - "app_http_logs_enabled", - "keyvault_logging_enabled" + "iam_role_user_access_admin_restricted", + "iam_subscription_roles_owner_custom_not_created", + "iam_custom_role_has_permissions_to_administer_resource_locks" ] }, { - "Id": "CCC.Core.CN10.AR01", - "Description": "When data is replicated, the service MUST ensure that replication only occurs to destinations that are explicitly included within the defined trust perimeter.", + "Id": "CCC.IAM.CN03.AR01", + "Description": "When a policy is created or updated that grants a principal permission to assume a role or impersonate a service identity, the principal MUST NOT contain a wildcard or be public/anonymous.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN10 Restrict Data Replication to Trust Perimeter", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN03 Restrict Role Assumption / Delegation", "SubSection": "", - "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "SubSectionObjective": "Limit which principals can assume a role or impersonate a service identity to only those required. This prevents unintended cross-account or public access by securing the \"who can act as this identity\" boundary.", "Applicability": [ "tlp-green", "tlp-amber", @@ -4500,7 +5596,1438 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH04" + "CCC.IAM.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3", + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_role_user_access_admin_restricted" + ] + }, + { + "Id": "CCC.IAM.CN03.AR02", + "Description": "When an external or unauthenticated principal tries to assume a role or impersonate a service identity, the service MUST deny the action.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN03 Restrict Role Assumption / Delegation", + "SubSection": "", + "SubSectionObjective": "Limit which principals can assume a role or impersonate a service identity to only those required. This prevents unintended cross-account or public access by securing the \"who can act as this identity\" boundary.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3", + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_role_user_access_admin_restricted" + ] + }, + { + "Id": "CCC.IAM.CN04.AR01", + "Description": "When an IAM policy is created or updated, it MUST NOT contain allow statements with wildcard permissions, unless the statement is restricted by a condition.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN04 Restrict Wildcard Usage in IAM Policies", + "SubSection": "", + "SubSectionObjective": "Limit the use of wildcard permissions in IAM policies to prevent overly broad access from being granted by default.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01", + "CCC.IAM.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-6" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.IAM.CN05.AR01", + "Description": "When a new cloud account is provisioned, a password policy MUST be configured for IAM users following the minimum PCI DSS v4.0.1 configurations.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN05 Strong Password Policies for IAM Users", + "SubSection": "", + "SubSectionObjective": "Ensure that the password policies for IAM users have strong configurations.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "When a new cloud account is provisioned, a password policy must be configured for all IAM users to align with the minimum requirements defined in PCI DSS v4.0.1. This includes, at a minimum: strength: 0 # Not yet specified - reference-id: A password length of at least 12 characters. strength: 0 # Not yet specified - reference-id: A mix of upper- and lower-case letters, numbers, and special characters. strength: 0 # Not yet specified - reference-id: Prevention of the use of previously used passwords (password history). strength: 0 # Not yet specified - reference-id: Password expiration at a defined interval (e.g., every 90 days). strength: 0 # Not yet specified - reference-id: Account lockout after a defined number of failed login attempts.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "IA-5" + ] + }, + { + "ReferenceId": "PCI-DSS", + "Identifiers": [ + "8.3.9", + "8.6.3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.IAM.CN06.AR01", + "Description": "When a static credential such as an access key has existed for 90 days or more, it MUST be rotated.", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN06 Maximum Age for Long-Term Static Credentials", + "SubSection": "", + "SubSectionObjective": "Ensure that long-lived static credentials like access keys are programmatically rotated within a defined time period to limit the window of opportunity if compromised.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "When a static credential such as an access key has existed for 90 days or more, it must be automatically rotated to reduce the risk of compromise due to long-term exposure. Organizations should implement automated checks to identify aging credentials and enforce rotation policies. Additionally, access key usage should be regularly monitored, and credentials that are no longer in use should be deactivated or deleted promptly. Where possible, prefer temporary, short-lived credentials over long-lived static ones to further minimize risk.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH09", + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.IAM.CN07.AR01", + "Description": "When a user account is disabled or deleted in the organization's IdP, the corresponding cloud identity and its access policies MUST be disabled or deleted within 24 hours.", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN07 Automate Identity De-provisioning", + "SubSection": "", + "SubSectionObjective": "Ensure that when an identity is terminated in the central Identity Provider (IdP), ts corresponding access to cloud resources is revoked automatically.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH10", + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.IAM.CN08.AR01", + "Description": "When an IAM user has credentials, such as passwords or access keys, that have not been used for 90 days or more, the unused credentials MUST be removed or deactivated.", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN08 Maximum Age for Unused Credentials", + "SubSection": "", + "SubSectionObjective": "Ensure that unused IAM credentals are removed to reduce exposure in the event of potential compromise.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "IAM user credentials (such as passwords or access keys) that have not been used for 90 days or more must be automatically removed or deactivated.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH11", + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.IAM.CN09.AR01", + "Description": "When a human user accesses the cloud environment, they MUST authenticate through the organization's federated IdP via a standard protocol (e.g., SAML, OIDC).", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN09 Enforce Federated Single Sign-On (SSO) for Human Users", + "SubSection": "", + "SubSectionObjective": "Ensure that all human users must authenticate through a central, federated Identity Provider (IdP) to access the cloud environment. This eliminates cloud-native user accounts with long-lived passwords, centralizes authentication controls, and simplifies lifecycle management.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01", + "CCC.IAM.TH09" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "IA-2" + ] + } + ] + } + ], + "Checks": [ + "entra_security_defaults_enabled" + ] + }, + { + "Id": "CCC.IAM.CN10.AR01", + "Description": "When suspicious API requests are detected, real time alerts MUST be generated to notify security personnel.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain IAM-related events.", + "Section": "CCC.IAM.CN10 Alert On Anomalous Behaviour", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when anomalous API requests are made by a single identity, such as API requests commonly associated with privilege escalation tactics, originating from an external or malicious IP address or performed by a previously dormant identity, which may indicate that credentals may be compromised, as well as for password brute-force attempts and account lockouts.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-03", + "DE.CM-06", + "DE.CM-09" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "SI-5", + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists" + ] + }, + { + "Id": "CCC.IAM.CN10.AR02", + "Description": "When suspicious API requests are detected, the associated events MUST be logged, including the source details, time, and nature of the activity.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain IAM-related events.", + "Section": "CCC.IAM.CN10 Alert On Anomalous Behaviour", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when anomalous API requests are made by a single identity, such as API requests commonly associated with privilege escalation tactics, originating from an external or malicious IP address or performed by a previously dormant identity, which may indicate that credentals may be compromised, as well as for password brute-force attempts and account lockouts.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-03", + "DE.CM-06", + "DE.CM-09" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "SI-5", + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "monitor_diagnostic_settings_exists", + "monitor_diagnostic_setting_with_appropriate_categories" + ] + }, + { + "Id": "CCC.IAM.CN11.AR01", + "Description": "When a cloud account or organization is provisioned, the native automated access and usage analysis services MUST be enabled to continuously monitor for external or public access to resources, and unused access.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain IAM-related events.", + "Section": "CCC.IAM.CN11 Enable Continuous IAM Access and Usage Analysis", + "SubSection": "", + "SubSectionObjective": "Enable and configure the cloud provider's native access and usage analysis services to continuously monitor for external access paths and internal unused access.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH02", + "CCC.IAM.TH10", + "CCC.IAM.TH11" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "ID.RA-01", + "ID.IM-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "CA-7", + "RA-5" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN01.AR01", + "Description": "Untrusted input such as user queries, RAG data or tool output MUST be validated before it is passed to a GenAI model.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN01 Model Input Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI model in order to filter or sanitise adversarial queries and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Input Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0021", + "AML.M0015" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN01.AR02", + "Description": "If malicious patterns such as prompt injection or sensitive data are detected during input validation, the input MUST be blocked or sanitised.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN01 Model Input Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI model in order to filter or sanitise adversarial queries and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Input Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0021", + "AML.M0015" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN02.AR01", + "Description": "GenAI model output MUST be validated for format conformance, malicious patterns, sensitive data and inapropriate content before being passed to users, application or plugins.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN02 Model Output Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate GenAI model output before passing it to users, applications or plugins in order to filter or sanitise insecure or unreliable output and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03", + "CCC.GenAI.TH04", + "CCC.GenAI.TH05", + "CCC.GenAI.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Output Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0002" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN02.AR02", + "Description": "In the event of policy violations, the AI-generated content MUST be redacted, encoded or rejected.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN02 Model Output Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate GenAI model output before passing it to users, applications or plugins in order to filter or sanitise insecure or unreliable output and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03", + "CCC.GenAI.TH04", + "CCC.GenAI.TH05", + "CCC.GenAI.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Output Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0002" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN03.AR01", + "Description": "When data is designated for model training or RAG ingestion, then its source MUST be explicitly approved and its provenance documented.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN03 Data Provenance and Source Vetting", + "SubSection": "", + "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes from trusted, approved sources and is authorised for the intended purposes in order to prevent the initial introduction of malicious content or leaked sensitive data.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-006" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Management" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0025" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN03.AR02", + "Description": "Data from unvetted sources MUST NOT be used in production systems.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN03 Data Provenance and Source Vetting", + "SubSection": "", + "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes from trusted, approved sources and is authorised for the intended purposes in order to prevent the initial introduction of malicious content or leaked sensitive data.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-006" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Management" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0025" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN04.AR01", + "Description": "When data is ingested for training, fine-tuning or conversion to vector embeddings, it MUST be validated for sensitive information or malicious content.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN04 Sanitisation of Ingested Data", + "SubSection": "", + "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems from extenal sources or internal knowledge bases, whether for training, conversion to vector embeddings, or real-time retireval, in order to remove or redact poisoned or sensitive data before further processing.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-002" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0007" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN04.AR02", + "Description": "If sensitive data or malicious content is detected, it must be rejected, redacted or flagged for manual review.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN04 Sanitisation of Ingested Data", + "SubSection": "", + "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems from extenal sources or internal knowledge bases, whether for training, conversion to vector embeddings, or real-time retireval, in order to remove or redact poisoned or sensitive data before further processing.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-002" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0007" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN05.AR01", + "Description": "When a RAG-enabled system generates a response containing information retrieved from its knowledge base, then the response MUST include a verifiable citation that links back to the specific source document.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN05 Citations and Source Traceability", + "SubSection": "", + "SubSectionObjective": "Require the GenAI system to provide citations or direct links back to the source documents used to generate a response, in to enhance the transparency, trustworthiness, and verifiability of AI-generated content.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH09", + "CCC.GenAI.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-DET-013" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN06.AR01", + "Description": "When an LLM invokes an external tool (e.g., an API, a plugin), then the tool MUST operate with the least privileges required for performing its intended functionality.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.GenAI.CN06 Least Privilege for Plugins", + "SubSection": "", + "SubSectionObjective": "Restricts the permissions of any external tools the GenAI system can call to limit the potential damage if an agent is coerced to perform unintended actions or vulnerabilities in the tools are exploited.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH07", + "CCC.GenAI.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Agent Permissions" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN07.AR01", + "Description": "When an application makes an API call to a foundational model in a production environment, then it MUST specify an explicit version identifier.", + "Attributes": [ + { + "FamilyName": "Configuration Management", + "FamilyDescription": "The Configuration Management control family involves establishing, maintaining and monitoring the configuration of the service and related applications and infrastructure to ensure consistency, secure defaults and compliance.", + "Section": "CCC.GenAI.CN07 Model Version Pinning", + "SubSection": "", + "SubSectionObjective": "Mandate that applications are locked (\"pinned\") to a specific, tested version of a foundational model to prevent unexpected behaviour changes introduced by provider-side updates.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-010" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN08.AR01", + "Description": "When a new AI model is considered for production deployment, it MUST undergo a formal red teaming and quality assurance review.", + "Attributes": [ + { + "FamilyName": "Model Assurance and Evaluation", + "FamilyDescription": "The Model Assurance and Evaluation control family encompasses the proactiveand continuous processes of testing and validating the AI model's behavior to ensure it aligns with safety, ethical, and quality standards.", + "Section": "CCC.GenAI.CN08 Quality Control and Red Teaming", + "SubSection": "", + "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial testing (red teaming) to ensure GenAI system meet all business, quality, security and compliance requirements before getting deployed into production environments.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH02", + "CCC.GenAI.TH04", + "CCC.GenAI.TH08", + "CCC.GenAI.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-005" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Adversarial Training and Testing", + "Red Teaming", + "Product Governance" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0008" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN08.AR02", + "Description": "If model quality review or red teaming identifies an issue that exceeds the organization's risk tolerance, the model MUST NOT be deployed until the issue is remediated.", + "Attributes": [ + { + "FamilyName": "Model Assurance and Evaluation", + "FamilyDescription": "The Model Assurance and Evaluation control family encompasses the proactiveand continuous processes of testing and validating the AI model's behavior to ensure it aligns with safety, ethical, and quality standards.", + "Section": "CCC.GenAI.CN08 Quality Control and Red Teaming", + "SubSection": "", + "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial testing (red teaming) to ensure GenAI system meet all business, quality, security and compliance requirements before getting deployed into production environments.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH02", + "CCC.GenAI.TH04", + "CCC.GenAI.TH08", + "CCC.GenAI.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-005" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Adversarial Training and Testing", + "Red Teaming", + "Product Governance" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0008" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN01.AR01", + "Description": "Verify that only authorized users can access MLDE resources, and that access modes are properly defined and enforced.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN01 Define Access Mode for ML Development Environments", + "SubSection": "", + "SubSectionObjective": "Ensure that access to Machine Learning Development Environment (MLDE) resources is strictly defined and controlled. Only authorized users with appropriate permissions can access these environments, mitigating the risk of unauthorized access, data leakage, or service disruption.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01", + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.1.1", + "2013 A.9.2.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-02" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN03.AR01", + "Description": "Verify that root access is disabled on MLDE instances containing sensitive data.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the risk of unauthorized system modifications and potential security breaches.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08", + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN03.AR02", + "Description": "For MLDE instances without sensitive data, ensure that root access is only enabled when necessary and properly authorized.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the risk of unauthorized system modifications and potential security breaches.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08", + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN04.AR01", + "Description": "Verify that terminal access is disabled on MLDE instances containing sensitive data.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of unauthorized commands and potential system compromise.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN04.AR02", + "Description": "For MLDE instances without sensitive data, ensure that terminal access is only enabled when necessary and properly authorized.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of unauthorized commands and potential system compromise.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN02.AR01", + "Description": "Confirm that file download functionality is disabled on MLDE instances containing sensitive data.", + "Attributes": [ + { + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH02", + "CCC.Core.TH02" ] } ], @@ -4514,168 +7041,98 @@ { "ReferenceId": "CCM", "Identifiers": [ - "DSP-10", - "DSP-19" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-4" - ] - } - ] - } - ], - "Checks": [ - "storage_cross_tenant_replication_disabled", - "cosmosdb_account_firewall_use_selected_networks", - "cosmosdb_account_use_private_endpoints", - "containerregistry_uses_private_link", - "keyvault_private_endpoints", - "storage_ensure_private_endpoints_in_storage_accounts" - ] - }, - { - "Id": "CCC.Core.CN02.AR01", - "Description": "When data is stored, it MUST be encrypted using the latest industry-standard encryption methods.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN02 Encrypt Data for Storage", - "SubSection": "", - "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong encryption algorithms.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "UEM-08", - "DSP-17" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-13", - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "storage_infrastructure_encryption_is_enabled", - "storage_ensure_encryption_with_customer_managed_keys", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "vm_ensure_attached_disks_encrypted_with_cmk", - "vm_ensure_unattached_disks_encrypted_with_cmk", - "sqlserver_tde_encrypted_with_cmk", - "sqlserver_tde_encryption_enabled", - "databricks_workspace_cmk_encryption_enabled" - ] - }, - { - "Id": "CCC.Core.CN11.AR01", - "Description": "When encryption keys are used, the service MUST verify that all encryption keys use the latest industry-standard cryptographic algorithms.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" + "DSI-05", + "DSI-07" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.10.1.2" + "2013 A.13.2.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-12", - "SC-17" + "SC-7", + "SC-8" ] } ] } ], - "Checks": [ - "storage_ensure_encryption_with_customer_managed_keys", - "databricks_workspace_cmk_encryption_enabled", - "keyvault_key_rotation_enabled", - "keyvault_key_expiration_set_in_non_rbac", - "keyvault_rbac_key_expiration_set", - "keyvault_rbac_secret_expiration_set", - "keyvault_non_rbac_secret_expiration_set", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "storage_smb_channel_encryption_with_secure_algorithm", - "storage_secure_transfer_required_is_enabled", - "storage_ensure_minimum_tls_version_12" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR02", - "Description": "When encryption keys are used, the service MUST rotate active keys within 180 days of issuance.", + "Id": "CCC.MLDE.CN02.AR02", + "Description": "For MLDE instances without sensitive data, ensure that file downloads are monitored and logged.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH02", + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-5" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSI-05", + "DSI-07" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.2.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7", + "SC-8" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN05.AR01", + "Description": "Verify that only approved VM and container images can be selected when creating MLDE instances.", + "Attributes": [ + { + "FamilyName": "Configuration Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual machine and container image options available when creating new MLDE instances to approved and secure configurations.", + "Applicability": [ + "tlp-red", "tlp-amber" ], "Recommendation": "", @@ -4683,7 +7140,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" + "CCC.MLDE.TH04" ] } ], @@ -4691,51 +7148,43 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.IP-1" ] }, { "ReferenceId": "CCM", "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" + "TVM-02" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.10.1.2" + "2013 A.12.5.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-12", - "SC-17" + "CM-2" ] } ] } ], - "Checks": [ - "keyvault_key_rotation_enabled", - "storage_key_rotation_90_days", - "databricks_workspace_cmk_encryption_enabled" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR03", - "Description": "When encrypting data, the service MUST verify that customer-managed encryption keys (CMEKs) are used.", + "Id": "CCC.MLDE.CN05.AR02", + "Description": "Attempt to create an MLDE instance with an unapproved image and confirm that it is denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "", - "SubSection": "CCC.Core.CN11 Protect Encryption Keys", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "FamilyName": "Configuration Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual machine and container image options available when creating new MLDE instances to approved and secure configurations.", "Applicability": [ - "tlp-amber", "tlp-red" ], "Recommendation": "", @@ -4743,7 +7192,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" + "CCC.MLDE.TH04" ] } ], @@ -4751,52 +7200,371 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.IP-1" ] }, { "ReferenceId": "CCM", "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" + "TVM-02" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.10.1.2" + "2013 A.12.5.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-12", - "SC-17" + "CM-2" ] } ] } ], - "Checks": [ - "databricks_workspace_cmk_encryption_enabled", - "storage_ensure_encryption_with_customer_managed_keys", - "vm_ensure_attached_disks_encrypted_with_cmk", - "vm_ensure_unattached_disks_encrypted_with_cmk", - "sqlserver_tde_encrypted_with_cmk", - "monitor_storage_account_with_activity_logs_cmk_encrypted" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR04", - "Description": "When encryption keys are accessed, the service MUST verify that access to encryption keys is restricted to authorized personnel and services, following the principle of least privilege.", + "Id": "CCC.MLDE.CN06.AR01", + "Description": "Verify that automatic scheduled upgrades are enabled on user-managed MLDE instances containing sensitive data.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", + "FamilyName": "Vulnerability Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the latest security patches by enforcing automatic scheduled upgrades.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH04", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-12" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "TVM-01", + "TVM-02" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.12.6.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-2" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN06.AR02", + "Description": "Ensure that the upgrade schedule is appropriately configured and does not interfere with critical operations.", + "Attributes": [ + { + "FamilyName": "Vulnerability Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the latest security patches by enforcing automatic scheduled upgrades.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH04", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-12" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "TVM-01", + "TVM-02" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.12.6.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-2" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN07.AR01", + "Description": "Verify that MLDE instances containing sensitive data cannot be accessed via public IP addresses.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH02", + "CCC.VPC.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "SEF-05" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.1.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN07.AR02", + "Description": "For MLDE instances without sensitive data requiring public access, ensure that appropriate security controls are in place and access is approved.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH02", + "CCC.VPC.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "SEF-05" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.1.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN08.AR01", + "Description": "Verify that MLDE instances containing sensitive data can only be deployed in approved virtual networks with appropriate security controls.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to ensure they are deployed within approved and secure network environments.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01", + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.1.2" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN08.AR02", + "Description": "Ensure that MLDE instances without sensitive data are deployed in networks that meet organizational security standards.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to ensure they are deployed within approved and secure network environments.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01", + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.1.2" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Message.CN01.AR01", + "Description": "Attempt to publish a message without using a customer-managed encryption key and verify that the message is rejected or not stored.", + "Attributes": [ + { + "FamilyName": "Encryption", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Message.CN01 Use Customer-Managed Encryption Keys (CMEK) for Messages", + "SubSection": "", + "SubSectionObjective": "Ensure that messages are encrypted using customer-managed encryption keys (CMEK) to provide enhanced control over encryption processes and keys, meeting compliance and security requirements.", "Applicability": [ "tlp-clear", "tlp-green", @@ -4808,7 +7576,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" + "CCC.Core.TH01" ] } ], @@ -4819,546 +7587,82 @@ "PR.DS-1" ] }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.2" - ] - }, { "ReferenceId": "NIST_800_53", "Identifiers": [ "SC-12", - "SC-17" + "SC-13" ] } ] } ], - "Checks": [ - "databricks_workspace_cmk_encryption_enabled", - "keyvault_rbac_enabled", - "keyvault_key_rotation_enabled", - "keyvault_key_expiration_set_in_non_rbac", - "keyvault_rbac_key_expiration_set", - "keyvault_access_only_through_private_endpoints", - "keyvault_private_endpoints", - "keyvault_logging_enabled", - "keyvault_recoverable", - "storage_ensure_encryption_with_customer_managed_keys" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR05", - "Description": "When encryption keys are used, the service MUST rotate active keys within 365 days of issuance.", + "Id": "CCC.SvlsComp.CN01.AR01", + "Description": "Attempt to access the serverless function over the public internet and verify that access is denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SvlsComp.CN01 Enforce Use of Private Endpoints for Serverless Function", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that the serverless function is accessible only through a private endpoint, allowing it to communicate securely within a virtual private network and preventing unauthorized external access.", "Applicability": [ - "tlp-clear", - "tlp-green" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-17" - ] - } - ] - } - ], - "Checks": [ - "keyvault_key_rotation_enabled", - "storage_ensure_encryption_with_customer_managed_keys", - "databricks_workspace_cmk_encryption_enabled", - "storage_key_rotation_90_days", - "keyvault_rbac_key_expiration_set", - "keyvault_rbac_secret_expiration_set", - "keyvault_key_expiration_set_in_non_rbac" - ] - }, - { - "Id": "CCC.Core.CN11.AR06", - "Description": "When encryption keys are used, the service MUST rotate active keys within 90 days of issuance.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-17" - ] - } - ] - } - ], - "Checks": [ - "keyvault_key_rotation_enabled" - ] - }, - { - "Id": "CCC.Core.CN14.AR01", - "Description": "When backups are created for disaster recovery purposes, the storage mechanism MUST NOT allow modification or deletion within 30 days of creation.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN14 Maintain Recent Backups", - "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Use immutable storage solutions where possible. Implement backup retention policies that enforce a minimum retention period of 30 days. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "vm_backup_enabled", - "vm_sufficient_daily_backup_retention_period" - ] - }, - { - "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 30 days.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN14 Maintain Recent Backups", - "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", - "Applicability": [ - "tlp-clear", - "tlp-green", + "tlp-red", "tlp-amber" ], - "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 30 days. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06" + "CCC.Core.TH01" ] } ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "vm_backup_enabled", - "vm_sufficient_daily_backup_retention_period" - ] - }, - { - "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 14 days.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN14 Maintain Recent Backups", - "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 14 days. ", - "SectionThreatMappings": [ + "SectionGuidelineMappings": [ { - "ReferenceId": "CCC", + "ReferenceId": "NIST-CSF", "Identifiers": [ - "CCC.TH06" + "PR.AC-5" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7", + "SC-8" ] } - ], - "SectionGuidelineMappings": [] + ] } ], "Checks": [ - "vm_backup_enabled", - "vm_sufficient_daily_backup_retention_period" + "app_function_not_publicly_accessible" ] }, { - "Id": "CCC.Core.CN03.AR01", - "Description": "When an entity attempts to modify the service through a user interface, the authentication process MUST require multiple identifying factors for authentication.", + "Id": "CCC.SvlsComp.CN02.AR01", + "Description": "Send requests to invoke the function up to the allowed threshold and confirm they are successful; then send additional requests exceeding the threshold from the same entity and verify that they are denied.", "Attributes": [ { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "FamilyName": "Availability", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SvlsComp.CN02 Implement Function Invocation Rate Limits", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that function invocation is limited to a specified threshold from any single entity, preventing resource exhaustion and denial of service attacks.", "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-red", + "tlp-amber" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IA-2" - ] - } - ] - } - ], - "Checks": [ - "entra_non_privileged_user_has_mfa", - "entra_privileged_user_has_mfa", - "entra_conditional_access_policy_require_mfa_for_management_api", - "entra_security_defaults_enabled", - "entra_user_with_vm_access_has_mfa" - ] - }, - { - "Id": "CCC.Core.CN03.AR02", - "Description": "When an entity attempts to modify the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IA-2" - ] - } - ] - } - ], - "Checks": [ - "entra_conditional_access_policy_require_mfa_for_management_api", - "entra_non_privileged_user_has_mfa", - "entra_privileged_user_has_mfa", - "entra_security_defaults_enabled" - ] - }, - { - "Id": "CCC.Core.CN03.AR03", - "Description": "When an entity attempts to view information on the service through a user interface, the authentication process MUST require multiple identifying factors from the user.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IA-2" - ] - } - ] - } - ], - "Checks": [ - "entra_conditional_access_policy_require_mfa_for_management_api", - "entra_non_privileged_user_has_mfa", - "entra_privileged_user_has_mfa", - "entra_security_defaults_enabled" - ] - }, - { - "Id": "CCC.Core.CN03.AR04", - "Description": "When an entity attempts to view information on the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IA-2" - ] - } - ] - } - ], - "Checks": [ - "entra_conditional_access_policy_require_mfa_for_management_api", - "entra_non_privileged_user_has_mfa", - "entra_privileged_user_has_mfa", - "entra_user_with_vm_access_has_mfa" - ] - }, - { - "Id": "CCC.Core.CN05.AR01", - "Description": "When an attempt is made to modify data on the service or a child resource, the service MUST block requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH12" ] } ], @@ -5366,782 +7670,19 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" + "PR.DS-4" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AC-3" + "SC-5" ] } ] } ], - "Checks": [ - "aks_cluster_rbac_enabled", - "aks_network_policy_enabled", - "aks_clusters_public_access_disabled", - "app_client_certificates_on", - "app_ensure_auth_is_set_up", - "app_register_with_identity", - "app_function_identity_is_configured", - "app_function_identity_without_admin_privileges", - "app_function_not_publicly_accessible", - "app_function_access_keys_configured", - "iam_custom_role_has_permissions_to_administer_resource_locks", - "iam_subscription_roles_owner_custom_not_created", - "iam_role_user_access_admin_restricted", - "keyvault_rbac_enabled", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "entra_conditional_access_policy_require_mfa_for_management_api", - "entra_global_admin_in_less_than_five_users", - "entra_non_privileged_user_has_mfa", - "entra_security_defaults_enabled", - "entra_trusted_named_locations_exists", - "entra_user_with_vm_access_has_mfa", - "vm_jit_access_enabled", - "vm_linux_enforce_ssh_authentication" - ] - }, - { - "Id": "CCC.Core.CN05.AR02", - "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST refuse requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "aks_cluster_rbac_enabled", - "app_register_with_identity", - "app_function_identity_is_configured", - "app_function_identity_without_admin_privileges", - "iam_custom_role_has_permissions_to_administer_resource_locks", - "iam_subscription_roles_owner_custom_not_created", - "iam_role_user_access_admin_restricted", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "containerregistry_admin_user_disabled", - "cosmosdb_account_use_private_endpoints", - "cosmosdb_account_firewall_use_selected_networks", - "cosmosdb_account_use_aad_and_rbac", - "keyvault_rbac_enabled", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "storage_account_key_access_disabled", - "storage_ensure_private_endpoints_in_storage_accounts", - "storage_ensure_azure_services_are_trusted_to_access_is_enabled", - "storage_default_to_entra_authorization_enabled", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted", - "aks_clusters_public_access_disabled", - "app_function_not_publicly_accessible", - "entra_global_admin_in_less_than_five_users", - "entra_conditional_access_policy_require_mfa_for_management_api", - "entra_non_privileged_user_has_mfa", - "entra_privileged_user_has_mfa", - "entra_trusted_named_locations_exists", - "entra_user_with_vm_access_has_mfa" - ] - }, - { - "Id": "CCC.Core.CN05.AR03", - "Description": "When administrative access or configuration change is attempted on the service or a child resource in a multi-tenant environment, the service MUST refuse requests across tenant boundaries unless the origin is explicitly included in a pre-approved allowlist.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "entra_conditional_access_policy_require_mfa_for_management_api", - "entra_global_admin_in_less_than_five_users", - "entra_non_privileged_user_has_mfa", - "iam_role_user_access_admin_restricted", - "entra_trusted_named_locations_exists", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "keyvault_rbac_enabled", - "cosmosdb_account_firewall_use_selected_networks", - "cosmosdb_account_use_private_endpoints", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "containerregistry_admin_user_disabled", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted", - "app_function_not_publicly_accessible", - "storage_default_network_access_rule_is_denied", - "storage_ensure_private_endpoints_in_storage_accounts", - "storage_blob_public_access_level_is_disabled", - "storage_cross_tenant_replication_disabled" - ] - }, - { - "Id": "CCC.Core.CN05.AR04", - "Description": "When data is requested from outside the trust perimeter, the service MUST refuse requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "", - "SubSection": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "aks_cluster_rbac_enabled", - "aks_network_policy_enabled", - "aks_clusters_public_access_disabled", - "app_register_with_identity", - "app_function_access_keys_configured", - "app_function_identity_is_configured", - "app_function_identity_without_admin_privileges", - "app_ensure_auth_is_set_up", - "app_function_not_publicly_accessible", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "containerregistry_admin_user_disabled", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "keyvault_rbac_enabled", - "storage_account_key_access_disabled", - "storage_default_to_entra_authorization_enabled", - "storage_ensure_azure_services_are_trusted_to_access_is_enabled", - "storage_default_network_access_rule_is_denied", - "storage_secure_transfer_required_is_enabled", - "iam_role_user_access_admin_restricted", - "iam_subscription_roles_owner_custom_not_created", - "cosmosdb_account_use_aad_and_rbac", - "sqlserver_azuread_administrator_enabled", - "sqlserver_unrestricted_inbound_access", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted", - "vm_jit_access_enabled", - "entra_conditional_access_policy_require_mfa_for_management_api", - "entra_global_admin_in_less_than_five_users", - "entra_non_privileged_user_has_mfa", - "entra_privileged_user_has_mfa", - "entra_trusted_named_locations_exists", - "entra_user_with_vm_access_has_mfa" - ] - }, - { - "Id": "CCC.Core.CN05.AR05", - "Description": "When any request is made from outside the trust perimeter, the service MUST NOT provide any response that may indicate the service exists.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "aks_clusters_created_with_private_nodes", - "aks_clusters_public_access_disabled", - "aks_network_policy_enabled", - "app_function_not_publicly_accessible", - "app_register_with_identity", - "app_function_identity_is_configured", - "app_function_identity_without_admin_privileges", - "app_client_certificates_on", - "app_ensure_auth_is_set_up", - "containerregistry_not_publicly_accessible", - "containerregistry_uses_private_link", - "containerregistry_admin_user_disabled", - "keyvault_private_endpoints", - "keyvault_access_only_through_private_endpoints", - "keyvault_rbac_enabled", - "keyvault_logging_enabled", - "storage_account_key_access_disabled", - "storage_default_to_entra_authorization_enabled", - "storage_ensure_private_endpoints_in_storage_accounts", - "storage_ensure_azure_services_are_trusted_to_access_is_enabled", - "iam_custom_role_has_permissions_to_administer_resource_locks", - "iam_role_user_access_admin_restricted", - "iam_subscription_roles_owner_custom_not_created", - "vm_jit_access_enabled", - "vm_linux_enforce_ssh_authentication", - "network_http_internet_access_restricted", - "network_rdp_internet_access_restricted", - "network_ssh_internet_access_restricted", - "network_udp_internet_access_restricted" - ] - }, - { - "Id": "CCC.Core.CN05.AR06", - "Description": "When any request is made to the service or a child resource, the service MUST refuse requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "aks_cluster_rbac_enabled", - "app_register_with_identity", - "app_function_identity_is_configured", - "app_function_identity_without_admin_privileges", - "app_function_access_keys_configured", - "app_function_not_publicly_accessible", - "iam_custom_role_has_permissions_to_administer_resource_locks", - "iam_role_user_access_admin_restricted", - "iam_subscription_roles_owner_custom_not_created", - "cosmosdb_account_use_aad_and_rbac", - "entra_global_admin_in_less_than_five_users", - "entra_non_privileged_user_has_mfa", - "entra_privileged_user_has_mfa", - "entra_conditional_access_policy_require_mfa_for_management_api", - "keyvault_rbac_enabled", - "vm_jit_access_enabled", - "vm_linux_enforce_ssh_authentication" - ] - }, - { - "Id": "CCC.Core.CN04.AR01", - "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN04 Log All Access and Changes", - "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-08" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3", - "AU-12" - ] - } - ] - } - ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "keyvault_logging_enabled", - "network_flow_log_captured_sent", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_alert_create_update_sqlserver_fr", - "monitor_alert_delete_nsg", - "monitor_alert_delete_public_ip_address_rule", - "monitor_alert_delete_security_solution", - "monitor_alert_create_policy_assignment", - "monitor_alert_create_update_public_ip_address_rule", - "monitor_alert_create_update_security_solution", - "monitor_alert_service_health_exists" - ] - }, - { - "Id": "CCC.Core.CN04.AR02", - "Description": "When any attempt is made to modify data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN04 Log All Access and Changes", - "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-08" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3", - "AU-12" - ] - } - ] - } - ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "keyvault_logging_enabled", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "network_flow_log_captured_sent" - ] - }, - { - "Id": "CCC.Core.CN04.AR03", - "Description": "When any attempt is made to read data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN04 Log All Access and Changes", - "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-08" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3", - "AU-12" - ] - } - ] - } - ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "keyvault_logging_enabled", - "app_http_logs_enabled", - "network_flow_log_captured_sent", - "monitor_storage_account_with_activity_logs_is_private", - "monitor_storage_account_with_activity_logs_cmk_encrypted" - ] - }, - { - "Id": "CCC.Core.CN07.AR01", - "Description": "When enumeration activities are detected, the service MUST publish an event to a monitored channel which includes the client identity, time, and nature of the activity.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", - "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement event publication mechanisms and alerts for patterns indicative of enumeration activities, such as repeated access attempts, requests, or liveness probes. Configure alerts to notify security teams of any activities that merit further investigation. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH15" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-05", - "SEF-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-6" - ] - } - ] - } - ], - "Checks": [ - "apim_threat_detection_llm_jacking" - ] - }, - { - "Id": "CCC.Core.CN07.AR02", - "Description": "When enumeration activities are detected, the service MUST log the client identity, time, and nature of the activity.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", - "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement logging mechanisms to capture details of enumeration activities, including client identity, timestamps, and activity nature. Retain logs according to organizational policies, and occasionally review them for patterns that may indicate reconnaissance activities. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH15" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-05", - "SEF-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-6" - ] - } - ] - } - ], - "Checks": [ - "monitor_diagnostic_settings_exists", - "monitor_diagnostic_setting_with_appropriate_categories", - "monitor_alert_create_update_sqlserver_fr", - "monitor_alert_delete_nsg", - "monitor_alert_delete_public_ip_address_rule", - "monitor_alert_delete_security_solution", - "monitor_alert_create_policy_assignment", - "monitor_alert_create_update_nsg", - "monitor_alert_create_update_public_ip_address_rule", - "monitor_alert_create_update_security_solution", - "monitor_alert_service_health_exists", - "monitor_storage_account_with_activity_logs_cmk_encrypted", - "monitor_storage_account_with_activity_logs_is_private" - ] + "Checks": [] } ] } diff --git a/prowler/compliance/gcp/ccc_gcp.json b/prowler/compliance/gcp/ccc_gcp.json index e3060646c5..df6b15bc5c 100644 --- a/prowler/compliance/gcp/ccc_gcp.json +++ b/prowler/compliance/gcp/ccc_gcp.json @@ -1,10 +1,1630 @@ { "Framework": "CCC", - "Version": "", + "Version": "v2025.10", "Provider": "GCP", "Name": "Common Cloud Controls Catalog (CCC)", "Description": "Common Cloud Controls Catalog (CCC) for GCP", "Requirements": [ + { + "Id": "CCC.Core.CN01.AR01", + "Description": "When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not already set, ensure that your services are configured or updated accordingly.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "cloudsql_instance_ssl_connections" + ] + }, + { + "Id": "CCC.Core.CN01.AR02", + "Description": "When a port is exposed for SSH network traffic, all traffic MUST include a SSH handshake AND be encrypted using SSHv2 or higher.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Any time port 22 is exposed, ensure that it has a properly implemented SSH server with SSHv2 enabled and configured with strong ciphers.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "compute_firewall_ssh_access_from_the_internet_allowed", + "compute_instance_block_project_wide_ssh_keys_disabled", + "compute_project_os_login_enabled", + "compute_project_os_login_2fa_enabled" + ] + }, + { + "Id": "CCC.Core.CN01.AR03", + "Description": "When the service receives unencrypted traffic, then it MUST either block the request or automatically redirect it to the secure equivalent.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Review firewall, load balancer, and application configurations to ensure insecure protocols such as HTTP, FTP, and Telnet are not exposed. Where possible, implement automatic redirection to secure protocols such as HTTPS, SFTP, SSH, and regularly scan for protocol drift.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [ + "cloudsql_instance_ssl_connections" + ] + }, + { + "Id": "CCC.Core.CN01.AR07", + "Description": "When a port is exposed, the service MUST ensure that the protocol and service officially assigned to that port number by the IANA Service Name and Transport Protocol Port Number Registry, and no other, is run on that port.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number Registry for more information about correct protocol-to-port assignments. Avoid running non-standard services on well-known ports.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN01.AR08", + "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be implemented to require both client and server certificate authentication for all connections.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "SubSection": "", + "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Configure mTLS for all endpoints that process or transmit sensitive data. Ensure both client and server certificates are validated and managed securely. Regularly review certificate authorities and automate certificate rotation where possible.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "IVS-03", + "IVS-07" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN13.AR01", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST only use valid, unexpired certificates issued by a trusted certificate authority.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH18" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN13.AR02", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 180 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "Applicability": [ + "tlp-amber" + ], + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH18" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN13.AR03", + "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 90 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH18" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN06.AR01", + "Description": "When the service is running, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", + "SubSection": "", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate the service's deployment location is included in this list.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-19" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN06.AR02", + "Description": "When a child resource is deployed, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", + "SubSection": "", + "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate that child resources can only be deployed to locations included in this list.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-19" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN08.AR01", + "Description": "When data is created or modified, the data MUST have a complete and recoverable duplicate that is stored in a physically separate data center.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", + "SubSection": "", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement automated data replication processes to ensure that data is consistently duplicated in another region or availability zone. Regularly test data recovery from the replicated location to ensure integrity and availability.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "BCR-08", + "BCR-10", + "BCR-11" + ] + } + ] + } + ], + "Checks": [ + "cloudsql_instance_automated_backups" + ] + }, + { + "Id": "CCC.Core.CN08.AR02", + "Description": "When data is replicated into a second location, the service MUST be able to accurately represent the replication locations, replication status, and data synchronization status.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", + "SubSection": "", + "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "BCR-08", + "BCR-10", + "BCR-11" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN09.AR01", + "Description": "When the service is operational, its logs and any child resource logs MUST NOT be accessible from the resource they record access to.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07", + "CCC.Core.TH09", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-02", + "LOG-04", + "LOG-09" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock" + ] + }, + { + "Id": "CCC.Core.CN09.AR02", + "Description": "When the service is operational, disabling the logs for the service or its child resources MUST NOT be possible without also disabling the corresponding resource.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "No normal business operations should disable logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging mechanisms are tightly integrated with service operations, so that logging cannot be disabled without stopping the service itself.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07", + "CCC.Core.TH09", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-02", + "LOG-04", + "LOG-09" + ] + } + ] + } + ], + "Checks": [ + "iam_audit_logs_enabled", + "logging_sink_created", + "cloudstorage_bucket_log_retention_policy_lock" + ] + }, + { + "Id": "CCC.Core.CN09.AR03", + "Description": "When the service is operational, any attempt to redirect logs for the service or its child resources MUST NOT be possible without halting operation of the corresponding resource and publishing corresponding events to monitored channels.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "No normal business operations should result in the redirection of logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging configurations are immutable during service operation so that any changes require stopping the service and publishing corresponding events to monitored channels.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07", + "CCC.Core.TH09", + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-02", + "LOG-04", + "LOG-09" + ] + } + ] + } + ], + "Checks": [ + "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled" + ] + }, + { + "Id": "CCC.Core.CN10.AR01", + "Description": "When data is replicated, the service MUST ensure that replication only occurs to destinations that are explicitly included within the defined trust perimeter.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN10 Restrict Data Replication to Trust Perimeter", + "SubSection": "", + "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-10", + "DSP-19" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN02.AR01", + "Description": "When data is stored, it MUST be encrypted using the latest industry-standard encryption methods.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN02 Encrypt Data for Storage", + "SubSection": "", + "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong encryption algorithms.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-03", + "CEK-04", + "UEM-08", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "compute_instance_encryption_with_csek_enabled", + "dataproc_encrypted_with_cmks_disabled", + "bigquery_dataset_cmk_encryption", + "bigquery_table_cmk_encryption" + ] + }, + { + "Id": "CCC.Core.CN11.AR01", + "Description": "When encryption keys are used, the service MUST verify that all encryption keys use the latest industry-standard cryptographic algorithms.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN11.AR02", + "Description": "When encryption keys are used, the service MUST rotate active keys within 180 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "kms_key_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN11.AR03", + "Description": "When encrypting data, the service MUST verify that customer-managed encryption keys (CMEKs) are used.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "bigquery_dataset_cmk_encryption", + "bigquery_table_cmk_encryption", + "dataproc_encrypted_with_cmks_disabled", + "compute_instance_encryption_with_csek_enabled" + ] + }, + { + "Id": "CCC.Core.CN11.AR04", + "Description": "When encryption keys are accessed, the service MUST verify that access to encryption keys is restricted to authorized personnel and services, following the principle of least privilege.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "kms_key_not_publicly_accessible", + "iam_role_kms_enforce_separation_of_duties" + ] + }, + { + "Id": "CCC.Core.CN11.AR05", + "Description": "When encryption keys are used, the service MUST rotate active keys within 365 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-clear", + "tlp-green" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "kms_key_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN11.AR06", + "Description": "When encryption keys are used, the service MUST rotate active keys within 90 days of issuance.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN11 Protect Encryption Keys", + "SubSection": "", + "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "CEK-08", + "CEK-10", + "CEK-12" + ] + } + ] + } + ], + "Checks": [ + "kms_key_rotation_enabled" + ] + }, + { + "Id": "CCC.Core.CN14.AR01", + "Description": "When backups are created for disaster recovery purposes, the storage mechanism MUST NOT allow modification or deletion within 30 days of creation.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN14 Maintain Recent Backups", + "SubSection": "", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Use immutable storage solutions where possible. Implement backup retention policies that enforce a minimum retention period of 30 days.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "cloudsql_instance_automated_backups", + "cloudstorage_bucket_log_retention_policy_lock", + "cloudstorage_bucket_sufficient_retention_period" + ] + }, + { + "Id": "CCC.Core.CN14.AR02", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 30 days.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN14 Maintain Recent Backups", + "SubSection": "", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber" + ], + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 30 days.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "cloudsql_instance_automated_backups" + ] + }, + { + "Id": "CCC.Core.CN14.AR03", + "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 14 days.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.Core.CN14 Maintain Recent Backups", + "SubSection": "", + "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 14 days.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [] + } + ], + "Checks": [ + "cloudsql_instance_automated_backups" + ] + }, + { + "Id": "CCC.Core.CN03.AR01", + "Description": "When an entity attempts to modify the service through a user interface, the authentication process MUST require multiple identifying factors for authentication.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "compute_project_os_login_2fa_enabled" + ] + }, + { + "Id": "CCC.Core.CN03.AR02", + "Description": "When an entity attempts to modify the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "apikeys_api_restrictions_configured", + "apikeys_key_exists", + "apikeys_key_rotated_in_90_days" + ] + }, + { + "Id": "CCC.Core.CN03.AR03", + "Description": "When an entity attempts to view information on the service through a user interface, the authentication process MUST require multiple identifying factors from the user.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "compute_project_os_login_2fa_enabled" + ] + }, + { + "Id": "CCC.Core.CN03.AR04", + "Description": "When an entity attempts to view information on the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "SubSection": "", + "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-14" + ] + } + ] + } + ], + "Checks": [ + "apikeys_api_restrictions_configured", + "apikeys_key_exists", + "apikeys_key_rotated_in_90_days" + ] + }, + { + "Id": "CCC.Core.CN05.AR01", + "Description": "When an attempt is made to modify data on the service or a child resource, the service MUST block requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_public_access", + "compute_instance_public_ip", + "cloudsql_instance_public_ip", + "compute_instance_default_service_account_in_use", + "compute_instance_default_service_account_in_use_with_full_api_access", + "gke_cluster_no_default_service_account", + "iam_no_service_roles_at_project_level", + "iam_role_kms_enforce_separation_of_duties", + "iam_role_sa_enforce_separation_of_duties", + "iam_sa_no_administrative_privileges" + ] + }, + { + "Id": "CCC.Core.CN05.AR02", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST refuse requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "iam_no_service_roles_at_project_level", + "iam_sa_no_administrative_privileges", + "iam_role_kms_enforce_separation_of_duties", + "iam_role_sa_enforce_separation_of_duties", + "iam_account_access_approval_enabled" + ] + }, + { + "Id": "CCC.Core.CN05.AR03", + "Description": "When administrative access or configuration change is attempted on the service or a child resource in a multi-tenant environment, the service MUST refuse requests across tenant boundaries unless the origin is explicitly included in a pre-approved allowlist.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_uses_vpc_service_controls" + ] + }, + { + "Id": "CCC.Core.CN05.AR04", + "Description": "When data is requested from outside the trust perimeter, the service MUST refuse requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_public_access", + "cloudstorage_uses_vpc_service_controls", + "cloudsql_instance_public_ip", + "cloudsql_instance_public_access", + "compute_instance_public_ip", + "kms_key_not_publicly_accessible", + "bigquery_dataset_public_access" + ] + }, + { + "Id": "CCC.Core.CN05.AR05", + "Description": "When any request is made from outside the trust perimeter, the service MUST NOT provide any response that may indicate the service exists.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "compute_instance_public_ip", + "cloudstorage_bucket_public_access", + "cloudsql_instance_public_ip", + "kms_key_not_publicly_accessible", + "compute_image_not_publicly_shared", + "bigquery_dataset_public_access" + ] + }, + { + "Id": "CCC.Core.CN05.AR06", + "Description": "When any request is made to the service or a child resource, the service MUST refuse requests from unauthorized entities.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", + "SubSection": "", + "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-01", + "DSP-07", + "DSP-08", + "DSP-10", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "iam_no_service_roles_at_project_level", + "iam_sa_no_administrative_privileges", + "iam_sa_no_user_managed_keys", + "iam_sa_user_managed_key_rotate_90_days", + "iam_sa_user_managed_key_unused", + "iam_service_account_unused", + "iam_role_kms_enforce_separation_of_duties", + "iam_role_sa_enforce_separation_of_duties" + ] + }, + { + "Id": "CCC.Core.CN04.AR01", + "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN04 Log All Access and Changes", + "SubSection": "", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-08" + ] + } + ] + } + ], + "Checks": [ + "iam_audit_logs_enabled", + "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", + "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", + "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", + "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", + "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", + "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", + "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", + "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled" + ] + }, + { + "Id": "CCC.Core.CN04.AR02", + "Description": "When any attempt is made to modify data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN04 Log All Access and Changes", + "SubSection": "", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-08" + ] + } + ] + } + ], + "Checks": [ + "iam_audit_logs_enabled", + "cloudstorage_audit_logs_enabled", + "cloudstorage_bucket_logging_enabled", + "logging_sink_created" + ] + }, + { + "Id": "CCC.Core.CN04.AR03", + "Description": "When any attempt is made to read data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN04 Log All Access and Changes", + "SubSection": "", + "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-08" + ] + } + ] + } + ], + "Checks": [ + "iam_audit_logs_enabled", + "cloudstorage_audit_logs_enabled", + "cloudstorage_bucket_logging_enabled", + "logging_sink_created" + ] + }, + { + "Id": "CCC.Core.CN07.AR01", + "Description": "When enumeration activities are detected, the service MUST publish an event to a monitored channel which includes the client identity, time, and nature of the activity.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement event publication mechanisms and alerts for patterns indicative of enumeration activities, such as repeated access attempts, requests, or liveness probes. Configure alerts to notify security teams of any activities that merit further investigation.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH15" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-05", + "SEF-05" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Core.CN07.AR02", + "Description": "When enumeration activities are detected, the service MUST log the client identity, time, and nature of the activity.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements.", + "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement logging mechanisms to capture details of enumeration activities, including client identity, timestamps, and activity nature. Retain logs according to organizational policies, and occasionally review them for patterns that may indicate reconnaissance activities.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH15" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "LOG-05", + "SEF-05" + ] + } + ] + } + ], + "Checks": [] + }, { "Id": "CCC.AuditLog.CN01.AR01", "Description": "When the signature validation process is performed, then it MUST detect any modification of data.", @@ -18,13 +1638,13 @@ "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure hash of data is included in digital signature. ", + "Recommendation": "Ensure hash of data is included in digital signature.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -44,12 +1664,7 @@ ] } ], - "Checks": [ - "iam_audit_logs_enabled", - "cloudstorage_bucket_log_retention_policy_lock", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled" - ] + "Checks": [] }, { "Id": "CCC.AuditLog.CN01.AR02", @@ -64,13 +1679,13 @@ "Applicability": [ "tlp-red" ], - "Recommendation": "Ensure verification process includes a chained hash function. ", + "Recommendation": "Ensure verification process includes a chained hash function.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -90,11 +1705,7 @@ ] } ], - "Checks": [ - "iam_audit_logs_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled" - ] + "Checks": [] }, { "Id": "CCC.AuditLog.CN02.AR01", @@ -115,7 +1726,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06" + "CCC.Core.TH06" ] } ], @@ -139,15 +1750,7 @@ ], "Checks": [ "iam_audit_logs_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled" + "logging_sink_created" ] }, { @@ -164,12 +1767,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured ", + "Recommendation": "Ensure alerting is correctly configured", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -191,17 +1794,7 @@ } ], "Checks": [ - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "cloudstorage_bucket_log_retention_policy_lock", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled", - "iam_audit_logs_enabled" + "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled" ] }, { @@ -218,12 +1811,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Ensure alerting is correctly configured ", + "Recommendation": "Ensure alerting is correctly configured", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -245,9 +1838,7 @@ } ], "Checks": [ - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "cloudstorage_bucket_log_retention_policy_lock", - "logging_sink_created" + "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled" ] }, { @@ -264,13 +1855,13 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure the audit log bucket to enable server access logging. Ensure the target logging bucket is configured for appropriate security, including restricted access and immutability. ", + "Recommendation": "Configure the audit log bucket to enable server access logging. Ensure the target logging bucket is configured for appropriate security, including restricted access and immutability.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01", - "CCC.TH09" + "CCC.Core.TH01", + "CCC.Core.TH09" ] } ], @@ -292,10 +1883,8 @@ } ], "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock", - "cloudstorage_bucket_public_access", - "cloudstorage_bucket_uniform_bucket_level_access", - "logging_sink_created" + "cloudstorage_bucket_logging_enabled", + "cloudstorage_audit_logs_enabled" ] }, { @@ -312,12 +1901,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure audit log exporting. ", + "Recommendation": "Configure audit log exporting.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -340,11 +1929,8 @@ } ], "Checks": [ - "logging_sink_created", "iam_audit_logs_enabled", - "cloudstorage_bucket_log_retention_policy_lock", - "cloudstorage_bucket_uniform_bucket_level_access", - "cloudstorage_bucket_public_access" + "logging_sink_created" ] }, { @@ -362,13 +1948,13 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce the required data retention period. ", + "Recommendation": "Configure the audit log bucket's lifecycle rules or object retention settings to enforce the required data retention period.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -390,8 +1976,7 @@ } ], "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock", - "logging_sink_created" + "cloudstorage_bucket_log_retention_policy_lock" ] }, { @@ -409,13 +1994,13 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations) on the audit log bucket. ", + "Recommendation": "Enable MFA Delete (or equivalent multi-factor authentication for delete operations) on the audit log bucket.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06", - "CCC.TH07" + "CCC.Core.TH06", + "CCC.Core.TH07" ] } ], @@ -436,11 +2021,7 @@ ] } ], - "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock", - "iam_audit_logs_enabled", - "logging_sink_created" - ] + "Checks": [] }, { "Id": "CCC.AuditLog.CN08.AR01", @@ -456,12 +2037,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy. ", + "Recommendation": "Configure object lock policy.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -500,12 +2081,12 @@ "tlp-red", "tlp-amber" ], - "Recommendation": "Review field level access controls on audit data. ", + "Recommendation": "Review field level access controls on audit data.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH07" ] } ], @@ -547,59 +2128,12 @@ "tlp-amber", "tlp-green" ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AA-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_public_access", - "cloudstorage_bucket_uniform_bucket_level_access", - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.AuditLog.CN10.AR02", - "Description": "When the URL of a audit log storage bucket's object is accessed publicly then, it should be denied by bucket policy.", - "Attributes": [ - { - "FamilyName": "Confidentiality", - "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", - "Section": "CCC.AuditLog.CN10 Ensure Audit Bucket is Not Publicly Accessible", - "SubSection": "", - "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent unauthorized exposure of sensitive log data.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green" - ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -626,25 +2160,26 @@ ] }, { - "Id": "CCC.Build.CN01.AR01", - "Description": "Attempt to initiate a build using an unauthorized build agent and verify that the build is rejected.", + "Id": "CCC.AuditLog.CN10.AR02", + "Description": "When the URL of a audit log storage bucket's object is accessed publicly then, it should be denied by bucket policy.", "Attributes": [ { - "FamilyName": "Access Control", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.Build.C01 Restrict Allowed Build Agents", - "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain control over the build environment and prevent unauthorized code execution.", + "FamilyName": "Confidentiality", + "FamilyDescription": "Controls designed to protected the confidentiality of Audit Log data.", + "Section": "CCC.AuditLog.CN10 Ensure Audit Bucket is Not Publicly Accessible", + "SubSection": "", + "SubSectionObjective": "Ensure that audit log storage buckets are not publicly accessible to prevent unauthorized exposure of sensitive log data.", "Applicability": [ "tlp-red", - "tlp-amber" + "tlp-amber", + "tlp-green" ], - "Recommendation": "", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -652,14 +2187,296 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-4" + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ "AC-3", - "AC-6" + "SC-7" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_public_access", + "cloudstorage_bucket_uniform_bucket_level_access" + ] + }, + { + "Id": "CCC.Logging.CN01.AR01", + "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be enabled by default and directed to the central sink.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", + "SubSection": "", + "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH07" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.PS-04" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-2", + "AU-3" + ] + } + ] + } + ], + "Checks": [ + "iam_audit_logs_enabled", + "compute_subnet_flow_logs_enabled", + "logging_sink_created" + ] + }, + { + "Id": "CCC.Logging.CN01.AR02", + "Description": "When a new cloud compute resource is deployed, it MUST be configured to forward all relevant logs (e.g., OS, application, service logs) to the central log sink.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", + "SubSection": "", + "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH07" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.PS-04" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-2", + "AU-3" + ] + } + ] + } + ], + "Checks": [ + "logging_sink_created", + "compute_subnet_flow_logs_enabled", + "compute_loadbalancer_logging_enabled", + "compute_network_dns_logging_enabled" + ] + }, + { + "Id": "CCC.Logging.CN02.AR01", + "Description": "When a new log bucket or stream is created, its retention policy MUST be configured in accordance with organisation's data retention policy.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "GV.PO-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-11" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock", + "cloudstorage_bucket_sufficient_retention_period" + ] + }, + { + "Id": "CCC.Logging.CN02.AR02", + "Description": "When a query is performed to retrieve log events older than the number of days defined in the organisation's data retention policy, it MUST return an empty result.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", + "SubSection": "", + "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "GV.PO-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-11" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock", + "cloudstorage_bucket_sufficient_retention_period" + ] + }, + { + "Id": "CCC.Logging.CN03.AR01", + "Description": "When an attempt is made to modify or delete data before the object lock period expires, then the action MUST be denied.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data.", + "Section": "CCC.Logging.CN03 Enable Object Lock On Log Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection using object lock on log storage buckets. This prevents logs from being modified or deleted during the defined retention period, supporting compliance and forensic integrity.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "Configure object lock policy.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH07" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.PS-04" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-9", + "AU-11" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock" + ] + }, + { + "Id": "CCC.Logging.CN04.AR01", + "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify logs.", + "Section": "CCC.Logging.CN04 Restrict Field And Log Type Access", + "SubSection": "", + "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "Review field level access controls on log data.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.PS-04" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6", + "AU-9", + "AC-3", + "PT-2", + "PT-3", + "PT-3" ] } ] @@ -668,25 +2485,26 @@ "Checks": [] }, { - "Id": "CCC.Build.CN02.AR01", - "Description": "Attempt to trigger a build from an unauthorized external service or repository and verify that the build does not start.", + "Id": "CCC.Logging.CN05.AR01", + "Description": "When a log storage bucket is created, the bucket's access control settings MUST explicitly deny public read and write access.", "Attributes": [ { - "FamilyName": "Access Control", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.Build.C02 Restrict Allowed External Services for Build Triggers", - "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or repositories to prevent unauthorized code execution or tampering.", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify logs.", + "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", + "SubSection": "", + "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", "Applicability": [ "tlp-red", - "tlp-amber" + "tlp-amber", + "tlp-green" ], - "Recommendation": "", + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -694,14 +2512,108 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-4" + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ "AC-3", - "AC-6" + "SC-7" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_public_access", + "cloudstorage_bucket_uniform_bucket_level_access" + ] + }, + { + "Id": "CCC.Logging.CN05.AR02", + "Description": "When the URL of a log storage bucket's object is accessed publicly, the action MUST be denied by bucket policy.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify logs.", + "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", + "SubSection": "", + "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green" + ], + "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", + "SC-7" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_public_access", + "cloudstorage_bucket_uniform_bucket_level_access" + ] + }, + { + "Id": "CCC.Logging.CN06.AR01", + "Description": "When a single principal executes an anomalously high number of log queries, an alert MUST be generated.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events.", + "Section": "CCC.Logging.CN06 Detect and Alert on Potential Log Exfiltration", + "SubSection": "", + "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt to exfiltrate log data.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Logging.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-03", + "DE.CM-09" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "CA-7", + "AU-6" ] } ] @@ -710,26 +2622,1273 @@ "Checks": [] }, { - "Id": "CCC.Build.CN03.AR01", - "Description": "Attempt to access the build environment from an external network and verify that access is denied.", + "Id": "CCC.Logging.CN07.AR01", + "Description": "When an audit log event is recorded that corresponds to a modification of the logging service configuration such as disabling a log trail, deleting a log sink, or altering a log forwarding rule, an alert MUST be generated.", "Attributes": [ { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "", - "SubSection": "CCC.Build.C03 Deny External Network Access for Build Environments", - "SubSectionObjective": "Ensure that build environments do not have external network access to prevent unauthorized external access and data exfiltration.", + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain logging-related events.", + "Section": "CCC.Logging.CN07 Detect and Alert on Log Service Tampering", + "SubSection": "", + "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified, or deleted, indicating a defense evasion attempt.", "Applicability": [ - "tlp-red", - "tlp-amber" + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH02", - "CCC.TH05" + "CCC.Core.TH16" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-03", + "DE.CM-09" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "CA-7", + "AU-6" + ] + } + ] + } + ], + "Checks": [ + "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled" + ] + }, + { + "Id": "CCC.Monitor.CN01.AR01", + "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then Rate Limiting MUST be applied and an Audit Alert MUST be generated.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain events from other monitoring services.", + "Section": "CCC.Monitor.CN01 Rate Limiting on External Monitoring", + "SubSection": "", + "SubSectionObjective": "Prevent DoS attacks using External Monitoring tools.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Monitor.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IR-01", + "DE.CM-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-5", + "SC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Monitor.CN02.AR01", + "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied to reduce the network impact of traffic and an alert must triggered.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain events from other monitoring services.", + "Section": "CCC.Monitor.CN02 Rate Limiting on Metric Generation", + "SubSection": "", + "SubSectionObjective": "Prevent Malicious Actor or misconfiguration from flooding services with metric data.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Monitor.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-5(2)", + "CA-7", + "SI-4" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Monitor.CN03.AR01", + "Description": "When external systems have approved access to internal systems not normally available for public access then they MUST be secured to prevent unauthorised access jumping through to the internal systems and only allow access to specific internal services.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", + "Section": "CCC.Monitor.CN03 Access External Monitoring", + "SubSection": "", + "SubSectionObjective": "Control access to Synthetic monitoring solutions using API keys or Certificate based authentication to ensure they don't become an attack path, preventing monitoring systems from forging network requests to gain access to internal systems.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Monitor.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-06", + "PR.IR-01", + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3" + ] + } + ] + } + ], + "Checks": [ + "apikeys_api_restrictions_configured", + "apikeys_key_exists", + "apikeys_key_rotated_in_90_days" + ] + }, + { + "Id": "CCC.Monitor.CN04.AR01", + "Description": "When monitoring dashboards display degraded services which may become potential targets then the dashboard MUST be protected from unauthorised access.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", + "Section": "CCC.Monitor.CN04 Restrict access to Monitoring Dashboards", + "SubSection": "", + "SubSectionObjective": "Control access to Monitoring Dashboards and reports to ensure they don't highlight an attack path.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Monitor.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-09", + "DE.AE-03" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "AC-3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Monitor.CN05.AR01", + "Description": "When monitoring services have generated an alert, the service MUST ensure only authorised responders silence or acknowledge the alert.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", + "Section": "CCC.Monitor.CN05 Restrict access to silence or acknowledge an alert", + "SubSection": "", + "SubSectionObjective": "Ensure only a subset of users can silence or acknowledge alerts to prevent attackers hiding their activity.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IR-01", + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Monitor.CN06.AR01", + "Description": "When systems push metrics or traces they MUST be authenticated for that particular type of metric or trace", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", + "Section": "CCC.Monitor.CN06 Metrics pushed for authorised services only", + "SubSection": "", + "SubSectionObjective": "Use IAM to control which types of metrics or traces can be pushed by different system to avoid a compromised system pushing fabricated metrics about a different service", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Monitor.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-5" + ] + } + ] + } + ], + "Checks": [ + "iam_sa_no_administrative_privileges", + "iam_sa_no_user_managed_keys", + "compute_instance_default_service_account_in_use", + "compute_instance_default_service_account_in_use_with_full_api_access" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR01", + "Description": "When a request is made to read a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "kms_key_not_publicly_accessible" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR02", + "Description": "When a request is made to read an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "kms_key_not_publicly_accessible" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR03", + "Description": "When a request is made to write to a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "kms_key_not_publicly_accessible" + ] + }, + { + "Id": "CCC.ObjStor.CN01.AR04", + "Description": "When a request is made to write to an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", + "SubSection": "", + "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption, or sensitive data decryption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-03", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "kms_key_not_publicly_accessible" + ] + }, + { + "Id": "CCC.ObjStor.CN03.AR01", + "Description": "When an object storage bucket deletion is attempted, the bucket MUST be fully recoverable for a set time-frame after deletion is requested.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", + "SubSection": "", + "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_soft_delete_enabled", + "cloudstorage_bucket_log_retention_policy_lock" + ] + }, + { + "Id": "CCC.ObjStor.CN03.AR02", + "Description": "When an attempt is made to modify the retention policy for an object storage bucket, the service MUST prevent the policy from being modified.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", + "SubSection": "", + "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock" + ] + }, + { + "Id": "CCC.ObjStor.CN04.AR01", + "Description": "When an object is uploaded to the object storage system, the object MUST automatically receive a default retention policy that prevents premature deletion or modification.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN04 Objects have an Effective Retention Policy by Default", + "SubSection": "", + "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + }, + { + "ReferenceId": "CCC.ObjStor", + "Identifiers": [ + "CCC.ObjStor.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_sufficient_retention_period", + "cloudstorage_bucket_log_retention_policy_lock" + ] + }, + { + "Id": "CCC.ObjStor.CN04.AR02", + "Description": "When an attempt is made to delete or modify an object that is subject to an active retention policy, the service MUST prevent the action from being completed.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN04 Objects have an Effective Retention Policy by Default", + "SubSection": "", + "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + }, + { + "ReferenceId": "CCC.ObjStor", + "Identifiers": [ + "CCC.ObjStor.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_log_retention_policy_lock" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR01", + "Description": "When an object is uploaded to the object storage bucket, the object MUST be stored with a unique identifier.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_versioning_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR02", + "Description": "When an object is modified, the service MUST assign a new unique identifier to the modified object to differentiate it from the previous version.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_versioning_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR03", + "Description": "When an object is modified, the service MUST allow for recovery of previous versions of the object.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_versioning_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN05.AR04", + "Description": "When an object is deleted, the service MUST retain other versions of the object to allow for recovery of previous versions.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN05 Versioning is Enabled for All Objects in the Bucket", + "SubSection": "", + "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "DSP-17" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_versioning_enabled" + ] + }, + { + "Id": "CCC.ObjStor.CN07.AR01", + "Description": "The object storage service MUST support a configuration option that requires MFA to be successfully completed before any object deletion can be attempted, regardless of the request interface.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN07 Multi-Factor Authentication Is Required for Object Deletion", + "SubSection": "", + "SubSectionObjective": "Ensure that deletion of objects stored in the object storage system is protected by multi-factor authentication (MFA), reducing the risk of accidental, unauthorized, or compromised-credential–based data destruction.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06", + "CCC.Core.TH17" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "IAM-12" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.ObjStor.CN07.AR02", + "Description": "When MFA deletion protection is enabled on a bucket or object namespace, the service MUST deny any deletion request from an identity that has not satisfied the MFA requirement at the time of the request.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN07 Multi-Factor Authentication Is Required for Object Deletion", + "SubSection": "", + "SubSectionObjective": "Ensure that deletion of objects stored in the object storage system is protected by multi-factor authentication (MFA), reducing the risk of accidental, unauthorized, or compromised-credential–based data destruction.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06", + "CCC.Core.TH17" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "IAM-12" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.ObjStor.CN07.AR03", + "Description": "When an attempt is made to delete an object, the service's audit logs MUST clearly record each deletion attempt, including whether MFA was required and whether validation was met.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + "Section": "CCC.ObjStor.CN07 Multi-Factor Authentication Is Required for Object Deletion", + "SubSection": "", + "SubSectionObjective": "Ensure that deletion of objects stored in the object storage system is protected by multi-factor authentication (MFA), reducing the risk of accidental, unauthorized, or compromised-credential–based data destruction.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01", + "CCC.Core.TH06", + "CCC.Core.TH17" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "DSP-16", + "IAM-12" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.ObjStor.CN02.AR01", + "Description": "When a permission set is allowed for an object in a bucket, the service MUST allow the same permission set to access all objects in the same bucket.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources.", + "Section": "CCC.ObjStor.CN02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", + "SubSection": "", + "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_uniform_bucket_level_access" + ] + }, + { + "Id": "CCC.ObjStor.CN02.AR02", + "Description": "When a permission set is denied for an object in a bucket, the service MUST deny the same permission set to access all objects in the same bucket.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources.", + "Section": "CCC.ObjStor.CN02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", + "SubSection": "", + "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08" + ] + } + ] + } + ], + "Checks": [ + "cloudstorage_bucket_uniform_bucket_level_access" + ] + }, + { + "Id": "CCC.LB.CN01.AR01", + "Description": "When a single client sends more than 2000 requests within any 5-minute sliding window, the load balancer MUST throttle all subsequent requests from that client for at least 60 seconds.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.", + "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", + "SubSection": "", + "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Implement per-IP token-bucket limits with and verify via synthetic traffic tests.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH01", + "CCC.LB.TH09" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-1", + "PR.AC-7", + "PR.PT-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-6", + "SC-5", + "AC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.LB.CN01.AR02", + "Description": "When throttling is invoked, the load balancer MUST record the event in the access log within 5 minutes for alerting and trend analysis.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.", + "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", + "SubSection": "", + "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Enable access logging and configure metric filters on HTTP 429 counts to trigger alerts.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH01", + "CCC.LB.TH09" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-1", + "PR.AC-7", + "PR.PT-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-6", + "SC-5", + "AC-7" + ] + } + ] + } + ], + "Checks": [ + "compute_loadbalancer_logging_enabled" + ] + }, + { + "Id": "CCC.LB.CN06.AR01", + "Description": "When more than 10 percent of targets change from healthy to unhealthy within five minutes, an alert MUST be issued.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity.", + "Section": "CCC.LB.CN06 Secure Health-Check Telemetry", + "SubSection": "", + "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on abnormal status changes.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Instrument metrics for health check results and target removal events. Configure monitoring alarms to alert on abnormal spikes in unhealthy targets.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.AE-2" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4" + ] + } + ] + } + ], + "Checks": [ + "compute_loadbalancer_logging_enabled" + ] + }, + { + "Id": "CCC.LB.CN04.AR01", + "Description": "When routing weights change, the request MUST originate from an explicitly defined and trusted identity and MUST be logged.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.", + "Section": "CCC.LB.CN04 Enforce Distribution Policies", + "SubSection": "", + "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified only by trusted identities.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Define a list of trusted principals allowed to modify routing configurations. Enforce via conditional access policies, and log changes using audit logging.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3" + ] + } + ] + } + ], + "Checks": [ + "iam_audit_logs_enabled", + "compute_loadbalancer_logging_enabled" + ] + }, + { + "Id": "CCC.LB.CN05.AR01", + "Description": "When stickiness is enabled, session cookies MUST expire within 30 minutes of inactivity.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.", + "Section": "CCC.LB.CN05 Validate Session Affinity", + "SubSection": "", + "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking risks.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Audit CCC.LB.CP15 parameters via configuration scans.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-7" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-23" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.LB.CN09.AR01", + "Description": "When an API call originates outside the approved CIDR set, the request MUST be denied.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can change or query load-balancer resources.", + "Section": "CCC.LB.CN09 Restrict Management API Access", + "SubSection": "", + "SubSectionObjective": "Limit load-balancer API calls to authorised identities and trusted networks.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Combine VPC endpoints with IAM condition-key filters for protected APIs.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH08" ] } ], @@ -743,102 +3902,7 @@ { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-7", - "SC-5" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_instance_public_ip", - "cloudstorage_bucket_public_access", - "cloudsql_instance_public_ip", - "cloudsql_instance_public_access" - ] - }, - { - "Id": "CCC.CntrReg.CN01.AR01", - "Description": "Attempt to push an artifact with known vulnerabilities to the registry and observe if it is flagged or rejected by the vulnerability scanning process.", - "Attributes": [ - { - "FamilyName": "Risk Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CntrReg.CN01 Implement Vulnerability Scanning for Artifacts", - "SubSection": "", - "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for vulnerabilities to identify and remediate security issues before deployment.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.CntrReg.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "ID.RA-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "RA-5", - "SI-5" - ] - } - ] - } - ], - "Checks": [ - "artifacts_container_analysis_enabled", - "gcr_container_scanning_enabled" - ] - }, - { - "Id": "CCC.DataWar.CN01.AR01", - "Description": "Attempt to access underlying database tables directly without using managed views and verify that access is denied.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.CN01 Enforce Use of Managed Views for Data Access", - "SubSection": "", - "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users from accessing underlying tables directly and enforcing consistent security policies.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "AC-6" + "SC-7" ] } ] @@ -847,25 +3911,26 @@ "Checks": [] }, { - "Id": "CCC.DataWar.CN02.AR01", - "Description": "Attempt to query sensitive columns without the necessary permissions and verify that access is denied or data is masked.", + "Id": "CCC.LB.CN02.AR01", + "Description": "When concurrent connections reach 80 percent of capacity, the autoscaling group MUST add at least one instance within five minutes.", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.CN02 Enforce Column-Level Security Policies", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer.", + "Section": "CCC.LB.CN02 Auto-Scale Load Balancer Capacity", "SubSection": "", - "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles, preventing unauthorized access to sensitive information.", + "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic spikes.", "Applicability": [ - "tlp-red", - "tlp-amber" + "tlp-green", + "tlp-amber", + "tlp-red" ], - "Recommendation": "", + "Recommendation": "Enable autoscaling policies.", "SectionThreatMappings": [ { - "ReferenceId": "CCC", + "ReferenceId": "LB", "Identifiers": [ - "CCC.TH01" + "CCC.LB.TH09" ] } ], @@ -873,14 +3938,13 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-4" + "ID.BE-5" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AC-3", - "AC-6" + "CP-10" ] } ] @@ -889,25 +3953,26 @@ "Checks": [] }, { - "Id": "CCC.DataWar.CN03.AR01", - "Description": "Attempt to query data rows that the user should not have access to and verify that access is denied or data is not returned.", + "Id": "CCC.LB.CN07.AR01", + "Description": "When responses pass through the load balancer, the \"Server\" header MUST be replaced with \"lb\".", "Attributes": [ { "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.DataWar.CN03 Enforce Row-Level Security Policies", + "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer.", + "Section": "CCC.LB.CN07 Scrub Sensitive Headers", "SubSection": "", - "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes, preventing unauthorized access to specific subsets of data.", + "SubSectionObjective": "Remove headers that disclose internal details or software versions from HTTP responses.", "Applicability": [ - "tlp-red", - "tlp-amber" + "tlp-green", + "tlp-amber", + "tlp-red" ], - "Recommendation": "", + "Recommendation": "Configure header-transformation rules.", "SectionThreatMappings": [ { - "ReferenceId": "CCC", + "ReferenceId": "LB", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH15" ] } ], @@ -915,50 +3980,286 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-4" + "PR.DS-2" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AC-3", - "AC-6" + "SC-13" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.LB.CN08.AR01", + "Description": "When a certificate is within 30 days of expiry, automated renewal MUST complete and deploy a new certificate within 24 hours.", + "Attributes": [ + { + "FamilyName": "Encryption", + "FamilyDescription": "Controls that ensure trustworthy TLS certificates and ciphers.", + "Section": "CCC.LB.CN08 Automate Certificate Renewal", + "SubSection": "", + "SubSectionObjective": "Maintain valid TLS certificates by automating renewal and deployment before expiry.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "Use certificate-manager auto-renewal workflows.", + "SectionThreatMappings": [ + { + "ReferenceId": "LB", + "Identifiers": [ + "CCC.LB.TH07" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.DS-6" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-17" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.VPC.CN01.AR01", + "Description": "When a subscription is created, the subscription MUST NOT contain default network resources.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.VPC.CN01 Restrict Default Network Creation", + "SubSection": "", + "SubSectionObjective": "Restrict the automatic creation of default virtual networks and related resources during subscription initialization to avoid insecure default configurations and enforce custom network policies.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.VPC.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-5" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "TVM-02" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.12.3.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7" + ] + } + ] + } + ], + "Checks": [ + "compute_network_default_in_use" + ] + }, + { + "Id": "CCC.VPC.CN02.AR01", + "Description": "When a resource is created in a public subnet, that resource MUST NOT be assigned an external IP address by default.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.VPC.CN02 Limit Resource Creation in Public Subnet", + "SubSection": "", + "SubSectionObjective": "Restrict the creation of resources in the public subnet with direct access to the internet to minimize attack surfaces.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.VPC.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "SEF-05" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.1.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-4" ] } ] } ], "Checks": [ - "iam_no_service_roles_at_project_level", - "iam_sa_no_administrative_privileges", - "iam_role_sa_enforce_separation_of_duties", - "iam_role_kms_enforce_separation_of_duties", - "iam_account_access_approval_enabled", - "iam_audit_logs_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "cloudsql_instance_postgres_enable_pgaudit_flag", - "cloudsql_instance_postgres_log_connections_flag", - "cloudsql_instance_postgres_log_disconnections_flag", - "cloudsql_instance_postgres_log_min_messages_flag", - "cloudsql_instance_postgres_log_min_duration_statement_flag", - "cloudsql_instance_postgres_log_error_verbosity_flag", - "cloudsql_instance_postgres_log_min_error_statement_flag", - "cloudsql_instance_public_ip", - "cloudsql_instance_private_ip_assignment", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_instance_default_service_account_in_use", - "compute_instance_default_service_account_in_use_with_full_api_access", "compute_instance_public_ip" ] }, + { + "Id": "CCC.VPC.CN03.AR01", + "Description": "When a VPC peering connection is requested, the service MUST prevent connections from VPCs that are not explicitly allowed.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.VPC.CN03 Restrict VPC Peering to Authorized Accounts", + "SubSection": "", + "SubSectionObjective": "Ensure VPC peering connections are only established with explicitly authorized destinations to limit network exposure and enforce boundary controls.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.VPC.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IVS-01" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.1.3" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-4" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.VPC.CN04.AR01", + "Description": "When any network traffic goes to or from an interface in the VPC, the service MUST capture and log all relevant information.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.VPC.CN04 Enforce VPC Flow Logs on VPCs", + "SubSection": "", + "SubSectionObjective": "Ensure VPCs are configured with flow logs enabled to capture traffic information.", + "Applicability": [ + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.VPC.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.PT-1" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.12.4.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AU-2" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IVS-06" + ] + } + ] + } + ], + "Checks": [ + "compute_subnet_flow_logs_enabled" + ] + }, { "Id": "CCC.KeyMgmt.CN01.AR01", "Description": "When a key version is scheduled for deletion or disabled, an alert MUST be generated within five minutes.", "Attributes": [ { - "FamilyName": "Logging and Metrics Publication", + "FamilyName": "Logging and Monitoring", "FamilyDescription": "Controls that collect, alert, and retain key-management events.", "Section": "CCC.KeyMgmt.CN01 Alert on Key-version Changes", "SubSection": "", @@ -1117,429 +4418,29 @@ ] } ], - "Checks": [ - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled" - ] - }, - { - "Id": "CCC.LB.CN01.AR01", - "Description": "When a single client sends more than 2000 requests within any 5-minute sliding window, the load balancer MUST throttle all subsequent requests from that client for at least 60 seconds.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", - "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", - "SubSection": "", - "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement per-IP token-bucket limits with and verify via synthetic traffic tests. ", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH01", - "CCC.LB.TH09" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-1", - "PR.AC-7", - "PR.PT-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-6", - "SC-5", - "AC-7" - ] - } - ] - } - ], - "Checks": [ - "compute_loadbalancer_logging_enabled", - "compute_subnet_flow_logs_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled" - ] - }, - { - "Id": "CCC.LB.CN01.AR02", - "Description": "When throttling is invoked, the load balancer MUST record the event in the access log within 5 minutes for alerting and trend analysis.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", - "Section": "CCC.LB.CN01 Enforce and Detect Rate Limiting", - "SubSection": "", - "SubSectionObjective": "Detect and throttle malicious or excessive requests to prevent downstream resource exhaustion and brute-force activity.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Enable access logging and configure metric filters on HTTP 429 counts to trigger alerts. ", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH01", - "CCC.LB.TH09" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-1", - "PR.AC-7", - "PR.PT-4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-6", - "SC-5", - "AC-7" - ] - } - ] - } - ], - "Checks": [ - "compute_loadbalancer_logging_enabled", - "logging_sink_created" - ] - }, - { - "Id": "CCC.LB.CN06.AR01", - "Description": "When more than 10 percent of targets change from healthy to unhealthy within five minutes, an alert MUST be issued.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that detect anomalous traffic and record load-balancer activity. ", - "Section": "CCC.LB.CN06 Secure Health-Check Telemetry", - "SubSection": "", - "SubSectionObjective": "Monitor health-check endpoints for tampering and alert on abnormal status changes.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Instrument metrics for health check results and target removal events. Configure monitoring alarms to alert on abnormal spikes in unhealthy targets. ", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-4" - ] - } - ] - } - ], - "Checks": [ - "compute_loadbalancer_logging_enabled", - "logging_sink_created", - "compute_subnet_flow_logs_enabled" - ] - }, - { - "Id": "CCC.LB.CN04.AR01", - "Description": "When routing weights change, the request MUST originate from an explicitly defined and trusted identity and MUST be logged.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", - "Section": "CCC.LB.CN04 Enforce Distribution Policies", - "SubSection": "", - "SubSectionObjective": "Ensure traffic-splitting weights and algorithms are modified only by trusted identities.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Define a list of trusted principals allowed to modify routing configurations. Enforce via conditional access policies, and log changes using audit logging. ", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "iam_audit_logs_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "compute_loadbalancer_logging_enabled", - "logging_sink_created", - "iam_no_service_roles_at_project_level", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_no_administrative_privileges" - ] - }, - { - "Id": "CCC.LB.CN05.AR01", - "Description": "When stickiness is enabled, session cookies MUST expire within 30 minutes of inactivity.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", - "Section": "CCC.LB.CN05 Validate Session Affinity", - "SubSection": "", - "SubSectionObjective": "Configure session persistence to minimise fixation and hijacking risks.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Audit CCC.LB.F15 parameters via configuration scans.", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-23" - ] - } - ] - } - ], - "Checks": [ - "iam_no_service_roles_at_project_level", - "iam_sa_no_administrative_privileges", - "iam_role_kms_enforce_separation_of_duties", - "iam_role_sa_enforce_separation_of_duties", - "iam_account_access_approval_enabled", - "iam_audit_logs_enabled", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled" - ] - }, - { - "Id": "CCC.LB.CN09.AR01", - "Description": "When an API call originates outside the approved CIDR set, the request MUST be denied.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can change or query load-balancer resources. ", - "Section": "CCC.LB.CN09 Restrict Management API Access", - "SubSection": "", - "SubSectionObjective": "Limit load-balancer API calls to authorised identities and trusted networks.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Combine VPC endpoints with IAM condition-key filters for protected APIs.", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH08" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "iam_account_access_approval_enabled", - "iam_audit_logs_enabled", - "iam_no_service_roles_at_project_level", - "iam_role_kms_enforce_separation_of_duties", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_no_administrative_privileges", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled" - ] - }, - { - "Id": "CCC.LB.CN02.AR01", - "Description": "When concurrent connections reach 80 percent of capacity, the autoscaling group MUST add at least one instance within five minutes.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", - "Section": "CCC.LB.CN02 Auto-Scale Load Balancer Capacity", - "SubSection": "", - "SubSectionObjective": "Expand load-balancer capacity to maintain availability during traffic spikes.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Enable autoscaling policies.", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH09" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "ID.BE-5" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CP-10" - ] - } - ] - } - ], "Checks": [] }, { - "Id": "CCC.LB.CN07.AR01", - "Description": "When responses pass through the load balancer, the \"Server\" header MUST be replaced with \"lb\".", + "Id": "CCC.SecMgmt.CN01.AR01", + "Description": "Attempt to use an outdated version of a secret after its rotation period has passed and verify that access is denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "Controls that preserve availability and confidentiality of traffic processed by the load balancer. ", - "Section": "CCC.LB.CN07 Scrub Sensitive Headers", + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SecMgmt.CN01 Enforce Automatic Secret Rotation", "SubSection": "", - "SubSectionObjective": "Remove headers that disclose internal details or software versions from HTTP responses.", + "SubSectionObjective": "Ensure that secrets are automatically rotated on a defined schedule to reduce the risk of secret compromise and unauthorized access.", "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-red", + "tlp-amber" ], - "Recommendation": "Configure header-transformation rules.", + "Recommendation": "", "SectionThreatMappings": [ { - "ReferenceId": "LB", + "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH15" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-13" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.LB.CN08.AR01", - "Description": "When a certificate is within 30 days of expiry, automated renewal MUST complete and deploy a new certificate within 24 hours.", - "Attributes": [ - { - "FamilyName": "Encryption", - "FamilyDescription": "Controls that ensure trustworthy TLS certificates and ciphers.", - "Section": "CCC.LB.CN08 Automate Certificate Renewal", - "SubSection": "", - "SubSectionObjective": "Maintain valid TLS certificates by automating renewal and deployment before expiry.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Use certificate-manager auto-renewal workflows.", - "SectionThreatMappings": [ - { - "ReferenceId": "LB", - "Identifiers": [ - "CCC.LB.TH07" + "CCC.Core.TH01", + "CCC.Core.TH14" ] } ], @@ -1553,7 +4454,8 @@ { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-17" + "SC-12", + "SC-28" ] } ] @@ -1562,214 +4464,26 @@ "Checks": [] }, { - "Id": "CCC.Logging.CN01.AR01", - "Description": "When a new cloud account is created, provider-level audit and network flow logging MUST be enabled by default and directed to the central sink.", + "Id": "CCC.SecMgmt.CN02.AR01", + "Description": "Attempt to retrieve a secret from an unauthorized region and verify that access is denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SecMgmt.CN02 Enforce Secret Replication Policies", "SubSection": "", - "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH07" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PS-04" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3" - ] - } - ] - } - ], - "Checks": [ - "iam_audit_logs_enabled", - "compute_subnet_flow_logs_enabled", - "logging_sink_created" - ] - }, - { - "Id": "CCC.Logging.CN01.AR02", - "Description": "When a new cloud compute resource is deployed, it MUST be configured to forward all relevant logs (e.g., OS, application, service logs) to the central log sink.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN01 Centralized and Comprehensive Log Aggregation", - "SubSection": "", - "SubSectionObjective": "Ensure all operational and security logs from across the cloud environment, including applications, operating systems, network traffic, and cloud service activity, are captured automatically and streamed to a central, secure log management service.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH07" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PS-04" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3" - ] - } - ] - } - ], - "Checks": [ - "logging_sink_created", - "cloudstorage_bucket_log_retention_policy_lock", - "iam_audit_logs_enabled", - "compute_subnet_flow_logs_enabled", - "compute_network_dns_logging_enabled", - "compute_loadbalancer_logging_enabled" - ] - }, - { - "Id": "CCC.Logging.CN02.AR01", - "Description": "When a new log bucket or stream is created, its retention policy MUST be configured in accordance with organisation's data retention policy.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", - "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "GV.PO-01" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-11" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.Logging.CN02.AR02", - "Description": "When a query is performed to retrieve log events older than the number of days defined in the organisation's data retention policy, it MUST return an empty result.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN02 Enforce Data Retention Policy for Logs", - "SubSection": "", - "SubSectionObjective": "Ensure that the retention period configured for logs aligns with the organization's data retention policy.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "GV.PO-01" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-11" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.AuditLog.CN08.AR01", - "Description": "When an attempt is made to modify or delete data before the object lock period expires, then the action MUST be denied.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "Controls related to the confidentiality, integrity and availability of log data. ", - "Section": "CCC.Logging.CN03 Enable Object Lock On Log Bucket", - "SubSection": "", - "SubSectionObjective": "Ensure log immutability by enabling Write Once, Read Many (WORM) protection using object lock on log storage buckets. This prevents logs from being modified or deleted during the defined retention period, supporting compliance and forensic integrity.", + "SubSectionObjective": "Ensure that secrets are replicated only to authorized locations as per organizational data residency and compliance requirements.", "Applicability": [ "tlp-red", "tlp-amber" ], - "Recommendation": "Configure object lock policy. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07" + "CCC.Core.TH03", + "CCC.Core.TH04" ] } ], @@ -1777,98 +4491,7 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.PS-04" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-9", - "AU-11" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.AuditLog.CN04.AR01", - "Description": "When restricted fields are accessed by unauthorized users, then those fields MUST remain masked.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs. ", - "Section": "CCC.Logging.CN04 Restrict Field And Log Type Access", - "SubSection": "", - "SubSectionObjective": "Configure access to logs to follow the principle of least privilege in particular where technically possible limit the log fields users have access to to prevent accidental exposure to sensitive information such as PII.", - "Applicability": [ - "tlp-red", - "tlp-amber" - ], - "Recommendation": "Review field level access controls on log data. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Logging.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PS-04" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-6", - "AU-9", - "AC-3", - "PT-2", - "PT-3", - "PT-3" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.Logging.CN05.AR01", - "Description": "When a log storage bucket is created, the bucket's access control settings MUST explicitly deny public read and write access.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs. ", - "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", - "SubSection": "", - "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", - "Applicability": [ - "tlp-red", - "tlp-amber", - "tlp-green" - ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AA-05" + "PR.DS-5" ] }, { @@ -1881,946 +4504,28 @@ ] } ], - "Checks": [ - "cloudstorage_bucket_public_access", - "cloudstorage_bucket_uniform_bucket_level_access", - "cloudstorage_bucket_log_retention_policy_lock", - "logging_sink_created" - ] + "Checks": [] }, { - "Id": "CCC.Logging.CN05.AR02", - "Description": "When the URL of a log storage bucket's object is accessed publicly, the action MUST be denied by bucket policy.", + "Id": "CCC.DataWar.CN01.AR01", + "Description": "Attempt to access underlying database tables directly without using managed views and verify that access is denied.", "Attributes": [ { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls that restrict who can access and modify logs. ", - "Section": "CCC.Logging.CN05 Ensure Log Bucket is Not Publicly Accessible", + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.DataWar.CN01 Enforce Use of Managed Views for Data Access", "SubSection": "", - "SubSectionObjective": "Ensure that log storage buckets are not publicly accessible to prevent unauthorized access to sensitive log data. In addition, logs should be replicated to another cloud region to enhance availability, durability, and support disaster recovery requirements.", + "SubSectionObjective": "Ensure that data access is provided through managed views, restricting users from accessing underlying tables directly and enforcing consistent security policies.", "Applicability": [ "tlp-red", - "tlp-amber", - "tlp-green" - ], - "Recommendation": "Configure bucket policies and access control lists (ACLs) to restrict public access. Regularly review bucket permissions to ensure no public access has been inadvertently granted. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AA-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3", - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_public_access", - "cloudstorage_bucket_uniform_bucket_level_access" - ] - }, - { - "Id": "CCC.Logging.CN06.AR01", - "Description": "When a single principal executes an anomalously high number of log queries, an alert MUST be generated.", - "Attributes": [ - { - "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", - "Section": "CCC.Logging.CN06 Detect and Alert on Potential Log Exfiltration", - "SubSection": "", - "SubSectionObjective": "Identify and alert on anomalous data access patterns that may indicate an attempt to exfiltrate log data.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-amber" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.Logging.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-03", - "DE.CM-09" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-4", - "CA-7", - "AU-6" - ] - } - ] - } - ], - "Checks": [ - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled", - "logging_sink_created" - ] - }, - { - "Id": "CCC.Logging.CN07.AR01", - "Description": "When an audit log event is recorded that corresponds to a modification of the logging service configuration such as disabling a log trail, deleting a log sink, or altering a log forwarding rule, an alert MUST be generated.", - "Attributes": [ - { - "FamilyName": "Logging and Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain logging-related events. ", - "Section": "CCC.Logging.CN07 Detect and Alert on Log Service Tampering", - "SubSection": "", - "SubSectionObjective": "Alert when any component of the critical logging infrastructure is disabled, modified, or deleted, indicating a defense evasion attempt.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-03", - "DE.CM-09" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-4", - "CA-7", - "AU-6" - ] - } - ] - } - ], - "Checks": [ - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR01", - "Description": "When a request is made to read a protected bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR02", - "Description": "When a request is made to read a protected object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.ObjStor.CN01.AR03", - "Description": "When a request is made to write to a bucket, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled", - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption", - "dataproc_encrypted_with_cmks_disabled" - ] - }, - { - "Id": "CCC.ObjStor.CN01.AR04", - "Description": "When a request is made to write to an object, the service MUST prevent any request using KMS keys not listed as trusted by the organization.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN01 Prevent Unencrypted Requests", - "SubSection": "CCC.ObjStor.C01 Prevent Requests to Buckets or Objects with Untrusted KMS Keys", - "SubSectionObjective": "Prevent any requests to object storage buckets or objects using untrusted KMS keys to protect against unauthorized data encryption that can impact data availability and integrity.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01", - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-04", - "DCS-06" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled", - "dataproc_encrypted_with_cmks_disabled", - "compute_instance_encryption_with_csek_enabled", - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption" - ] - }, - { - "Id": "CCC.ObjStor.CN03.AR01", - "Description": "When an object storage bucket deletion is attempted, the bucket MUST be fully recoverable for a set time-frame after deletion is requested.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", - "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.ObjStor.CN03.AR02", - "Description": "When an attempt is made to modify the retention policy for an object storage bucket, the service MUST prevent the policy from being modified.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "CCC.ObjStor.C03 Prevent Bucket Deletion Through Irrevocable Bucket Retention Policy", - "SubSectionObjective": "Ensure that object storage bucket is not deleted after creation, and that the preventative measure cannot be unset.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.ObjStor.CN04.AR01", - "Description": "When an object is uploaded to the object storage system, the object MUST automatically receive a default retention policy that prevents premature deletion or modification.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN04 Log All Access and Changes", - "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", - "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.ObjStor.CN04.AR02", - "Description": "When an attempt is made to delete or modify an object that is subject to an active retention policy, the service MUST prevent the action from being completed.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN04 Log All Access and Changes", - "SubSection": "CCC.ObjStor.C04 Objects have an Effective Retention Policy by Default", - "SubSectionObjective": "Ensure that all objects stored in the object storage system have a retention policy applied by default, preventing premature deletion or modification of objects and ensuring compliance with data retention regulations.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.ObjStor.CN05.AR01", - "Description": "When an object is uploaded to the object storage bucket, the object MUST be stored with a unique identifier.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.ObjStor.CN05.AR02", - "Description": "When an object is modified, the service MUST assign a new unique identifier to the modified object to differentiate it from the previous version.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.ObjStor.CN05.AR03", - "Description": "When an object is modified, the service MUST allow for recovery of previous versions of the object.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.ObjStor.CN05.AR04", - "Description": "When an object is deleted, the service MUST retain other versions of the object to allow for recovery of previous versions.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN05 Prevent Access from Untrusted Entities", - "SubSection": "CCC.ObjStor.C05 Versioning is Enabled for All Objects in the Bucket", - "SubSectionObjective": "Ensure that versioning is enabled for all objects stored in the object storage bucket to enable recovery of previous versions of objects in case of loss or corruption.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.1.4" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-28", - "CP-10" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-16" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.ObjStor.CN06.AR01", - "Description": "When an object storage bucket is accessed, the service MUST store access logs in a separate data store.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN06 Prevent Deployment in Restricted Regions", - "SubSection": "CCC.ObjStor.C06 Access Logs are Stored in a Separate Data Store", - "SubSectionObjective": "Ensure that access logs for object storage buckets are stored in a separate data store to protect against unauthorized access, tampering, or deletion of logs (Logbuckets are exempt from this requirement, but must be tlp-red).", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH07", - "CCC.TH09" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-6" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-07", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2022 A.8.15.0" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-9", - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock", - "logging_sink_created" - ] - }, - { - "Id": "CCC.ObjStor.CN02.AR01", - "Description": "When a permission set is allowed for an object in a bucket, the service MUST allow the same permission set to access all objects in the same bucket.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", - "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", - "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -2831,54 +4536,38 @@ "PR.AC-4" ] }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.1" - ] - }, { "ReferenceId": "NIST_800_53", "Identifiers": [ "AC-3", "AC-6" ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-09" - ] } ] } ], - "Checks": [ - "cloudstorage_bucket_uniform_bucket_level_access" - ] + "Checks": [] }, { - "Id": "CCC.ObjStor.CN02.AR02", - "Description": "When a permission set is denied for an object in a bucket, the service MUST deny the same permission set to access all objects in the same bucket.", + "Id": "CCC.DataWar.CN02.AR01", + "Description": "Attempt to query sensitive columns without the necessary permissions and verify that access is denied or data is masked.", "Attributes": [ { - "FamilyName": "Identity and Access Management", + "FamilyName": "Data", "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.CN02 Ensure Data Encryption at Rest for All Stored Data", - "SubSection": "CCC.ObjStor.C02 Enforce Uniform Bucket-level Access to Prevent Inconsistent Permissions", - "SubSectionObjective": "Ensure that uniform bucket-level access is enforced across all object storage buckets. This prevents the use of ad-hoc or inconsistent object-level permissions, ensuring centralized, consistent, and secure access management in accordance with the principle of least privilege.", + "Section": "CCC.DataWar.CN02 Enforce Column-Level Security Policies", + "SubSection": "", + "SubSectionObjective": "Ensure that access to sensitive data columns is restricted based on user roles, preventing unauthorized access to sensitive information.", "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-red", + "tlp-amber" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -2890,9 +4579,45 @@ ] }, { - "ReferenceId": "ISO_27001", + "ReferenceId": "NIST_800_53", "Identifiers": [ - "2013 A.9.4.1" + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.DataWar.CN03.AR01", + "Description": "Attempt to query data rows that the user should not have access to and verify that access is denied or data is not returned.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.DataWar.CN03 Enforce Row-Level Security Policies", + "SubSection": "", + "SubSectionObjective": "Ensure that access to data rows is restricted based on user roles or attributes, preventing unauthorized access to specific subsets of data.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" ] }, { @@ -2901,502 +4626,12 @@ "AC-3", "AC-6" ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DCS-09" - ] - } - ] - } - ], - "Checks": [ - "cloudstorage_bucket_uniform_bucket_level_access" - ] - }, - { - "Id": "CCC.Monitor.CN01.AR01", - "Description": "When an External Monitoring system exceeds the anticipated rate of monitoring checks then Rate Limiting MUST be applied and an Audit Alert MUST be generated.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain events from other monitoring services.", - "Section": "CCC.Monitor.CN01 Rate Limiting on External Monitoring", - "SubSection": "", - "SubSectionObjective": "Prevent DoS attacks using External Monitoring tools.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Monitor.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IR-01", - "DE.CM-01" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-5", - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "iam_audit_logs_enabled", - "logging_sink_created", - "cloudstorage_bucket_log_retention_policy_lock", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled" - ] - }, - { - "Id": "CCC.Monitor.CN02.AR01", - "Description": "When an Custom or User-Defined Metric starts to flood a collector, then a rate limit MUST be applied to reduce the network impact of traffic and an alert must triggered.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "Controls that collect, alert, and retain events from other monitoring services.", - "Section": "CCC.Monitor.CN02 Rate Limiting on Metric Generation", - "SubSection": "", - "SubSectionObjective": "Prevent Malicious Actor or misconfiguration from flooding services with metric data.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Monitor.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-01" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-5(2)", - "CA-7", - "SI-4" - ] - } - ] - } - ], - "Checks": [ - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "iam_audit_logs_enabled", - "compute_loadbalancer_logging_enabled", - "compute_network_dns_logging_enabled", - "compute_subnet_flow_logs_enabled" - ] - }, - { - "Id": "CCC.Monitor.CN03.AR01", - "Description": "When external systems have approved access to internal systems not normally available for public access then they MUST be secured to prevent unauthorised access jumping through to the internal systems and only allow access to specific internal services.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", - "Section": "CCC.Monitor.CN03 Access External Monitoring", - "SubSection": "", - "SubSectionObjective": "Control access to Synthetic monitoring solutions using API keys or Certificate based authentication to ensure they don't become an attack path, preventing monitoring systems from forging network requests to gain access to internal systems.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Monitor.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-06", - "PR.IR-01", - "PR.AA-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "apikeys_api_restrictions_configured", - "apikeys_key_exists", - "apikeys_key_rotated_in_90_days" - ] - }, - { - "Id": "CCC.Monitor.CN04.AR01", - "Description": "When monitoring dashboards display degraded services which may become potential targets then the dashboard MUST be protected from unauthorised access.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", - "Section": "CCC.Monitor.CN04 Restrict access to Monitoring Dashboards", - "SubSection": "", - "SubSectionObjective": "Control access to Monitoring Dashboards and reports to ensure they don't highlight an attack path.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Monitor.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.CM-09", - "DE.AE-03" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SI-4", - "AC-3" - ] } ] } ], "Checks": [] }, - { - "Id": "CCC.Monitor.CN05.AR01", - "Description": "When monitoring services have generated an alert, the service MUST ensure only authorised responders silence or acknowledge the alert.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", - "Section": "CCC.Monitor.CN05 Restrict access to silence or acknowledge an alert", - "SubSection": "", - "SubSectionObjective": "Ensure only a subset of users can silence or acknowledge alerts to prevent attackers hiding their activity.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH10" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.IR-01", - "PR.AA-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled", - "logging_sink_created", - "iam_organization_essential_contacts_configured" - ] - }, - { - "Id": "CCC.Monitor.CN06.AR01", - "Description": "When systems push metrics or traces they MUST be authenticated for that particular type of metric or trace", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "Controls designed to prevent unauthorised access to monitoring features.", - "Section": "CCC.Monitor.CN06 Metrics pushed for authorised services only", - "SubSection": "", - "SubSectionObjective": "Use IAM to control which types of metrics or traces can be pushed by different system to avoid a compromised system pushing fabricated metrics about a different service", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.Monitor.TH05" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AA-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-5" - ] - } - ] - } - ], - "Checks": [ - "compute_instance_default_service_account_in_use", - "compute_instance_default_service_account_in_use_with_full_api_access", - "gke_cluster_no_default_service_account", - "iam_no_service_roles_at_project_level", - "iam_sa_no_administrative_privileges", - "iam_sa_no_user_managed_keys", - "iam_sa_user_managed_key_rotate_90_days", - "iam_sa_user_managed_key_unused", - "iam_service_account_unused" - ] - }, - { - "Id": "CCC.VPC.CN01.AR01", - "Description": "When a subscription is created, the subscription MUST NOT contain default network resources.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.VPC.CN01 Restrict Default Network Creation", - "SubSection": "", - "SubSectionObjective": "Restrict the automatic creation of default virtual networks and related resources during subscription initialization to avoid insecure default configurations and enforce custom network policies.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.VPC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "TVM-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.3.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-7" - ] - } - ] - } - ], - "Checks": [ - "compute_network_default_in_use" - ] - }, - { - "Id": "CCC.VPC.CN03.AR01", - "Description": "When a VPC peering connection is requested, the service MUST prevent connections from VPCs that are not explicitly allowed.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.VPC.CN03 Restrict VPC Peering to Authorized Accounts", - "SubSection": "", - "SubSectionObjective": "Ensure VPC peering connections are only established with explicitly authorized destinations to limit network exposure and enforce boundary controls.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.VPC.TH03" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IVS-01" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-4" - ] - } - ] - } - ], - "Checks": [] - }, - { - "Id": "CCC.VPC.CN04.AR01", - "Description": "When any network traffic goes to or from an interface in the VPC, the service MUST capture and log all relevant information.", - "Attributes": [ - { - "FamilyName": "Network Security", - "FamilyDescription": "TODO: Describe this control family", - "Section": "CCC.VPC.CN04 Enforce VPC Flow Logs on VPCs", - "SubSection": "", - "SubSectionObjective": "Ensure VPCs are configured with flow logs enabled to capture traffic information.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.VPC.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PT-1" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.12.4.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IVS-06" - ] - } - ] - } - ], - "Checks": [ - "compute_subnet_flow_logs_enabled" - ] - }, { "Id": "CCC.Vector.CN01.AR01", "Description": "When a vector embedding is submitted for indexing, the system MUST validate that it matches expected schema, dimension, and format profiles.", @@ -3420,7 +4655,7 @@ "Identifiers": [ "CCC.Vector.TH02", "CCC.Vector.TH05", - "CCC.TH12" + "CCC.Core.TH12" ] } ], @@ -3459,7 +4694,7 @@ "Identifiers": [ "CCC.Vector.TH02", "CCC.Vector.TH04", - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -3474,11 +4709,7 @@ } ], "Checks": [ - "iam_role_kms_enforce_separation_of_duties", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_no_administrative_privileges", - "iam_no_service_roles_at_project_level", - "kms_key_not_publicly_accessible" + "iam_sa_no_administrative_privileges" ] }, { @@ -3501,7 +4732,7 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH03", - "CCC.TH01" + "CCC.Core.TH01" ] } ], @@ -3540,7 +4771,7 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH02", - "CCC.TH12" + "CCC.Core.TH12" ] } ], @@ -3576,8 +4807,8 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH04", - "CCC.TH09", - "CCC.TH04" + "CCC.Core.TH09", + "CCC.Core.TH04" ] } ], @@ -3591,18 +4822,7 @@ ] } ], - "Checks": [ - "iam_audit_logs_enabled", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled" - ] + "Checks": [] }, { "Id": "CCC.Vector.CN06.AR01", @@ -3626,7 +4846,7 @@ "ReferenceId": "CCC", "Identifiers": [ "CCC.Vector.TH05", - "CCC.TH06" + "CCC.Core.TH06" ] } ], @@ -3671,422 +4891,25 @@ "Checks": [] }, { - "Id": "CCC.Core.CN01.AR01", - "Description": "When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", + "Id": "CCC.RDMS.CN01.AR02", + "Description": "When an attempt is made to authenticate to the database using known default credentials, the authentication attempt must fail and no access should be granted.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN01 Password Management", "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Most cloud services enable TLS 1.3 by default. Where it is not already set, ensure that your services are configured or updated accordingly. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "cloudsql_instance_ssl_connections" - ] - }, - { - "Id": "CCC.Core.CN01.AR02", - "Description": "When a port is exposed for SSH network traffic, all traffic MUST include a SSH handshake AND be encrypted using SSHv2 or higher.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Any time port 22 is exposed, ensure that it has a properly implemented SSH server with SSHv2 enabled and configured with strong ciphers. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_ssh_access_from_the_internet_allowed" - ] - }, - { - "Id": "CCC.Core.CN01.AR03", - "Description": "When the service receives unencrypted traffic, then it MUST either block the request or automatically redirect it to the secure equivalent.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Review firewall, load balancer, and application configurations to ensure insecure protocols such as HTTP, FTP, and Telnet are not exposed. Where possible, implement automatic redirection to secure protocols such as HTTPS, SFTP, SSH, and regularly scan for protocol drift. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "cloudsql_instance_ssl_connections", - "cloudsql_instance_public_access", - "cloudsql_instance_public_ip", - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_instance_public_ip", - "cloudstorage_bucket_public_access" - ] - }, - { - "Id": "CCC.Core.CN01.AR07", - "Description": "When a port is exposed, the service MUST ensure that the protocol and service officially assigned to that port number by the IANA Service Name and Transport Protocol Port Number Registry, and no other, is run on that port.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Reference the IANA Service Name and Transport Protocol Port Number Registry for more information about correct protocol-to-port assignments. Avoid running non-standard services on well-known ports. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed" - ] - }, - { - "Id": "CCC.Core.CN01.AR08", - "Description": "When a service transmits data using TLS, mutual TLS (mTLS) MUST be implemented to require both client and server certificate authentication for all connections.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN01 Encrypt Data for Transmission", - "SubSection": "", - "SubSectionObjective": "Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Configure mTLS for all endpoints that process or transmit sensitive data. Ensure both client and server certificates are validated and managed securely. Regularly review certificate authorities and automate certificate rotation where possible. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH02" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-03", - "CEK-04", - "IVS-03", - "IVS-07" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-02" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.1" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-8", - "SC-13" - ] - } - ] - } - ], - "Checks": [ - "cloudsql_instance_ssl_connections" - ] - }, - { - "Id": "CCC.Core.CN13.AR01", - "Description": "When a port is exposed that uses certificate-based encryption, the service MUST only use valid, unexpired certificates issued by a trusted certificate authority.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH18" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [] - }, - { - "Id": "CCC.Core.CN13.AR02", - "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 180 days of issuance.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", + "SubSectionObjective": "Ensure default vendor-supplied DB administrator credentials are replaced with strong, unique passwords and that these credentials are properly managed using a secure password or secrets management solution.", "Applicability": [ + "tlp-red", "tlp-amber" ], - "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH18" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [] - }, - { - "Id": "CCC.Core.CN13.AR03", - "Description": "When a port is exposed that uses certificate-based encryption, the service MUST rotate active certificates within 90 days of issuance.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN13 Minimize Lifetime of Encryption and Authentication Certificates", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption and authentication certificates have a limited lifetime to reduce the risk of compromise and ensure the use of up-to-date security practices.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "Track certificate expiration dates and automate certificate renewal where possible. Use certificate management tools to ensure only certificates from trusted authorities are deployed. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH18" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "apikeys_key_rotated_in_90_days", - "kms_key_rotation_enabled" - ] - }, - { - "Id": "CCC.Core.CN06.AR01", - "Description": "When the service is running, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", - "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate the service's deployment location is included in this list. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH03" + "CCC.RDMS.TH01" ] } ], @@ -4094,19 +4917,89 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.AA-01" ] }, { - "ReferenceId": "CCM", + "ReferenceId": "NIST_800_53", "Identifiers": [ - "DSP-19" + "AC-2" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.RDMS.CN02.AR01", + "Description": "When repeated failed login attempts are made in a short timeframe, the account must be locked out or rate-limited to prevent further login attempts.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN02 Account Lockout and Rate-Limiting", + "SubSection": "", + "SubSectionObjective": "Ensure the database enforces lockouts or rate-limiting after a specified number of failed authentication attempts. This prevents brute force or password-guessing attacks from succeeding.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.RDMS.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-1" ] }, { - "ReferenceId": "ISO_27001", + "ReferenceId": "NIST_800_53", "Identifiers": [ - "2013 A.11.1.1" + "AC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.RDMS.CN04.AR01", + "Description": "When there is an attempt to perform a backup or restore, then the attempt must fail with an access denied message if credentials or roles that are not explicitly authorized for backup/restore functions.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN04 Access Control for Backup and Restore Operations", + "SubSection": "", + "SubSectionObjective": "Restrict who can initiate, manage, and validate database backup or restore operations through strict role-based or least-privilege access. Prevents accidental or malicious restorations, protecting data integrity and availability.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.RDMS.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" ] }, { @@ -4118,30 +5011,30 @@ ] } ], - "Checks": [] + "Checks": [ + "iam_no_service_roles_at_project_level" + ] }, { - "Id": "CCC.Core.CN06.AR02", - "Description": "When a child resource is deployed, its region and availability zone MUST be included in a list of explicitly trusted or approved locations within the trust perimeter.", + "Id": "CCC.RDMS.CN05.AR01", + "Description": "When an attempt is made to share a snapshot with an unauthorized account, the sharing request must be denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN06 Restrict Deployments to Trust Perimeter", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN05 Restrict Snapshot Sharing to Authorized Accounts", "SubSection": "", - "SubSectionObjective": "Ensure that the service and its child resources are only deployed on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "SubSectionObjective": "Ensure database snapshots can only be shared with explicitly authorized accounts, thereby minimizing the risk of data exposure or exfiltration.", "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-red", + "tlp-amber" ], - "Recommendation": "Maintain an up-to-date list of trusted and approved regions based on organizational policies. Validate that child resources can only be deployed to locations included in this list. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH03" + "CCC.RDMS.TH05" ] } ], @@ -4149,24 +5042,99 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-19" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.11.1.1" + "PR.DS-10" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ + "AC-4" + ] + } + ] + } + ], + "Checks": [ + "compute_image_not_publicly_shared" + ] + }, + { + "Id": "CCC.RDMS.CN03.AR01", + "Description": "When backups are disabled, paused, or fail to run as scheduled, an alert must be triggered and logged.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.RDMS.CN03 Enforce and Monitor Automated Backups", + "SubSection": "", + "SubSectionObjective": "Ensure database backups are automatically scheduled, actively monitored, and promptly reported if any disruptions occur. This helps maintain data integrity, facilitates disaster recovery, and supports business continuity when a system failure or breach occurs.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.RDMS.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "CP-9" + ] + } + ] + } + ], + "Checks": [ + "cloudsql_instance_automated_backups" + ] + }, + { + "Id": "CCC.Build.CN01.AR01", + "Description": "Attempt to initiate a build using an unauthorized build agent and verify that the build is rejected.", + "Attributes": [ + { + "FamilyName": "Access Control", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Build.CN01 Restrict Allowed Build Agents", + "SubSection": "", + "SubSectionObjective": "Ensure that builds are executed only on authorized build agents to maintain control over the build environment and prevent unauthorized code execution.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-3", "AC-6" ] } @@ -4176,80 +5144,25 @@ "Checks": [] }, { - "Id": "CCC.Core.CN08.AR01", - "Description": "When data is created or modified, the data MUST have a complete and recoverable duplicate that is stored in a physically separate data center.", + "Id": "CCC.Build.CN02.AR01", + "Description": "Attempt to trigger a build from an unauthorized external service or repository and verify that the build does not start.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", + "FamilyName": "Access Control", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Build.CN02 Restrict Allowed External Services for Build Triggers", "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", + "SubSectionObjective": "Ensure that builds can only be triggered by authorized external services or repositories to prevent unauthorized code execution or tampering.", "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement automated data replication processes to ensure that data is consistently duplicated in another region or availability zone. Regularly test data recovery from the replicated location to ensure integrity and availability. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.PT-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "BCR-08", - "BCR-10", - "BCR-11" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "CP-2", - "CP-10" - ] - } - ] - } - ], - "Checks": [ - "cloudsql_instance_automated_backups", - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.Core.CN08.AR02", - "Description": "When data is replicated into a second location, the service MUST be able to accurately represent the replication locations, replication status, and data synchronization status.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN08 Replicate Data to Multiple Locations", - "SubSection": "", - "SubSectionObjective": "Ensure that data is replicated across multiple physical locations to protect against data loss due to hardware failures, natural disasters, or other catastrophic events.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-red", + "tlp-amber" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH06" + "CCC.Core.TH01" ] } ], @@ -4257,22 +5170,14 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.PT-5" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "BCR-08", - "BCR-10", - "BCR-11" + "PR.AC-4" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "CP-2", - "CP-10" + "AC-3", + "AC-6" ] } ] @@ -4281,15 +5186,144 @@ "Checks": [] }, { - "Id": "CCC.Core.CN09.AR01", - "Description": "When the service is operational, its logs and any child resource logs MUST NOT be accessible from the resource they record access to.", + "Id": "CCC.Build.CN03.AR01", + "Description": "Attempt to access the build environment from an external network and verify that access is denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Build.CN03 Deny External Network Access for Build Environments", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "SubSectionObjective": "Ensure that build environments do not have external network access to prevent unauthorized external access and data exfiltration.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH02", + "CCC.Core.TH05" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-5" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7", + "SC-5" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.CntrReg.CN01.AR01", + "Description": "Attempt to push an artifact with known vulnerabilities to the registry and observe if it is flagged or rejected by the vulnerability scanning process.", + "Attributes": [ + { + "FamilyName": "Risk Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.CntrReg.CN01 Implement Vulnerability Scanning for Artifacts", + "SubSection": "", + "SubSectionObjective": "Ensure that container images and artifacts stored in the container registry are scanned for vulnerabilities to identify and remediate security issues before deployment.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.CntrReg.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "ID.RA-1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "RA-5", + "SI-5" + ] + } + ] + } + ], + "Checks": [ + "artifacts_container_analysis_enabled", + "gcr_container_scanning_enabled" + ] + }, + { + "Id": "CCC.CntrReg.CN02.AR01", + "Description": "Confirm that artifacts older than the specified retention period are automatically deleted from the registry.", + "Attributes": [ + { + "FamilyName": "Data Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.CntrReg.CN02 Implement Cleanup Policies for Artifacts", + "SubSection": "", + "SubSectionObjective": "Ensure that unused or outdated artifacts are cleaned up according to defined policies to manage storage effectively and reduce security risks associated with outdated versions.", + "Applicability": [ + "tlp-red", + "tlp-amber" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.Core.TH14" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-6" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-12" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.IAM.CN01.AR01", + "Description": "When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials or generating temporary session tokens.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN01 Restrict IAM User Credentials Creation", + "SubSection": "", + "SubSectionObjective": "Prevent non-administrative principals from creating new long-lived credentials like access keys or generating temporary session tokens. This blocks a common privilege escalation and persistence vector.", "Applicability": [ "tlp-clear", "tlp-green", @@ -4301,9 +5335,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07", - "CCC.TH09", - "CCC.TH04" + "CCC.IAM.TH03" ] } ], @@ -4311,56 +5343,48 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-6" + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AU-9" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-02", - "LOG-04", - "LOG-09" + "AC-2", + "AC-3", + "AC-5", + "AC-6" ] } ] } ], "Checks": [ - "logging_sink_created", - "cloudstorage_bucket_log_retention_policy_lock", - "iam_audit_logs_enabled" + "iam_sa_no_user_managed_keys", + "iam_no_service_roles_at_project_level" ] }, { - "Id": "CCC.Core.CN09.AR02", - "Description": "When the service is operational, disabling the logs for the service or its child resources MUST NOT be possible without also disabling the corresponding resource.", + "Id": "CCC.IAM.CN01.AR02", + "Description": "When a non-administrative principal attempts to create new credentials or a temporary session token, the service MUST deny the action.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN01 Restrict IAM User Credentials Creation", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", + "SubSectionObjective": "Prevent non-administrative principals from creating new long-lived credentials like access keys or generating temporary session tokens. This blocks a common privilege escalation and persistence vector.", "Applicability": [ "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" ], - "Recommendation": "No normal business operations should disable logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging mechanisms are tightly integrated with service operations, so that logging cannot be disabled without stopping the service itself. ", + "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH07", - "CCC.TH09", - "CCC.TH04" + "CCC.IAM.TH03" ] } ], @@ -4368,21 +5392,589 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-6" + "PR.AA-05" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AU-9" + "AC-2", + "AC-3", + "AC-5", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_sa_no_user_managed_keys", + "iam_no_service_roles_at_project_level" + ] + }, + { + "Id": "CCC.IAM.CN02.AR01", + "Description": "When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating, updating, or attaching policies.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN02 Restrict IAM Policies Modification", + "SubSection": "", + "SubSectionObjective": "Ensure that only designated administrative accounts have the ability to create, modify, or attach policies that define permissions for other identities.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" ] }, { - "ReferenceId": "CCM", + "ReferenceId": "NIST_800_53", "Identifiers": [ - "LOG-02", - "LOG-04", - "LOG-09" + "AC-2", + "AC-3", + "AC-5", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_no_service_roles_at_project_level", + "iam_sa_no_administrative_privileges" + ] + }, + { + "Id": "CCC.IAM.CN02.AR02", + "Description": "When a non-administrative principal attempts to create, update, or attach policies, the service MUST deny the action.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN02 Restrict IAM Policies Modification", + "SubSection": "", + "SubSectionObjective": "Ensure that only designated administrative accounts have the ability to create, modify, or attach policies that define permissions for other identities.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-5", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_no_service_roles_at_project_level", + "iam_sa_no_administrative_privileges" + ] + }, + { + "Id": "CCC.IAM.CN03.AR01", + "Description": "When a policy is created or updated that grants a principal permission to assume a role or impersonate a service identity, the principal MUST NOT contain a wildcard or be public/anonymous.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN03 Restrict Role Assumption / Delegation", + "SubSection": "", + "SubSectionObjective": "Limit which principals can assume a role or impersonate a service identity to only those required. This prevents unintended cross-account or public access by securing the \"who can act as this identity\" boundary.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3", + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_role_sa_enforce_separation_of_duties", + "iam_no_service_roles_at_project_level" + ] + }, + { + "Id": "CCC.IAM.CN03.AR02", + "Description": "When an external or unauthenticated principal tries to assume a role or impersonate a service identity, the service MUST deny the action.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN03 Restrict Role Assumption / Delegation", + "SubSection": "", + "SubSectionObjective": "Limit which principals can assume a role or impersonate a service identity to only those required. This prevents unintended cross-account or public access by securing the \"who can act as this identity\" boundary.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3", + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_role_sa_enforce_separation_of_duties", + "iam_no_service_roles_at_project_level" + ] + }, + { + "Id": "CCC.IAM.CN04.AR01", + "Description": "When an IAM policy is created or updated, it MUST NOT contain allow statements with wildcard permissions, unless the statement is restricted by a condition.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN04 Restrict Wildcard Usage in IAM Policies", + "SubSection": "", + "SubSectionObjective": "Limit the use of wildcard permissions in IAM policies to prevent overly broad access from being granted by default.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01", + "CCC.IAM.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-6" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3", + "AC-6" + ] + } + ] + } + ], + "Checks": [ + "iam_sa_no_administrative_privileges" + ] + }, + { + "Id": "CCC.IAM.CN05.AR01", + "Description": "When a new cloud account is provisioned, a password policy MUST be configured for IAM users following the minimum PCI DSS v4.0.1 configurations.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "Controls that restrict who can access and modify IAM resources.", + "Section": "CCC.IAM.CN05 Strong Password Policies for IAM Users", + "SubSection": "", + "SubSectionObjective": "Ensure that the password policies for IAM users have strong configurations.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "When a new cloud account is provisioned, a password policy must be configured for all IAM users to align with the minimum requirements defined in PCI DSS v4.0.1. This includes, at a minimum: strength: 0 # Not yet specified - reference-id: A password length of at least 12 characters. strength: 0 # Not yet specified - reference-id: A mix of upper- and lower-case letters, numbers, and special characters. strength: 0 # Not yet specified - reference-id: Prevention of the use of previously used passwords (password history). strength: 0 # Not yet specified - reference-id: Password expiration at a defined interval (e.g., every 90 days). strength: 0 # Not yet specified - reference-id: Account lockout after a defined number of failed login attempts.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-05" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "IA-5" + ] + }, + { + "ReferenceId": "PCI-DSS", + "Identifiers": [ + "8.3.9", + "8.6.3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.IAM.CN06.AR01", + "Description": "When a static credential such as an access key has existed for 90 days or more, it MUST be rotated.", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN06 Maximum Age for Long-Term Static Credentials", + "SubSection": "", + "SubSectionObjective": "Ensure that long-lived static credentials like access keys are programmatically rotated within a defined time period to limit the window of opportunity if compromised.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "When a static credential such as an access key has existed for 90 days or more, it must be automatically rotated to reduce the risk of compromise due to long-term exposure. Organizations should implement automated checks to identify aging credentials and enforce rotation policies. Additionally, access key usage should be regularly monitored, and credentials that are no longer in use should be deactivated or deleted promptly. Where possible, prefer temporary, short-lived credentials over long-lived static ones to further minimize risk.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH09", + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "iam_sa_user_managed_key_rotate_90_days" + ] + }, + { + "Id": "CCC.IAM.CN07.AR01", + "Description": "When a user account is disabled or deleted in the organization's IdP, the corresponding cloud identity and its access policies MUST be disabled or deleted within 24 hours.", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN07 Automate Identity De-provisioning", + "SubSection": "", + "SubSectionObjective": "Ensure that when an identity is terminated in the central Identity Provider (IdP), ts corresponding access to cloud resources is revoked automatically.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH10", + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "iam_service_account_unused", + "iam_sa_user_managed_key_unused" + ] + }, + { + "Id": "CCC.IAM.CN08.AR01", + "Description": "When an IAM user has credentials, such as passwords or access keys, that have not been used for 90 days or more, the unused credentials MUST be removed or deactivated.", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN08 Maximum Age for Unused Credentials", + "SubSection": "", + "SubSectionObjective": "Ensure that unused IAM credentals are removed to reduce exposure in the event of potential compromise.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "IAM user credentials (such as passwords or access keys) that have not been used for 90 days or more must be automatically removed or deactivated.", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH11", + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "iam_sa_user_managed_key_unused", + "iam_service_account_unused" + ] + }, + { + "Id": "CCC.IAM.CN09.AR01", + "Description": "When a human user accesses the cloud environment, they MUST authenticate through the organization's federated IdP via a standard protocol (e.g., SAML, OIDC).", + "Attributes": [ + { + "FamilyName": "Identity Provisioning and Lifecycle", + "FamilyDescription": "Controls related to the provisioning and lifecycle of IAM identities.", + "Section": "CCC.IAM.CN09 Enforce Federated Single Sign-On (SSO) for Human Users", + "SubSection": "", + "SubSectionObjective": "Ensure that all human users must authenticate through a central, federated Identity Provider (IdP) to access the cloud environment. This eliminates cloud-native user accounts with long-lived passwords, centralizes authentication controls, and simplifies lifecycle management.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01", + "CCC.IAM.TH09" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AA-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "IA-2" + ] + } + ] + } + ], + "Checks": [ + "compute_project_os_login_enabled" + ] + }, + { + "Id": "CCC.IAM.CN10.AR01", + "Description": "When suspicious API requests are detected, real time alerts MUST be generated to notify security personnel.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain IAM-related events.", + "Section": "CCC.IAM.CN10 Alert On Anomalous Behaviour", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when anomalous API requests are made by a single identity, such as API requests commonly associated with privilege escalation tactics, originating from an external or malicious IP address or performed by a previously dormant identity, which may indicate that credentals may be compromised, as well as for password brute-force attempts and account lockouts.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-03", + "DE.CM-06", + "DE.CM-09" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "SI-5", + "AC-2" + ] + } + ] + } + ], + "Checks": [ + "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", + "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled" + ] + }, + { + "Id": "CCC.IAM.CN10.AR02", + "Description": "When suspicious API requests are detected, the associated events MUST be logged, including the source details, time, and nature of the activity.", + "Attributes": [ + { + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain IAM-related events.", + "Section": "CCC.IAM.CN10 Alert On Anomalous Behaviour", + "SubSection": "", + "SubSectionObjective": "Ensure that logs and associated alerts are generated when anomalous API requests are made by a single identity, such as API requests commonly associated with privilege escalation tactics, originating from an external or malicious IP address or performed by a previously dormant identity, which may indicate that credentals may be compromised, as well as for password brute-force attempts and account lockouts.", + "Applicability": [ + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.IAM.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "DE.CM-03", + "DE.CM-06", + "DE.CM-09" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-4", + "SI-5", + "AC-2" ] } ] @@ -4390,78 +5982,21 @@ ], "Checks": [ "iam_audit_logs_enabled", - "logging_sink_created", - "cloudstorage_bucket_log_retention_policy_lock", - "compute_subnet_flow_logs_enabled", - "compute_loadbalancer_logging_enabled", - "compute_network_dns_logging_enabled" + "logging_sink_created" ] }, { - "Id": "CCC.Core.CN09.AR03", - "Description": "When the service is operational, any attempt to redirect logs for the service or its child resources MUST NOT be possible without halting operation of the corresponding resource and publishing corresponding events to monitored channels.", + "Id": "CCC.IAM.CN11.AR01", + "Description": "When a cloud account or organization is provisioned, the native automated access and usage analysis services MUST be enabled to continuously monitor for external or public access to resources, and unused access.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN09 Ensure Integrity of Access Logs", + "FamilyName": "Logging and Monitoring", + "FamilyDescription": "Controls that collect, alert, and retain IAM-related events.", + "Section": "CCC.IAM.CN11 Enable Continuous IAM Access and Usage Analysis", "SubSection": "", - "SubSectionObjective": "Ensure that access logs are always recorded to an external location that cannot be manipulated from the context of the service(s) it contains logs for.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "No normal business operations should result in the redirection of logs, as this could indicate an attempt to cover up unauthorized access. Ensure that logging configurations are immutable during service operation so that any changes require stopping the service and publishing corresponding events to monitored channels. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH07", - "CCC.TH09", - "CCC.TH04" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-6" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-9" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-02", - "LOG-04", - "LOG-09" - ] - } - ] - } - ], - "Checks": [ - "logging_sink_created", - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.Core.CN10.AR01", - "Description": "When data is replicated, the service MUST ensure that replication only occurs to destinations that are explicitly included within the defined trust perimeter.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN10 Restrict Data Replication to Trust Perimeter", - "SubSection": "", - "SubSectionObjective": "Ensure that data is only replicated on infrastructure in locations that are explicitly included within a defined trust perimeter.", + "SubSectionObjective": "Enable and configure the cloud provider's native access and usage analysis services to continuously monitor for external access paths and internal unused access.", "Applicability": [ + "tlp-clear", "tlp-green", "tlp-amber", "tlp-red" @@ -4471,7 +6006,978 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH04" + "CCC.IAM.TH02", + "CCC.IAM.TH10", + "CCC.IAM.TH11" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "ID.RA-01", + "ID.IM-01" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "CA-7", + "RA-5" + ] + } + ] + } + ], + "Checks": [ + "iam_account_access_approval_enabled", + "iam_cloud_asset_inventory_enabled" + ] + }, + { + "Id": "CCC.GenAI.CN01.AR01", + "Description": "Untrusted input such as user queries, RAG data or tool output MUST be validated before it is passed to a GenAI model.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN01 Model Input Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI model in order to filter or sanitise adversarial queries and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Input Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0021", + "AML.M0015" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN01.AR02", + "Description": "If malicious patterns such as prompt injection or sensitive data are detected during input validation, the input MUST be blocked or sanitised.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN01 Model Input Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate input before it is passed to a GenAI model in order to filter or sanitise adversarial queries and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Input Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0021", + "AML.M0015" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN02.AR01", + "Description": "GenAI model output MUST be validated for format conformance, malicious patterns, sensitive data and inapropriate content before being passed to users, application or plugins.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN02 Model Output Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate GenAI model output before passing it to users, applications or plugins in order to filter or sanitise insecure or unreliable output and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03", + "CCC.GenAI.TH04", + "CCC.GenAI.TH05", + "CCC.GenAI.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Output Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0002" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN02.AR02", + "Description": "In the event of policy violations, the AI-generated content MUST be redacted, encoded or rejected.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN02 Model Output Filtering and Sanitisation", + "SubSection": "", + "SubSectionObjective": "Inspect and validate GenAI model output before passing it to users, applications or plugins in order to filter or sanitise insecure or unreliable output and prevent sensitive data leakage.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH03", + "CCC.GenAI.TH04", + "CCC.GenAI.TH05", + "CCC.GenAI.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-003", + "AIR-PREV-017", + "AIR-PREV-002", + "AIR-DET-001" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Output Validation and Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0020", + "AML.M0002" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN03.AR01", + "Description": "When data is designated for model training or RAG ingestion, then its source MUST be explicitly approved and its provenance documented.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN03 Data Provenance and Source Vetting", + "SubSection": "", + "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes from trusted, approved sources and is authorised for the intended purposes in order to prevent the initial introduction of malicious content or leaked sensitive data.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-006" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Management" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0025" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN03.AR02", + "Description": "Data from unvetted sources MUST NOT be used in production systems.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN03 Data Provenance and Source Vetting", + "SubSection": "", + "SubSectionObjective": "Ensure that all data for training, fine-tuning or RAG comes from trusted, approved sources and is authorised for the intended purposes in order to prevent the initial introduction of malicious content or leaked sensitive data.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-006" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Management" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0025" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN04.AR01", + "Description": "When data is ingested for training, fine-tuning or conversion to vector embeddings, it MUST be validated for sensitive information or malicious content.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN04 Sanitisation of Ingested Data", + "SubSection": "", + "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems from extenal sources or internal knowledge bases, whether for training, conversion to vector embeddings, or real-time retireval, in order to remove or redact poisoned or sensitive data before further processing.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-002" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0007" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN04.AR02", + "Description": "If sensitive data or malicious content is detected, it must be rejected, redacted or flagged for manual review.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN04 Sanitisation of Ingested Data", + "SubSection": "", + "SubSectionObjective": "Validate and sanitise all data ingested by GenAI systems from extenal sources or internal knowledge bases, whether for training, conversion to vector embeddings, or real-time retireval, in order to remove or redact poisoned or sensitive data before further processing.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH02", + "CCC.GenAI.TH03" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-002" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Training Data Sanitization" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0007" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN05.AR01", + "Description": "When a RAG-enabled system generates a response containing information retrieved from its knowledge base, then the response MUST include a verifiable citation that links back to the specific source document.", + "Attributes": [ + { + "FamilyName": "Data", + "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters.", + "Section": "CCC.GenAI.CN05 Citations and Source Traceability", + "SubSection": "", + "SubSectionObjective": "Require the GenAI system to provide citations or direct links back to the source documents used to generate a response, in to enhance the transparency, trustworthiness, and verifiability of AI-generated content.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH09", + "CCC.GenAI.TH04" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-DET-013" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN06.AR01", + "Description": "When an LLM invokes an external tool (e.g., an API, a plugin), then the tool MUST operate with the least privileges required for performing its intended functionality.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration.", + "Section": "CCC.GenAI.CN06 Least Privilege for Plugins", + "SubSection": "", + "SubSectionObjective": "Restricts the permissions of any external tools the GenAI system can call to limit the potential damage if an agent is coerced to perform unintended actions or vulnerabilities in the tools are exploited.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH07", + "CCC.GenAI.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Agent Permissions" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN07.AR01", + "Description": "When an application makes an API call to a foundational model in a production environment, then it MUST specify an explicit version identifier.", + "Attributes": [ + { + "FamilyName": "Configuration Management", + "FamilyDescription": "The Configuration Management control family involves establishing, maintaining and monitoring the configuration of the service and related applications and infrastructure to ensure consistency, secure defaults and compliance.", + "Section": "CCC.GenAI.CN07 Model Version Pinning", + "SubSection": "", + "SubSectionObjective": "Mandate that applications are locked (\"pinned\") to a specific, tested version of a foundational model to prevent unexpected behaviour changes introduced by provider-side updates.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-010" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN08.AR01", + "Description": "When a new AI model is considered for production deployment, it MUST undergo a formal red teaming and quality assurance review.", + "Attributes": [ + { + "FamilyName": "Model Assurance and Evaluation", + "FamilyDescription": "The Model Assurance and Evaluation control family encompasses the proactiveand continuous processes of testing and validating the AI model's behavior to ensure it aligns with safety, ethical, and quality standards.", + "Section": "CCC.GenAI.CN08 Quality Control and Red Teaming", + "SubSection": "", + "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial testing (red teaming) to ensure GenAI system meet all business, quality, security and compliance requirements before getting deployed into production environments.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH02", + "CCC.GenAI.TH04", + "CCC.GenAI.TH08", + "CCC.GenAI.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-005" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Adversarial Training and Testing", + "Red Teaming", + "Product Governance" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0008" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.GenAI.CN08.AR02", + "Description": "If model quality review or red teaming identifies an issue that exceeds the organization's risk tolerance, the model MUST NOT be deployed until the issue is remediated.", + "Attributes": [ + { + "FamilyName": "Model Assurance and Evaluation", + "FamilyDescription": "The Model Assurance and Evaluation control family encompasses the proactiveand continuous processes of testing and validating the AI model's behavior to ensure it aligns with safety, ethical, and quality standards.", + "Section": "CCC.GenAI.CN08 Quality Control and Red Teaming", + "SubSection": "", + "SubSectionObjective": "Establish a formal program for quality evaluation and adversarial testing (red teaming) to ensure GenAI system meet all business, quality, security and compliance requirements before getting deployed into production environments.", + "Applicability": [ + "tlp-clear", + "tlp-green", + "tlp-amber", + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.GenAI.TH01", + "CCC.GenAI.TH02", + "CCC.GenAI.TH04", + "CCC.GenAI.TH08", + "CCC.GenAI.TH10" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "FINOS-AIGF", + "Identifiers": [ + "AIR-PREV-005" + ] + }, + { + "ReferenceId": "SAIF", + "Identifiers": [ + "Adversarial Training and Testing", + "Red Teaming", + "Product Governance" + ] + }, + { + "ReferenceId": "MITRE-ATLAS", + "Identifiers": [ + "AML.M0008" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN01.AR01", + "Description": "Verify that only authorized users can access MLDE resources, and that access modes are properly defined and enforced.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN01 Define Access Mode for ML Development Environments", + "SubSection": "", + "SubSectionObjective": "Ensure that access to Machine Learning Development Environment (MLDE) resources is strictly defined and controlled. Only authorized users with appropriate permissions can access these environments, mitigating the risk of unauthorized access, data leakage, or service disruption.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01", + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.1.1", + "2013 A.9.2.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-2", + "AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-01", + "IAM-02" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN03.AR01", + "Description": "Verify that root access is disabled on MLDE instances containing sensitive data.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the risk of unauthorized system modifications and potential security breaches.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08", + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN03.AR02", + "Description": "For MLDE instances without sensitive data, ensure that root access is only enabled when necessary and properly authorized.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN03 Disable Root Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from obtaining root access on MLDE instances to reduce the risk of unauthorized system modifications and potential security breaches.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08", + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN04.AR01", + "Description": "Verify that terminal access is disabled on MLDE instances containing sensitive data.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of unauthorized commands and potential system compromise.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN04.AR02", + "Description": "For MLDE instances without sensitive data, ensure that terminal access is only enabled when necessary and properly authorized.", + "Attributes": [ + { + "FamilyName": "Identity and Access Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN04 Disable Terminal Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent users from accessing the terminal on MLDE instances to limit the risk of unauthorized commands and potential system compromise.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-08" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.2.3" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN02.AR01", + "Description": "Confirm that file download functionality is disabled on MLDE instances containing sensitive data.", + "Attributes": [ + { + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH02", + "CCC.Core.TH02" ] } ], @@ -4485,14 +6991,21 @@ { "ReferenceId": "CCM", "Identifiers": [ - "DSP-10", - "DSP-19" + "DSI-05", + "DSI-07" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.2.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "AC-4" + "SC-7", + "SC-8" ] } ] @@ -4501,26 +7014,28 @@ "Checks": [] }, { - "Id": "CCC.Core.CN02.AR01", - "Description": "When data is stored, it MUST be encrypted using the latest industry-standard encryption methods.", + "Id": "CCC.MLDE.CN02.AR02", + "Description": "For MLDE instances without sensitive data, ensure that file downloads are monitored and logged.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN02 Encrypt Data for Storage", + "FamilyName": "Data Protection", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN02 Disable File Downloads on MLDE Instances", "SubSection": "", - "SubSectionObjective": "Ensure that all data stored is encrypted at rest using strong encryption algorithms.", + "SubSectionObjective": "Prevent unauthorized file downloads from MLDE instances to protect sensitive data from being exfiltrated.", "Applicability": [ + "tlp-red", + "tlp-amber", "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-clear" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.MLDE.TH02", + "CCC.Core.TH02" ] } ], @@ -4528,110 +7043,46 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.DS-5" ] }, { "ReferenceId": "CCM", "Identifiers": [ - "CEK-03", - "CEK-04", - "UEM-08", - "DSP-17" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-13", - "SC-28" - ] - } - ] - } - ], - "Checks": [ - "compute_instance_encryption_with_csek_enabled", - "dataproc_encrypted_with_cmks_disabled", - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption", - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled" - ] - }, - { - "Id": "CCC.Core.CN11.AR01", - "Description": "When encryption keys are used, the service MUST verify that all encryption keys use the latest industry-standard cryptographic algorithms.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" + "DSI-05", + "DSI-07" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.10.1.2" + "2013 A.13.2.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-12", - "SC-17" + "SC-7", + "SC-8" ] } ] } ], - "Checks": [ - "kms_key_rotation_enabled", - "dataproc_encrypted_with_cmks_disabled", - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption", - "kms_key_not_publicly_accessible" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR02", - "Description": "When encryption keys are used, the service MUST rotate active keys within 180 days of issuance.", + "Id": "CCC.MLDE.CN05.AR01", + "Description": "Verify that only approved VM and container images can be selected when creating MLDE instances.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", + "FamilyName": "Configuration Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Limit the virtual machine and container image options available when creating new MLDE instances to approved and secure configurations.", "Applicability": [ + "tlp-red", "tlp-amber" ], "Recommendation": "", @@ -4639,7 +7090,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" + "CCC.MLDE.TH04" ] } ], @@ -4647,53 +7098,43 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.IP-1" ] }, { "ReferenceId": "CCM", "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" + "TVM-02" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.10.1.2" + "2013 A.12.5.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-12", - "SC-17" + "CM-2" ] } ] } ], - "Checks": [ - "kms_key_rotation_enabled", - "kms_key_not_publicly_accessible", - "dataproc_encrypted_with_cmks_disabled", - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR03", - "Description": "When encrypting data, the service MUST verify that customer-managed encryption keys (CMEKs) are used.", + "Id": "CCC.MLDE.CN05.AR02", + "Description": "Attempt to create an MLDE instance with an unapproved image and confirm that it is denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "", - "SubSection": "CCC.Core.CN11 Protect Encryption Keys", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "FamilyName": "Configuration Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN05 Restrict Environment Options on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual machine and container image options available when creating new MLDE instances to approved and secure configurations.", "Applicability": [ - "tlp-amber", "tlp-red" ], "Recommendation": "", @@ -4701,7 +7142,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" + "CCC.MLDE.TH04" ] } ], @@ -4709,51 +7150,371 @@ { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.DS-1" + "PR.IP-1" ] }, { "ReferenceId": "CCM", "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" + "TVM-02" ] }, { "ReferenceId": "ISO_27001", "Identifiers": [ - "2013 A.10.1.2" + "2013 A.12.5.1" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "SC-12", - "SC-17" + "CM-2" ] } ] } ], - "Checks": [ - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption", - "dataproc_encrypted_with_cmks_disabled", - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR04", - "Description": "When encryption keys are accessed, the service MUST verify that access to encryption keys is restricted to authorized personnel and services, following the principle of least privilege.", + "Id": "CCC.MLDE.CN06.AR01", + "Description": "Verify that automatic scheduled upgrades are enabled on user-managed MLDE instances containing sensitive data.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", + "FamilyName": "Vulnerability Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the latest security patches by enforcing automatic scheduled upgrades.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH04", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-12" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "TVM-01", + "TVM-02" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.12.6.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-2" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN06.AR02", + "Description": "Ensure that the upgrade schedule is appropriately configured and does not interfere with critical operations.", + "Attributes": [ + { + "FamilyName": "Vulnerability Management", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN06 Require Automatic Scheduled Upgrades on User-Managed MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Ensure that MLDE instances are kept up-to-date with the latest security patches by enforcing automatic scheduled upgrades.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH04", + "CCC.Core.TH06" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.IP-12" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "TVM-01", + "TVM-02" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.12.6.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SI-2" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN07.AR01", + "Description": "Verify that MLDE instances containing sensitive data cannot be accessed via public IP addresses.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH02", + "CCC.VPC.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "SEF-05" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.1.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN07.AR02", + "Description": "For MLDE instances without sensitive data requiring public access, ensure that appropriate security controls are in place and access is approved.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN07 Restrict Public IP Access on MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Prevent public IP access to MLDE instances to reduce exposure to the internet and enhance security.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH02", + "CCC.VPC.TH02" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-3" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "SEF-05" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.13.1.1" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "SC-7" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN08.AR01", + "Description": "Verify that MLDE instances containing sensitive data can only be deployed in approved virtual networks with appropriate security controls.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to ensure they are deployed within approved and secure network environments.", + "Applicability": [ + "tlp-red" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01", + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.1.2" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.MLDE.CN08.AR02", + "Description": "Ensure that MLDE instances without sensitive data are deployed in networks that meet organizational security standards.", + "Attributes": [ + { + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.MLDE.CN08 Restrict Virtual Networks for MLDE Instances", + "SubSection": "", + "SubSectionObjective": "Limit the virtual networks that can be used when creating new MLDE instances to ensure they are deployed within approved and secure network environments.", + "Applicability": [ + "tlp-red", + "tlp-amber", + "tlp-green", + "tlp-clear" + ], + "Recommendation": "", + "SectionThreatMappings": [ + { + "ReferenceId": "CCC", + "Identifiers": [ + "CCC.MLDE.TH01", + "CCC.Core.TH01" + ] + } + ], + "SectionGuidelineMappings": [ + { + "ReferenceId": "NIST-CSF", + "Identifiers": [ + "PR.AC-4" + ] + }, + { + "ReferenceId": "CCM", + "Identifiers": [ + "IAM-12" + ] + }, + { + "ReferenceId": "ISO_27001", + "Identifiers": [ + "2013 A.9.1.2" + ] + }, + { + "ReferenceId": "NIST_800_53", + "Identifiers": [ + "AC-6" + ] + } + ] + } + ], + "Checks": [] + }, + { + "Id": "CCC.Message.CN01.AR01", + "Description": "Attempt to publish a message without using a customer-managed encryption key and verify that the message is rejected or not stored.", + "Attributes": [ + { + "FamilyName": "Encryption", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.Message.CN01 Use Customer-Managed Encryption Keys (CMEK) for Messages", + "SubSection": "", + "SubSectionObjective": "Ensure that messages are encrypted using customer-managed encryption keys (CMEK) to provide enhanced control over encryption processes and keys, meeting compliance and security requirements.", "Applicability": [ "tlp-clear", "tlp-green", @@ -4765,7 +7526,7 @@ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH16" + "CCC.Core.TH01" ] } ], @@ -4776,310 +7537,53 @@ "PR.DS-1" ] }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.2" - ] - }, { "ReferenceId": "NIST_800_53", "Identifiers": [ "SC-12", - "SC-17" + "SC-13" ] } ] } ], - "Checks": [ - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled", - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption", - "dataproc_encrypted_with_cmks_disabled" - ] + "Checks": [] }, { - "Id": "CCC.Core.CN11.AR05", - "Description": "When encryption keys are used, the service MUST rotate active keys within 365 days of issuance.", + "Id": "CCC.SvlsComp.CN01.AR01", + "Description": "Attempt to access the serverless function over the public internet and verify that access is denied.", "Attributes": [ { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", + "FamilyName": "Network Security", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SvlsComp.CN01 Enforce Use of Private Endpoints for Serverless Function", "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", + "SubSectionObjective": "Ensure that the serverless function is accessible only through a private endpoint, allowing it to communicate securely within a virtual private network and preventing unauthorized external access.", "Applicability": [ - "tlp-clear", - "tlp-green" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-17" - ] - } - ] - } - ], - "Checks": [ - "kms_key_rotation_enabled", - "kms_key_not_publicly_accessible", - "dataproc_encrypted_with_cmks_disabled", - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption" - ] - }, - { - "Id": "CCC.Core.CN11.AR06", - "Description": "When encryption keys are used, the service MUST rotate active keys within 90 days of issuance.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN11 Protect Encryption Keys", - "SubSection": "", - "SubSectionObjective": "Ensure that encryption keys are managed securely by enforcing the use of approved algorithms, regular key rotation, and customer-managed encryption keys (CMEKs).", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH16" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.DS-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "CEK-08", - "CEK-10", - "CEK-12" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.10.1.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "SC-12", - "SC-17" - ] - } - ] - } - ], - "Checks": [ - "kms_key_rotation_enabled", - "kms_key_not_publicly_accessible", - "dataproc_encrypted_with_cmks_disabled", - "bigquery_dataset_cmk_encryption", - "bigquery_table_cmk_encryption" - ] - }, - { - "Id": "CCC.Core.CN14.AR01", - "Description": "When backups are created for disaster recovery purposes, the storage mechanism MUST NOT allow modification or deletion within 30 days of creation.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN14 Maintain Recent Backups", - "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Use immutable storage solutions where possible. Implement backup retention policies that enforce a minimum retention period of 30 days. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "cloudstorage_bucket_log_retention_policy_lock", - "cloudsql_instance_automated_backups" - ] - }, - { - "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 30 days.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN14 Maintain Recent Backups", - "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", - "Applicability": [ - "tlp-clear", - "tlp-green", + "tlp-red", "tlp-amber" ], - "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 30 days. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "cloudsql_instance_automated_backups" - ] - }, - { - "Id": "CCC.Core.CN14.AR02", - "Description": "When backups are created for disaster recovery purposes, the most recent backup MUST have a creation date within the past 14 days.", - "Attributes": [ - { - "FamilyName": "Data", - "FamilyDescription": "The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle. These controls govern how data is transmitted, stored, replicated, and protected from unauthorized access, tampering, or exposure beyond defined trust perimeters. ", - "Section": "CCC.Core.CN14 Maintain Recent Backups", - "SubSection": "", - "SubSectionObjective": "Ensure that all backups used for disaster recovery are recent and subject to a retention policy that limits deletion.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "Implement automated backup processes to ensure that backups are created regularly. Monitor backup schedules and verify that the most recent backup creation date is within the last 14 days. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH06" - ] - } - ], - "SectionGuidelineMappings": [] - } - ], - "Checks": [ - "cloudsql_instance_automated_backups", - "cloudstorage_bucket_log_retention_policy_lock" - ] - }, - { - "Id": "CCC.Core.CN03.AR01", - "Description": "When an entity attempts to modify the service through a user interface, the authentication process MUST require multiple identifying factors for authentication.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH01" ] } ], "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" + "PR.AC-5" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "IA-2" + "SC-7", + "SC-8" ] } ] @@ -5088,990 +7592,45 @@ "Checks": [] }, { - "Id": "CCC.Core.CN03.AR02", - "Description": "When an entity attempts to modify the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", + "Id": "CCC.SvlsComp.CN02.AR01", + "Description": "Send requests to invoke the function up to the allowed threshold and confirm they are successful; then send additional requests exceeding the threshold from the same entity and verify that they are denied.", "Attributes": [ { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", + "FamilyName": "Availability", + "FamilyDescription": "TODO: Describe this control family", + "Section": "CCC.SvlsComp.CN02 Implement Function Invocation Rate Limits", "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", + "SubSectionObjective": "Ensure that function invocation is limited to a specified threshold from any single entity, preventing resource exhaustion and denial of service attacks.", "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" + "tlp-red", + "tlp-amber" ], "Recommendation": "", "SectionThreatMappings": [ { "ReferenceId": "CCC", "Identifiers": [ - "CCC.TH01" + "CCC.Core.TH12" ] } ], "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, { "ReferenceId": "NIST-CSF", "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" + "PR.DS-4" ] }, { "ReferenceId": "NIST_800_53", "Identifiers": [ - "IA-2" - ] - } - ] - } - ], - "Checks": [ - "apikeys_api_restrictions_configured", - "apikeys_key_exists", - "apikeys_key_rotated_in_90_days", - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_instance_public_ip", - "cloudsql_instance_public_ip", - "cloudstorage_bucket_public_access" - ] - }, - { - "Id": "CCC.Core.CN03.AR03", - "Description": "When an entity attempts to view information on the service through a user interface, the authentication process MUST require multiple identifying factors from the user.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IA-2" + "SC-5" ] } ] } ], "Checks": [] - }, - { - "Id": "CCC.Core.CN03.AR04", - "Description": "When an entity attempts to view information on the service through an API endpoint, the authentication process MUST require a credential such as an API key or token AND originate from within the trust perimeter.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN03 Implement Multi-factor Authentication (MFA) for Access", - "SubSection": "", - "SubSectionObjective": "Ensure that all sensitive activities require two or more identity factors during authentication to prevent unauthorized access.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-14" - ] - }, - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-7" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "IAM-03", - "IAM-08" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.9.4.2" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "IA-2" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "cloudsql_instance_public_ip", - "cloudsql_instance_public_access", - "cloudstorage_bucket_public_access", - "apikeys_api_restrictions_configured", - "iam_account_access_approval_enabled", - "iam_audit_logs_enabled", - "compute_project_os_login_enabled" - ] - }, - { - "Id": "CCC.Core.CN05.AR01", - "Description": "When an attempt is made to modify data on the service or a child resource, the service MUST block requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "cloudstorage_bucket_public_access", - "compute_instance_public_ip", - "cloudsql_instance_public_ip", - "compute_instance_ip_forwarding_is_enabled", - "compute_instance_default_service_account_in_use", - "compute_instance_default_service_account_in_use_with_full_api_access", - "gke_cluster_no_default_service_account", - "iam_no_service_roles_at_project_level", - "iam_role_kms_enforce_separation_of_duties", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_no_administrative_privileges", - "iam_sa_no_user_managed_keys", - "iam_sa_user_managed_key_rotate_90_days", - "iam_sa_user_managed_key_unused", - "iam_service_account_unused", - "iam_audit_logs_enabled", - "iam_account_access_approval_enabled" - ] - }, - { - "Id": "CCC.Core.CN05.AR02", - "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST refuse requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "iam_account_access_approval_enabled", - "iam_audit_logs_enabled", - "iam_no_service_roles_at_project_level", - "iam_sa_no_administrative_privileges", - "iam_role_kms_enforce_separation_of_duties", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_no_user_managed_keys", - "iam_sa_user_managed_key_rotate_90_days", - "iam_sa_user_managed_key_unused", - "iam_service_account_unused", - "compute_project_os_login_enabled", - "compute_instance_default_service_account_in_use", - "compute_instance_default_service_account_in_use_with_full_api_access", - "compute_instance_public_ip", - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "cloudsql_instance_public_ip", - "cloudstorage_bucket_public_access", - "apikeys_api_restrictions_configured", - "apikeys_key_exists", - "apikeys_key_rotated_in_90_days", - "iam_cloud_asset_inventory_enabled" - ] - }, - { - "Id": "CCC.Core.CN05.AR03", - "Description": "When administrative access or configuration change is attempted on the service or a child resource in a multi-tenant environment, the service MUST refuse requests across tenant boundaries unless the origin is explicitly included in a pre-approved allowlist.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "iam_no_service_roles_at_project_level", - "iam_account_access_approval_enabled", - "iam_audit_logs_enabled", - "iam_role_kms_enforce_separation_of_duties", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_no_administrative_privileges", - "iam_sa_no_user_managed_keys", - "iam_sa_user_managed_key_rotate_90_days", - "iam_sa_user_managed_key_unused", - "iam_service_account_unused", - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_instance_public_ip", - "cloudsql_instance_public_ip", - "cloudsql_instance_public_access", - "cloudstorage_bucket_public_access", - "compute_instance_default_service_account_in_use", - "compute_instance_default_service_account_in_use_with_full_api_access", - "gke_cluster_no_default_service_account" - ] - }, - { - "Id": "CCC.Core.CN05.AR04", - "Description": "When data is requested from outside the trust perimeter, the service MUST refuse requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "", - "SubSection": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "iam_no_service_roles_at_project_level", - "iam_account_access_approval_enabled", - "iam_audit_logs_enabled", - "iam_role_kms_enforce_separation_of_duties", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_no_administrative_privileges", - "iam_sa_no_user_managed_keys", - "iam_sa_user_managed_key_rotate_90_days", - "iam_sa_user_managed_key_unused", - "iam_service_account_unused", - "gke_cluster_no_default_service_account", - "compute_instance_default_service_account_in_use", - "compute_instance_default_service_account_in_use_with_full_api_access", - "compute_instance_block_project_wide_ssh_keys_disabled", - "compute_instance_public_ip", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_firewall_rdp_access_from_the_internet_allowed", - "cloudsql_instance_public_ip", - "cloudsql_instance_public_access", - "cloudstorage_bucket_public_access", - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled", - "apikeys_api_restrictions_configured", - "apikeys_key_exists", - "apikeys_key_rotated_in_90_days" - ] - }, - { - "Id": "CCC.Core.CN05.AR05", - "Description": "When any request is made from outside the trust perimeter, the service MUST NOT provide any response that may indicate the service exists.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_instance_public_ip", - "cloudsql_instance_public_access", - "cloudstorage_bucket_public_access", - "compute_instance_default_service_account_in_use", - "compute_instance_default_service_account_in_use_with_full_api_access", - "gke_cluster_no_default_service_account", - "iam_no_service_roles_at_project_level", - "iam_account_access_approval_enabled", - "iam_audit_logs_enabled", - "iam_sa_no_administrative_privileges", - "iam_sa_no_user_managed_keys", - "iam_role_kms_enforce_separation_of_duties", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_user_managed_key_rotate_90_days", - "iam_sa_user_managed_key_unused", - "iam_service_account_unused", - "apikeys_api_restrictions_configured", - "kms_key_not_publicly_accessible", - "compute_instance_ip_forwarding_is_enabled" - ] - }, - { - "Id": "CCC.Core.CN05.AR06", - "Description": "When any request is made to the service or a child resource, the service MUST refuse requests from unauthorized entities.", - "Attributes": [ - { - "FamilyName": "Identity and Access Management", - "FamilyDescription": "The Identity and Access Management control family ensures that only trusted and authenticated entities can access resources. These controls establish strong authentication, enforce multi-factor verification, and restrict access to approved sources to prevent unauthorized use or data exfiltration. ", - "Section": "CCC.Core.CN05 Prevent Access from Untrusted Entities", - "SubSection": "", - "SubSectionObjective": "Ensure that secure access controls enforce the principle of least privilege to restrict access to authorized entities from explicitly trusted sources only.", - "Applicability": [ - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "PR.AC-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "DSP-01", - "DSP-07", - "DSP-08", - "DSP-10", - "DSP-17" - ] - }, - { - "ReferenceId": "ISO_27001", - "Identifiers": [ - "2013 A.13.1.3" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AC-3" - ] - } - ] - } - ], - "Checks": [ - "compute_firewall_rdp_access_from_the_internet_allowed", - "compute_firewall_ssh_access_from_the_internet_allowed", - "compute_instance_public_ip", - "compute_instance_default_service_account_in_use", - "compute_instance_default_service_account_in_use_with_full_api_access", - "gke_cluster_no_default_service_account", - "cloudstorage_bucket_public_access", - "cloudsql_instance_public_access", - "cloudsql_instance_public_ip", - "cloudsql_instance_private_ip_assignment", - "apikeys_api_restrictions_configured", - "apikeys_key_exists", - "apikeys_key_rotated_in_90_days", - "kms_key_not_publicly_accessible", - "kms_key_rotation_enabled", - "iam_no_service_roles_at_project_level", - "iam_account_access_approval_enabled", - "iam_audit_logs_enabled", - "iam_role_kms_enforce_separation_of_duties", - "iam_role_sa_enforce_separation_of_duties", - "iam_sa_no_administrative_privileges", - "iam_sa_no_user_managed_keys", - "iam_sa_user_managed_key_rotate_90_days", - "iam_sa_user_managed_key_unused", - "iam_service_account_unused" - ] - }, - { - "Id": "CCC.Core.CN04.AR01", - "Description": "When administrative access or configuration change is attempted on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN04 Log All Access and Changes", - "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-08" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3", - "AU-12" - ] - } - ] - } - ], - "Checks": [ - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "iam_audit_logs_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled" - ] - }, - { - "Id": "CCC.Core.CN04.AR02", - "Description": "When any attempt is made to modify data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN04 Log All Access and Changes", - "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-08" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3", - "AU-12" - ] - } - ] - } - ], - "Checks": [ - "iam_audit_logs_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled" - ] - }, - { - "Id": "CCC.Core.CN04.AR03", - "Description": "When any attempt is made to read data on the service or a child resource, the service MUST log the client identity, time, and result of the attempt.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN04 Log All Access and Changes", - "SubSection": "", - "SubSectionObjective": "Ensure that all access attempts are logged to maintain a detailed audit trail for security and compliance purposes.", - "Applicability": [ - "tlp-red" - ], - "Recommendation": "", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH01" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-3" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-08" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-2", - "AU-3", - "AU-12" - ] - } - ] - } - ], - "Checks": [ - "iam_audit_logs_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled" - ] - }, - { - "Id": "CCC.Core.CN07.AR01", - "Description": "When enumeration activities are detected, the service MUST publish an event to a monitored channel which includes the client identity, time, and nature of the activity.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", - "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", - "Applicability": [ - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement event publication mechanisms and alerts for patterns indicative of enumeration activities, such as repeated access attempts, requests, or liveness probes. Configure alerts to notify security teams of any activities that merit further investigation. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH15" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-05", - "SEF-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-6" - ] - } - ] - } - ], - "Checks": [ - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_sink_created" - ] - }, - { - "Id": "CCC.Core.CN07.AR02", - "Description": "When enumeration activities are detected, the service MUST log the client identity, time, and nature of the activity.", - "Attributes": [ - { - "FamilyName": "Logging & Monitoring", - "FamilyDescription": "The Logging & Monitoring control family ensures that access, changes, and security-relevant events are captured, monitored, and alerted on in order to provide visibility, support incident response, and meet compliance requirements. ", - "Section": "CCC.Core.CN07 Alert on Unusual Enumeration Activity", - "SubSection": "", - "SubSectionObjective": "Ensure that logs and associated alerts are generated when unusual enumeration activity is detected that may indicate reconnaissance activities.", - "Applicability": [ - "tlp-clear", - "tlp-green", - "tlp-amber", - "tlp-red" - ], - "Recommendation": "Implement logging mechanisms to capture details of enumeration activities, including client identity, timestamps, and activity nature. Retain logs according to organizational policies, and occasionally review them for patterns that may indicate reconnaissance activities. ", - "SectionThreatMappings": [ - { - "ReferenceId": "CCC", - "Identifiers": [ - "CCC.TH15" - ] - } - ], - "SectionGuidelineMappings": [ - { - "ReferenceId": "NIST-CSF", - "Identifiers": [ - "DE.AE-1" - ] - }, - { - "ReferenceId": "CCM", - "Identifiers": [ - "LOG-05", - "SEF-05" - ] - }, - { - "ReferenceId": "NIST_800_53", - "Identifiers": [ - "AU-6" - ] - } - ] - } - ], - "Checks": [ - "iam_audit_logs_enabled", - "logging_sink_created", - "logging_log_metric_filter_and_alert_for_audit_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_custom_role_changes_enabled", - "logging_log_metric_filter_and_alert_for_bucket_permission_changes_enabled", - "logging_log_metric_filter_and_alert_for_project_ownership_changes_enabled", - "logging_log_metric_filter_and_alert_for_sql_instance_configuration_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_network_route_changes_enabled", - "logging_log_metric_filter_and_alert_for_vpc_firewall_rule_changes_enabled" - ] } ] } diff --git a/prowler/lib/outputs/compliance/ccc/ccc.py b/prowler/lib/outputs/compliance/ccc/ccc.py new file mode 100644 index 0000000000..99a6c91cd9 --- /dev/null +++ b/prowler/lib/outputs/compliance/ccc/ccc.py @@ -0,0 +1,98 @@ +from colorama import Fore, Style +from tabulate import tabulate + +from prowler.config.config import orange_color + + +def get_ccc_table( + findings: list, + bulk_checks_metadata: dict, + compliance_framework: str, + output_filename: str, + output_directory: str, + compliance_overview: bool, +): + section_table = { + "Provider": [], + "Section": [], + "Status": [], + "Muted": [], + } + pass_count = [] + fail_count = [] + muted_count = [] + sections = {} + for index, finding in enumerate(findings): + check = bulk_checks_metadata[finding.check_metadata.CheckID] + check_compliances = check.Compliance + for compliance in check_compliances: + if compliance.Framework == "CCC": + for requirement in compliance.Requirements: + for attribute in requirement.Attributes: + section = attribute.Section + + if section not in sections: + sections[section] = {"FAIL": 0, "PASS": 0, "Muted": 0} + + if finding.muted: + if index not in muted_count: + muted_count.append(index) + sections[section]["Muted"] += 1 + else: + if finding.status == "FAIL" and index not in fail_count: + fail_count.append(index) + sections[section]["FAIL"] += 1 + elif finding.status == "PASS" and index not in pass_count: + pass_count.append(index) + sections[section]["PASS"] += 1 + + sections = dict(sorted(sections.items())) + for section in sections: + section_table["Provider"].append(compliance.Provider) + section_table["Section"].append(section) + if sections[section]["FAIL"] > 0: + section_table["Status"].append( + f"{Fore.RED}FAIL({sections[section]['FAIL']}){Style.RESET_ALL}" + ) + else: + if sections[section]["PASS"] > 0: + section_table["Status"].append( + f"{Fore.GREEN}PASS({sections[section]['PASS']}){Style.RESET_ALL}" + ) + else: + section_table["Status"].append(f"{Fore.GREEN}PASS{Style.RESET_ALL}") + section_table["Muted"].append( + f"{orange_color}{sections[section]['Muted']}{Style.RESET_ALL}" + ) + + if ( + len(fail_count) + len(pass_count) + len(muted_count) > 1 + ): # If there are no resources, don't print the compliance table + print( + f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:" + ) + total_findings_count = len(fail_count) + len(pass_count) + len(muted_count) + overview_table = [ + [ + f"{Fore.RED}{round(len(fail_count) / total_findings_count * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}", + f"{Fore.GREEN}{round(len(pass_count) / total_findings_count * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}", + f"{orange_color}{round(len(muted_count) / total_findings_count * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}", + ] + ] + print(tabulate(overview_table, tablefmt="rounded_grid")) + if not compliance_overview: + if len(fail_count) > 0 and len(section_table["Section"]) > 0: + print( + f"\nFramework {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Results:" + ) + print( + tabulate( + section_table, + tablefmt="rounded_grid", + headers="keys", + ) + ) + print(f"\nDetailed results of {compliance_framework.upper()} are in:") + print( + f" - CSV: {output_directory}/compliance/{output_filename}_{compliance_framework}.csv\n" + ) diff --git a/prowler/lib/outputs/compliance/compliance.py b/prowler/lib/outputs/compliance/compliance.py index bb7fbf1146..d399900837 100644 --- a/prowler/lib/outputs/compliance/compliance.py +++ b/prowler/lib/outputs/compliance/compliance.py @@ -3,6 +3,7 @@ import sys from prowler.lib.check.models import Check_Report from prowler.lib.logger import logger from prowler.lib.outputs.compliance.c5.c5 import get_c5_table +from prowler.lib.outputs.compliance.ccc.ccc import get_ccc_table from prowler.lib.outputs.compliance.cis.cis import get_cis_table from prowler.lib.outputs.compliance.csa.csa import get_csa_table from prowler.lib.outputs.compliance.ens.ens import get_ens_table @@ -104,6 +105,15 @@ def display_compliance_table( output_directory, compliance_overview, ) + elif compliance_framework.startswith("ccc_"): + get_ccc_table( + findings, + bulk_checks_metadata, + compliance_framework, + output_filename, + output_directory, + compliance_overview, + ) else: get_generic_compliance_table( findings, diff --git a/tests/lib/outputs/compliance/ccc/__init__.py b/tests/lib/outputs/compliance/ccc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/lib/outputs/compliance/ccc/ccc_aws_test.py b/tests/lib/outputs/compliance/ccc/ccc_aws_test.py new file mode 100644 index 0000000000..39460fd0ec --- /dev/null +++ b/tests/lib/outputs/compliance/ccc/ccc_aws_test.py @@ -0,0 +1,138 @@ +from io import StringIO +from unittest import mock + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.ccc.ccc_aws import CCC_AWS +from prowler.lib.outputs.compliance.ccc.models import CCC_AWSModel +from tests.lib.outputs.compliance.fixtures import CCC_AWS_FIXTURE +from tests.lib.outputs.fixtures.fixtures import generate_finding_output +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1 + + +class TestAWSCCC: + def test_output_transform_evaluated_requirement(self): + findings = [ + generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"}) + ] + + output = CCC_AWS(findings, CCC_AWS_FIXTURE) + output_data = output.data[0] + + assert isinstance(output_data, CCC_AWSModel) + assert output_data.Provider == "aws" + assert output_data.AccountId == AWS_ACCOUNT_NUMBER + assert output_data.Region == AWS_REGION_EU_WEST_1 + assert output_data.Description == CCC_AWS_FIXTURE.Description + assert output_data.Requirements_Id == CCC_AWS_FIXTURE.Requirements[0].Id + assert ( + output_data.Requirements_Description + == CCC_AWS_FIXTURE.Requirements[0].Description + ) + attribute = CCC_AWS_FIXTURE.Requirements[0].Attributes[0] + assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName + assert ( + output_data.Requirements_Attributes_FamilyDescription + == attribute.FamilyDescription + ) + assert output_data.Requirements_Attributes_Section == attribute.Section + assert output_data.Requirements_Attributes_SubSection == attribute.SubSection + assert ( + output_data.Requirements_Attributes_SubSectionObjective + == attribute.SubSectionObjective + ) + assert ( + output_data.Requirements_Attributes_Applicability == attribute.Applicability + ) + assert ( + output_data.Requirements_Attributes_Recommendation + == attribute.Recommendation + ) + assert ( + output_data.Requirements_Attributes_SectionThreatMappings + == attribute.SectionThreatMappings + ) + assert ( + output_data.Requirements_Attributes_SectionGuidelineMappings + == attribute.SectionGuidelineMappings + ) + assert output_data.Status == "PASS" + assert output_data.StatusExtended == "" + assert output_data.ResourceId == "" + assert output_data.ResourceName == "" + assert output_data.CheckId == "service_test_check_id" + assert output_data.Muted is False + + def test_output_transform_manual_requirement(self): + # Use a finding for the evaluated requirement so the manual one is appended + # by the manual-loop branch (Checks=[]). + findings = [ + generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"}) + ] + + output = CCC_AWS(findings, CCC_AWS_FIXTURE) + # data[0] is the evaluated PASS row, data[1] is the manual row + manual_row = output.data[1] + + assert isinstance(manual_row, CCC_AWSModel) + assert manual_row.Provider == "aws" + assert manual_row.AccountId == "" + assert manual_row.Region == "" + assert manual_row.Description == CCC_AWS_FIXTURE.Description + assert manual_row.Requirements_Id == CCC_AWS_FIXTURE.Requirements[1].Id + manual_attribute = CCC_AWS_FIXTURE.Requirements[1].Attributes[0] + assert ( + manual_row.Requirements_Attributes_FamilyName == manual_attribute.FamilyName + ) + assert manual_row.Requirements_Attributes_Section == manual_attribute.Section + assert manual_row.Status == "MANUAL" + assert manual_row.StatusExtended == "Manual check" + assert manual_row.ResourceId == "manual_check" + assert manual_row.ResourceName == "Manual check" + assert manual_row.CheckId == "manual" + assert manual_row.Muted is False + + @freeze_time("2025-01-01 00:00:00") + @mock.patch( + "prowler.lib.outputs.compliance.ccc.ccc_aws.timestamp", + "2025-01-01 00:00:00", + ) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [ + generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"}) + ] + output = CCC_AWS(findings, CCC_AWS_FIXTURE) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + + # Header check: AWS-specific columns must be present + header = content.split("\r\n", 1)[0] + assert "ACCOUNTID" in header + assert "REGION" in header + assert "REQUIREMENTS_ATTRIBUTES_FAMILYNAME" in header + assert "REQUIREMENTS_ATTRIBUTES_SECTION" in header + assert "REQUIREMENTS_ATTRIBUTES_APPLICABILITY" in header + assert "REQUIREMENTS_ATTRIBUTES_SECTIONTHREATMAPPINGS" in header + # Header should NOT contain Azure or GCP-only columns + assert "SUBSCRIPTIONID" not in header + assert "PROJECTID" not in header + + # Body checks: evaluated row + manual row + rows = [r for r in content.split("\r\n") if r] + assert len(rows) == 3 # header + evaluated + manual + assert "CCC.Core.CN01.AR01" in rows[1] + assert "PASS" in rows[1] + assert AWS_ACCOUNT_NUMBER in rows[1] + assert AWS_REGION_EU_WEST_1 in rows[1] + assert "CCC.IAM.CN01.AR01" in rows[2] + assert "MANUAL" in rows[2] + assert "manual_check" in rows[2] + # The frozen timestamp should appear + assert "2025-01-01 00:00:00" in rows[1] diff --git a/tests/lib/outputs/compliance/ccc/ccc_azure_test.py b/tests/lib/outputs/compliance/ccc/ccc_azure_test.py new file mode 100644 index 0000000000..a3a2f7d028 --- /dev/null +++ b/tests/lib/outputs/compliance/ccc/ccc_azure_test.py @@ -0,0 +1,99 @@ +from io import StringIO +from unittest import mock + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.ccc.ccc_azure import CCC_Azure +from prowler.lib.outputs.compliance.ccc.models import CCC_AzureModel +from tests.lib.outputs.compliance.fixtures import CCC_AZURE_FIXTURE +from tests.lib.outputs.fixtures.fixtures import generate_finding_output +from tests.providers.azure.azure_fixtures import AZURE_SUBSCRIPTION_ID + +AZURE_LOCATION = "westeurope" + + +class TestAzureCCC: + def test_output_transform_evaluated_requirement(self): + findings = [ + generate_finding_output( + provider="azure", + compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"}, + account_uid=AZURE_SUBSCRIPTION_ID, + region=AZURE_LOCATION, + ) + ] + + output = CCC_Azure(findings, CCC_AZURE_FIXTURE) + output_data = output.data[0] + + assert isinstance(output_data, CCC_AzureModel) + assert output_data.Provider == "azure" + assert output_data.SubscriptionId == AZURE_SUBSCRIPTION_ID + assert output_data.Location == AZURE_LOCATION + assert output_data.Description == CCC_AZURE_FIXTURE.Description + assert output_data.Requirements_Id == CCC_AZURE_FIXTURE.Requirements[0].Id + attribute = CCC_AZURE_FIXTURE.Requirements[0].Attributes[0] + assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName + assert output_data.Requirements_Attributes_Section == attribute.Section + assert ( + output_data.Requirements_Attributes_Applicability == attribute.Applicability + ) + assert output_data.Status == "PASS" + assert output_data.CheckId == "service_test_check_id" + + def test_output_transform_manual_requirement(self): + findings = [ + generate_finding_output( + provider="azure", + compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"}, + account_uid=AZURE_SUBSCRIPTION_ID, + region=AZURE_LOCATION, + ) + ] + output = CCC_Azure(findings, CCC_AZURE_FIXTURE) + manual_row = output.data[1] + + assert isinstance(manual_row, CCC_AzureModel) + assert manual_row.Provider == "azure" + assert manual_row.SubscriptionId == "" + assert manual_row.Location == "" + assert manual_row.Requirements_Id == CCC_AZURE_FIXTURE.Requirements[1].Id + assert manual_row.Status == "MANUAL" + assert manual_row.CheckId == "manual" + + @freeze_time("2025-01-01 00:00:00") + @mock.patch( + "prowler.lib.outputs.compliance.ccc.ccc_azure.timestamp", + "2025-01-01 00:00:00", + ) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [ + generate_finding_output( + provider="azure", + compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"}, + account_uid=AZURE_SUBSCRIPTION_ID, + region=AZURE_LOCATION, + ) + ] + output = CCC_Azure(findings, CCC_AZURE_FIXTURE) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + header = content.split("\r\n", 1)[0] + assert "SUBSCRIPTIONID" in header + assert "LOCATION" in header + assert "ACCOUNTID" not in header + assert "PROJECTID" not in header + assert "REGION" not in header + rows = [r for r in content.split("\r\n") if r] + assert len(rows) == 3 + assert "CCC.Core.CN01.AR01" in rows[1] + assert AZURE_SUBSCRIPTION_ID in rows[1] + assert "CCC.IAM.CN01.AR01" in rows[2] + assert "MANUAL" in rows[2] diff --git a/tests/lib/outputs/compliance/ccc/ccc_gcp_test.py b/tests/lib/outputs/compliance/ccc/ccc_gcp_test.py new file mode 100644 index 0000000000..9ba2127235 --- /dev/null +++ b/tests/lib/outputs/compliance/ccc/ccc_gcp_test.py @@ -0,0 +1,99 @@ +from io import StringIO +from unittest import mock + +from freezegun import freeze_time +from mock import patch + +from prowler.lib.outputs.compliance.ccc.ccc_gcp import CCC_GCP +from prowler.lib.outputs.compliance.ccc.models import CCC_GCPModel +from tests.lib.outputs.compliance.fixtures import CCC_GCP_FIXTURE +from tests.lib.outputs.fixtures.fixtures import generate_finding_output + +GCP_PROJECT_ID = "test-project" +GCP_LOCATION = "europe-west1" + + +class TestGCPCCC: + def test_output_transform_evaluated_requirement(self): + findings = [ + generate_finding_output( + provider="gcp", + compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"}, + account_uid=GCP_PROJECT_ID, + region=GCP_LOCATION, + ) + ] + + output = CCC_GCP(findings, CCC_GCP_FIXTURE) + output_data = output.data[0] + + assert isinstance(output_data, CCC_GCPModel) + assert output_data.Provider == "gcp" + assert output_data.ProjectId == GCP_PROJECT_ID + assert output_data.Location == GCP_LOCATION + assert output_data.Description == CCC_GCP_FIXTURE.Description + assert output_data.Requirements_Id == CCC_GCP_FIXTURE.Requirements[0].Id + attribute = CCC_GCP_FIXTURE.Requirements[0].Attributes[0] + assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName + assert output_data.Requirements_Attributes_Section == attribute.Section + assert ( + output_data.Requirements_Attributes_Applicability == attribute.Applicability + ) + assert output_data.Status == "PASS" + assert output_data.CheckId == "service_test_check_id" + + def test_output_transform_manual_requirement(self): + findings = [ + generate_finding_output( + provider="gcp", + compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"}, + account_uid=GCP_PROJECT_ID, + region=GCP_LOCATION, + ) + ] + output = CCC_GCP(findings, CCC_GCP_FIXTURE) + manual_row = output.data[1] + + assert isinstance(manual_row, CCC_GCPModel) + assert manual_row.Provider == "gcp" + assert manual_row.ProjectId == "" + assert manual_row.Location == "" + assert manual_row.Requirements_Id == CCC_GCP_FIXTURE.Requirements[1].Id + assert manual_row.Status == "MANUAL" + assert manual_row.CheckId == "manual" + + @freeze_time("2025-01-01 00:00:00") + @mock.patch( + "prowler.lib.outputs.compliance.ccc.ccc_gcp.timestamp", + "2025-01-01 00:00:00", + ) + def test_batch_write_data_to_file(self): + mock_file = StringIO() + findings = [ + generate_finding_output( + provider="gcp", + compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"}, + account_uid=GCP_PROJECT_ID, + region=GCP_LOCATION, + ) + ] + output = CCC_GCP(findings, CCC_GCP_FIXTURE) + output._file_descriptor = mock_file + + with patch.object(mock_file, "close", return_value=None): + output.batch_write_data_to_file() + + mock_file.seek(0) + content = mock_file.read() + header = content.split("\r\n", 1)[0] + assert "PROJECTID" in header + assert "LOCATION" in header + assert "ACCOUNTID" not in header + assert "SUBSCRIPTIONID" not in header + assert "REGION" not in header + rows = [r for r in content.split("\r\n") if r] + assert len(rows) == 3 + assert "CCC.Core.CN01.AR01" in rows[1] + assert GCP_PROJECT_ID in rows[1] + assert "CCC.IAM.CN01.AR01" in rows[2] + assert "MANUAL" in rows[2] diff --git a/tests/lib/outputs/compliance/fixtures.py b/tests/lib/outputs/compliance/fixtures.py index 7b29411663..41c68d148f 100644 --- a/tests/lib/outputs/compliance/fixtures.py +++ b/tests/lib/outputs/compliance/fixtures.py @@ -1,5 +1,6 @@ from prowler.lib.check.compliance_models import ( AWS_Well_Architected_Requirement_Attribute, + CCC_Requirement_Attribute, CIS_Requirement_Attribute, Compliance, Compliance_Requirement, @@ -1022,3 +1023,169 @@ PROWLER_THREATSCORE_M365 = Compliance( ), ], ) + + +# CCC fixtures cover the three providers Prowler ships catalogs for. Each +# fixture has one auto-evaluated requirement (with Checks) and one manual +# requirement (Checks=[]) so test suites can exercise both paths. +CCC_AWS_FIXTURE = Compliance( + Framework="CCC", + Name="Common Cloud Controls Catalog (CCC)", + Provider="AWS", + Version="v2025.10", + Description="Common Cloud Controls Catalog (CCC) for AWS", + Requirements=[ + Compliance_Requirement( + Checks=["service_test_check_id"], + Id="CCC.Core.CN01.AR01", + Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", + Attributes=[ + CCC_Requirement_Attribute( + FamilyName="Data", + FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + Section="CCC.Core.CN01 Encrypt Data for Transmission", + SubSection="", + SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + Applicability=["tlp-green", "tlp-amber", "tlp-red"], + Recommendation="Most cloud services enable TLS 1.3 by default.", + SectionThreatMappings=[ + {"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]} + ], + SectionGuidelineMappings=[ + {"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]} + ], + ) + ], + ), + Compliance_Requirement( + Checks=[], + Id="CCC.IAM.CN01.AR01", + Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials or generating temporary session tokens.", + Attributes=[ + CCC_Requirement_Attribute( + FamilyName="Identity and Access Management", + FamilyDescription="Controls that restrict who can access and modify IAM resources.", + Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation", + SubSection="", + SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.", + Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"], + Recommendation="", + SectionThreatMappings=[ + {"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]} + ], + SectionGuidelineMappings=[ + {"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]} + ], + ) + ], + ), + ], +) + +CCC_AZURE_FIXTURE = Compliance( + Framework="CCC", + Name="Common Cloud Controls Catalog (CCC)", + Provider="Azure", + Version="v2025.10", + Description="Common Cloud Controls Catalog (CCC) for Azure", + Requirements=[ + Compliance_Requirement( + Checks=["service_test_check_id"], + Id="CCC.Core.CN01.AR01", + Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", + Attributes=[ + CCC_Requirement_Attribute( + FamilyName="Data", + FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + Section="CCC.Core.CN01 Encrypt Data for Transmission", + SubSection="", + SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + Applicability=["tlp-green", "tlp-amber", "tlp-red"], + Recommendation="Most cloud services enable TLS 1.3 by default.", + SectionThreatMappings=[ + {"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]} + ], + SectionGuidelineMappings=[ + {"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]} + ], + ) + ], + ), + Compliance_Requirement( + Checks=[], + Id="CCC.IAM.CN01.AR01", + Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials.", + Attributes=[ + CCC_Requirement_Attribute( + FamilyName="Identity and Access Management", + FamilyDescription="Controls that restrict who can access and modify IAM resources.", + Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation", + SubSection="", + SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.", + Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"], + Recommendation="", + SectionThreatMappings=[ + {"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]} + ], + SectionGuidelineMappings=[ + {"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]} + ], + ) + ], + ), + ], +) + +CCC_GCP_FIXTURE = Compliance( + Framework="CCC", + Name="Common Cloud Controls Catalog (CCC)", + Provider="GCP", + Version="v2025.10", + Description="Common Cloud Controls Catalog (CCC) for GCP", + Requirements=[ + Compliance_Requirement( + Checks=["service_test_check_id"], + Id="CCC.Core.CN01.AR01", + Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.", + Attributes=[ + CCC_Requirement_Attribute( + FamilyName="Data", + FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.", + Section="CCC.Core.CN01 Encrypt Data for Transmission", + SubSection="", + SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.", + Applicability=["tlp-green", "tlp-amber", "tlp-red"], + Recommendation="Most cloud services enable TLS 1.3 by default.", + SectionThreatMappings=[ + {"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]} + ], + SectionGuidelineMappings=[ + {"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]} + ], + ) + ], + ), + Compliance_Requirement( + Checks=[], + Id="CCC.IAM.CN01.AR01", + Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials.", + Attributes=[ + CCC_Requirement_Attribute( + FamilyName="Identity and Access Management", + FamilyDescription="Controls that restrict who can access and modify IAM resources.", + Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation", + SubSection="", + SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.", + Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"], + Recommendation="", + SectionThreatMappings=[ + {"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]} + ], + SectionGuidelineMappings=[ + {"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]} + ], + ) + ], + ), + ], +) From 7eb204fff0eb5ea123ce239b10b9c3e092fd5490 Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:39:43 +0200 Subject: [PATCH 20/65] docs: classify supported providers by category on main page (#10621) Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com> --- docs/introduction.mdx | 70 ++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/docs/introduction.mdx b/docs/introduction.mdx index 766f60e3c0..5747eeadb1 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -21,29 +21,57 @@ ## Supported Providers -The supported providers right now are: +Prowler supports a wide range of providers organized by category: -| Provider | Support | Audit Scope/Entities | Interface | -| -------------------------------------------------------------------------------- | ---------- | ---------------------------- | ------------ | -| [AWS](/user-guide/providers/aws/getting-started-aws) | Official | Accounts | UI, API, CLI | -| [Azure](/user-guide/providers/azure/getting-started-azure) | Official | Subscriptions | UI, API, CLI | -| [Google Cloud](/user-guide/providers/gcp/getting-started-gcp) | Official | Projects | UI, API, CLI | -| [Kubernetes](/user-guide/providers/kubernetes/getting-started-k8s) | Official | Clusters | UI, API, CLI | -| [M365](/user-guide/providers/microsoft365/getting-started-m365) | Official | Tenants | UI, API, CLI | -| [Github](/user-guide/providers/github/getting-started-github) | Official | Organizations / Repositories | UI, API, CLI | -| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | Tenancies / Compartments | UI, API, CLI | -| [Alibaba Cloud](/user-guide/providers/alibabacloud/getting-started-alibabacloud) | Official | Accounts | UI, API, CLI | -| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | UI, API, CLI | -| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | Repositories | UI, API, CLI | -| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | Organizations | UI, API, CLI | -| [OpenStack](/user-guide/providers/openstack/getting-started-openstack) | Official | Projects | UI, API, CLI | -| [Vercel](/user-guide/providers/vercel/getting-started-vercel) | Official | Teams / Projects | CLI | -| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | Models | CLI | -| [Image](/user-guide/providers/image/getting-started-image) | Official | Container Images | CLI, API | -| [Google Workspace](/user-guide/providers/googleworkspace/getting-started-googleworkspace) | Official | Domains | CLI | -| **NHN** | Unofficial | Tenants | CLI | +### Cloud Service Providers (Infrastructure) -For more information about the checks and compliance of each provider visit [Prowler Hub](https://hub.prowler.com). +| Provider | Support | Audit Scope/Entities | Interface | +| -------------------------------------------------------------------------------- | ---------- | ------------------------ | ------------ | +| [Alibaba Cloud](/user-guide/providers/alibabacloud/getting-started-alibabacloud) | Official | Accounts | UI, API, CLI | +| [AWS](/user-guide/providers/aws/getting-started-aws) | Official | Accounts | UI, API, CLI | +| [Azure](/user-guide/providers/azure/getting-started-azure) | Official | Subscriptions | UI, API, CLI | +| [Cloudflare](/user-guide/providers/cloudflare/getting-started-cloudflare) | Official | Accounts | UI, API, CLI | +| [Google Cloud](/user-guide/providers/gcp/getting-started-gcp) | Official | Projects | UI, API, CLI | +| **NHN** | Unofficial | Tenants | CLI | +| [OpenStack](/user-guide/providers/openstack/getting-started-openstack) | Official | Projects | UI, API, CLI | +| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | Tenancies / Compartments | UI, API, CLI | + +### Infrastructure as Code Providers + +| Provider | Support | Audit Scope/Entities | Interface | +| --------------------------------------------------------------------- | -------- | -------------------- | ------------ | +| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | Repositories | UI, API, CLI | + +### Software as a Service (SaaS) Providers + +| Provider | Support | Audit Scope/Entities | Interface | +| ----------------------------------------------------------------------------------------- | -------- | ---------------------------- | ------------ | +| [GitHub](/user-guide/providers/github/getting-started-github) | Official | Organizations / Repositories | UI, API, CLI | +| [Google Workspace](/user-guide/providers/googleworkspace/getting-started-googleworkspace) | Official | Domains | CLI | +| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | Models | CLI | +| [M365](/user-guide/providers/microsoft365/getting-started-m365) | Official | Tenants | UI, API, CLI | +| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | Organizations | UI, API, CLI | +| [Vercel](/user-guide/providers/vercel/getting-started-vercel) | Official | Teams / Projects | CLI | + +### Kubernetes + +| Provider | Support | Audit Scope/Entities | Interface | +| -------------------------------------------------------------------------- | -------- | -------------------- | ------------ | +| [Kubernetes](/user-guide/providers/kubernetes/getting-started-k8s) | Official | Clusters | UI, API, CLI | + +### Containers + +| Provider | Support | Audit Scope/Entities | Interface | +| ------------------------------------------------------------------- | -------- | -------------------- | --------- | +| [Image](/user-guide/providers/image/getting-started-image) | Official | Container Images / Registries | CLI, API | + +### Custom Providers (Prowler Cloud Enterprise Only) + +| Provider | Support | Audit Scope/Entities | Interface | +| -------------------- | -------- | -------------------- | --------- | +| VMware/Broadcom VCF | Official | Infrastructure | CLI | + +For more information about the checks and compliance of each provider, visit [Prowler Hub](https://hub.prowler.com). ## Where to go next? From ca50b24d7708d06132721fd11491dbdbf806e54e Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:40:44 +0200 Subject: [PATCH 21/65] docs: add Vercel Cloud getting started (#10609) --- .../providers/select-vercel-prowler-cloud.png | Bin 0 -> 151052 bytes docs/images/providers/vercel-launch-scan.png | Bin 0 -> 82946 bytes docs/images/providers/vercel-team-id-form.png | Bin 0 -> 79526 bytes docs/images/providers/vercel-token-form.png | Bin 0 -> 91853 bytes .../vercel/getting-started-vercel.mdx | 56 +++++++++++++++++- 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 docs/images/providers/select-vercel-prowler-cloud.png create mode 100644 docs/images/providers/vercel-launch-scan.png create mode 100644 docs/images/providers/vercel-team-id-form.png create mode 100644 docs/images/providers/vercel-token-form.png diff --git a/docs/images/providers/select-vercel-prowler-cloud.png b/docs/images/providers/select-vercel-prowler-cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..b332103e1ff1187a4d6796436f20d45c09abe698 GIT binary patch literal 151052 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%0AMH!$VOFG;00c4uLy#;7gyHofMpXR}2w-lfi z%pH4_Ui3Y@B&^0`=r-2w6xr0?W}xmx%>wi=p`Y@iVG7J}eLq_YDW0|*@O$!DLJbl( zWi0sKm|CUlz|F|;H!8q{+i#)H%1T#;iIrtzx|oZJX&m8_=J|1QPA;hMiS73t7PZ{K z_p=SzdXM4cy4;4DnObrgmeWuilsEvp4@3h3;=Pfy7Kt3O@KUOYMoBEcp2j0*HV7U-wAk7vO!=WtMS{iMpg z)HTyB&Ifs5>gmtO~tA9xzB3sm)S1j!8`xVC6JzA6{?oRCz%*x_mOz+W;tR74g)g+^gC)F zEj1=JqLPy>s}IUX+bze#B&x8voa8tH+SthH`9@kdMz@DkhiKy#kMd}xEG z)K15AmfOi**~bL#N%rXqbaspYq_GG5mi-QrFFyxId;?_FvA5|ki099daYyXmoUID` zsD`GrMU0lCGExr>4>57}t74`9EivxffC0L-jLqH=6Vs26k8jV(!`=bSn8?=F@w9rh z1#;=1cWAzdwP6FO9*d-|u&QeXE}IV;@AjI*{W2KGkB;gz1^nX&Q$k1}PTbs_hNO&? z40}Y&e=q2v90^(&H|gA7d(kpZORxHd9eZ~mS(?09rN(50A@w$Q32f%ITEW~t^1f({ z;P`3)cDuQx2F5f+u4M@Mtzc2<{qtS>@kqW}cb%^`-2&t`)sB17jv(5yc-GT=v=S}V z>yt(5UNA`lVCNv+V7n~qp)s4=)%ffHm?ucAZ(vQyx98=!T5t4(mkn-v0X`H;4Ys^M zGLuoN-7xYtzy=6M3kRtQRs1i2gLPE7Z~tka+BdwT)&rw9R8 ziNf2u)v$=0b^r@=o9Hv7#463((nUYQFcIeFI<(k*R~q5pl$8j-J@79Q=s%9aK7AO= zqF3O#7WSN&u5@0D{*=n5rrZ~HzzGXD5DI}&Px`>FrM8+aAOAh^zV{o$Z=FcZ8nZ8E zkFXY#`Qe|xj0rqzv@h8c!S{t^u$b51S}5%wHQcBL1=(uRUdIllvkry

vPR@eJx4 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 BQiT8j literal 0 HcmV?d00001 diff --git a/docs/images/providers/vercel-launch-scan.png b/docs/images/providers/vercel-launch-scan.png new file mode 100644 index 0000000000000000000000000000000000000000..4e7dd36a25fa80b0f3865539569411b5d260a1c3 GIT binary patch literal 82946 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 literal 0 HcmV?d00001 diff --git a/docs/images/providers/vercel-token-form.png b/docs/images/providers/vercel-token-form.png new file mode 100644 index 0000000000000000000000000000000000000000..991b9ddedc3cb3553d763ebb0c9f7c9ae29f98a7 GIT binary patch literal 91853 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

Z^?P9D*6it*DMPK%hAtU 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 literal 0 HcmV?d00001 diff --git a/docs/user-guide/providers/vercel/getting-started-vercel.mdx b/docs/user-guide/providers/vercel/getting-started-vercel.mdx index 67d4853d18..ddec26e6dc 100644 --- a/docs/user-guide/providers/vercel/getting-started-vercel.mdx +++ b/docs/user-guide/providers/vercel/getting-started-vercel.mdx @@ -13,9 +13,63 @@ Set up authentication for Vercel with the [Vercel Authentication](/user-guide/pr - Create a Vercel API Token with access to the target team - Identify the Team ID (optional, required to scope the scan to a single team) + + + Onboard Vercel using Prowler Cloud + + + Onboard Vercel using Prowler CLI + + + +## Prowler Cloud + + + +### Step 1: Add the Provider + +1. Go to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app). +2. Navigate to "Configuration" > "Cloud Providers". + + ![Cloud Providers Page](/images/prowler-app/cloud-providers-page.png) + +3. Click "Add Cloud Provider". + + ![Add a Cloud Provider](/images/prowler-app/add-cloud-provider.png) + +4. Select "Vercel". + + ![Select Vercel](/images/providers/select-vercel-prowler-cloud.png) + +5. Enter the **Team ID** and an optional alias, then click "Next". + + ![Add Vercel Team ID](/images/providers/vercel-team-id-form.png) + + +The Team ID can be found in the Vercel Dashboard under "Settings" > "General". It follows the format `team_xxxxxxxxxxxxxxxxxxxx`. For detailed instructions, see the [Authentication guide](/user-guide/providers/vercel/authentication). + + +### Step 2: Provide Credentials + +1. Enter the **API Token** created in the Vercel Dashboard. + + ![API Token Form](/images/providers/vercel-token-form.png) + +For the complete token creation workflow, follow the [Authentication guide](/user-guide/providers/vercel/authentication#api-token). + +### Step 3: Launch the Scan + +1. Review the connection summary. +2. Choose the scan schedule: run a single scan or set up daily scans (every 24 hours). +3. Click **Launch Scan** to start auditing Vercel. + + ![Launch Scan](/images/providers/vercel-launch-scan.png) + +--- + ## Prowler CLI - + ### Step 1: Set Up Authentication From cccb3a4b945b71eac5e94c1853c033f6f6bcfe16 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:42:49 +0100 Subject: [PATCH 22/65] chore(sdk,mcp): pin direct dependencies to exact versions (#10593) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Adrián Jesús Peña Rodríguez --- docs/developer-guide/introduction.mdx | 2 ++ mcp_server/CHANGELOG.md | 4 ++++ mcp_server/pyproject.toml | 2 +- mcp_server/uv.lock | 2 +- poetry.lock | 10 +++++----- prowler/CHANGELOG.md | 1 + pyproject.toml | 8 ++++---- 7 files changed, 18 insertions(+), 11 deletions(-) diff --git a/docs/developer-guide/introduction.mdx b/docs/developer-guide/introduction.mdx index 2a4aa3abe1..947c4b4c71 100644 --- a/docs/developer-guide/introduction.mdx +++ b/docs/developer-guide/introduction.mdx @@ -163,6 +163,8 @@ These resources help ensure that AI-assisted contributions maintain consistency All dependencies are listed in the `pyproject.toml` file. +The SDK keeps direct dependencies pinned to exact versions, while `poetry.lock` records the full resolved dependency tree and the artifact hashes for every package. Use `poetry install` from the lock file instead of ad-hoc `pip` installs when you need a reproducible environment. + For proper code documentation, refer to the following and follow the code documentation practices presented there: [Google Python Style Guide - Comments and Docstrings](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings). diff --git a/mcp_server/CHANGELOG.md b/mcp_server/CHANGELOG.md index 21aa71dbf2..e94487e257 100644 --- a/mcp_server/CHANGELOG.md +++ b/mcp_server/CHANGELOG.md @@ -8,6 +8,10 @@ All notable changes to the **Prowler MCP Server** are documented in this file. - Resource events tool to get timeline for a resource (who, what, when) [(#10412)](https://github.com/prowler-cloud/prowler/pull/10412) +### 🔄 Changed + +- Pin `httpx` dependency to exact version for reproducible installs [(#10593)](https://github.com/prowler-cloud/prowler/pull/10593) + ### 🔐 Security - `authlib` bumped from 1.6.5 to 1.6.9 to fix CVE-2026-28802 (JWT `alg: none` validation bypass) [(#10579)](https://github.com/prowler-cloud/prowler/pull/10579) diff --git a/mcp_server/pyproject.toml b/mcp_server/pyproject.toml index 4ea4a9859e..2a6885fedb 100644 --- a/mcp_server/pyproject.toml +++ b/mcp_server/pyproject.toml @@ -5,7 +5,7 @@ requires = ["setuptools>=61.0", "wheel"] [project] dependencies = [ "fastmcp==2.14.0", - "httpx>=0.28.0" + "httpx==0.28.1" ] description = "MCP server for Prowler ecosystem" name = "prowler-mcp" diff --git a/mcp_server/uv.lock b/mcp_server/uv.lock index ca1d9482be..bcff18e2d9 100644 --- a/mcp_server/uv.lock +++ b/mcp_server/uv.lock @@ -727,7 +727,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "fastmcp", specifier = "==2.14.0" }, - { name = "httpx", specifier = ">=0.28.0" }, + { name = "httpx", specifier = "==0.28.1" }, ] [[package]] diff --git a/poetry.lock b/poetry.lock index a76112ff76..c0ffadab6f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -808,7 +808,7 @@ description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -2379,7 +2379,7 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -3938,7 +3938,7 @@ description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.10" groups = ["dev"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, @@ -6094,7 +6094,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -6743,4 +6743,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "91739ee5e383337160f9f08b76944ab4e8629c94084c8a9d115246862557f7c5" +content-hash = "4050d3a95f5bc5448576ca0361fd899b35aa04de28d379cdfd3c2b0db67848ad" diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 8f60b4a7d7..4835fe7f75 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -27,6 +27,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Added `internet-exposed` category to 13 AWS checks (CloudFront, CodeArtifact, EC2, EFS, RDS, SageMaker, Shield, VPC) [(#10502)](https://github.com/prowler-cloud/prowler/pull/10502) - Minimum Python version from 3.9 to 3.10 and updated classifiers to reflect supported versions (3.10, 3.11, 3.12) [(#10464)](https://github.com/prowler-cloud/prowler/pull/10464) +- Pin direct SDK dependencies to exact versions and rely on `poetry.lock` artifact hashes for reproducible installs [(#10593)](https://github.com/prowler-cloud/prowler/pull/10593) - Sensitive CLI flags now warn when values are passed directly, recommending environment variables instead [(#10532)](https://github.com/prowler-cloud/prowler/pull/10532) ### 🐞 Fixed diff --git a/pyproject.toml b/pyproject.toml index a3b6f7dece..0db8391be7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,11 +49,11 @@ dependencies = [ "cryptography==46.0.6", "dash==3.1.1", "dash-bootstrap-components==2.0.3", - "defusedxml>=0.7.1", + "defusedxml==0.7.1", "detect-secrets==1.5.0", "dulwich==0.23.0", "google-api-python-client==2.163.0", - "google-auth-httplib2>=0.1,<0.3", + "google-auth-httplib2==0.2.0", "jsonschema==4.23.0", "kubernetes==32.0.1", "markdown==3.10.2", @@ -63,9 +63,9 @@ dependencies = [ "openstacksdk==4.2.0", "pandas==2.2.3", "py-ocsf-models==0.8.1", - "pydantic (>=2.0,<3.0)", + "pydantic==2.12.5", "pygithub==2.8.0", - "python-dateutil (>=2.9.0.post0,<3.0.0)", + "python-dateutil==2.9.0.post0", "pytz==2025.1", "schema==0.7.5", "shodan==1.31.0", From b898f257f1b25a482523b733eeb2ae6370d7d242 Mon Sep 17 00:00:00 2001 From: Avula Jeevan Yadav Date: Thu, 9 Apr 2026 19:26:29 +0530 Subject: [PATCH 23/65] feat(stepfunctions): add check for secrets in state machine definition (#10570) Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com> --- prowler/CHANGELOG.md | 1 + .../__init__.py | 0 ...ine_no_secrets_in_definition.metadata.json | 44 +++++ ...s_statemachine_no_secrets_in_definition.py | 45 +++++ .../__init__.py | 0 ...temachine_no_secrets_in_definition_test.py | 180 ++++++++++++++++++ 6 files changed, 270 insertions(+) create mode 100644 prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/__init__.py create mode 100644 prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition.metadata.json create mode 100644 prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition.py create mode 100644 tests/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/__init__.py create mode 100644 tests/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 4835fe7f75..eb7944ff09 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -21,6 +21,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - `entra_conditional_access_policy_device_registration_mfa_required` check and `entra_intune_enrollment_sign_in_frequency_every_time` enhancement for M365 provider [(#10222)](https://github.com/prowler-cloud/prowler/pull/10222) - `entra_conditional_access_policy_block_elevated_insider_risk` check for M365 provider [(#10234)](https://github.com/prowler-cloud/prowler/pull/10234) - `Vercel` provider support with 30 checks [(#10189)](https://github.com/prowler-cloud/prowler/pull/10189) +- `stepfunctions_statemachine_no_secrets_in_definition` check for hardcoded secrets in AWS Step Functions state machine definitions [(#10570)](https://github.com/prowler-cloud/prowler/pull/10570) - CCC improvements with the latest checks and new mappings [(#10625)](https://github.com/prowler-cloud/prowler/pull/10625) ### 🔄 Changed diff --git a/prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/__init__.py b/prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition.metadata.json b/prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition.metadata.json new file mode 100644 index 0000000000..746b53d8fd --- /dev/null +++ b/prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition.metadata.json @@ -0,0 +1,44 @@ +{ + "Provider": "aws", + "CheckID": "stepfunctions_statemachine_no_secrets_in_definition", + "CheckTitle": "Step Functions state machine has no sensitive credentials in its definition", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices", + "TTPs/Credential Access", + "Effects/Data Exposure", + "Sensitive Data Identifications/Security" + ], + "ServiceName": "stepfunctions", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "critical", + "ResourceType": "AwsStepFunctionStateMachine", + "ResourceGroup": "serverless", + "Description": "**AWS Step Functions state machines** are inspected for **hardcoded secrets** (keys, tokens, passwords) embedded directly in the state machine **definition** (Amazon States Language JSON).\n\nSuch values indicate sensitive data is stored directly in task parameters instead of being sourced securely.", + "Risk": "Plaintext secrets in state machine definitions reduce confidentiality: values can be viewed in the AWS Console, CLI, and may leak into execution logs or public outputs. Compromised credentials enable unauthorized AWS actions, lateral movement, and data exfiltration.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/step-functions/latest/dg/concepts-amazon-states-language.html", + "https://docs.aws.amazon.com/step-functions/latest/dg/security-best-practices.html", + "https://docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_how-services-use-secrets_step-functions.html", + "https://docs.aws.amazon.com/systems-manager/latest/userguide/integration-ps-secretsmanager.html" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "```yaml\nResources:\n :\n Type: AWS::StepFunctions::StateMachine\n Properties:\n StateMachineName: \n RoleArn: \n DefinitionString: |\n {\n \"Comment\": \"Example state machine\",\n \"StartAt\": \"MyTask\",\n \"States\": {\n \"MyTask\": {\n \"Type\": \"Task\",\n \"Resource\": \"arn:aws:states:::aws-sdk:secretsmanager:getSecretValue\",\n \"Parameters\": {\n \"SecretId\": \"\"\n },\n \"End\": true\n }\n }\n }\n```", + "Other": "1. In AWS Console, go to Step Functions and open your state machine\n2. Click Edit\n3. Remove any hardcoded secrets from the definition\n4. Use AWS Secrets Manager or Parameter Store to retrieve secrets at runtime\n5. Grant the state machine IAM role permission to access the secret\n6. Save the updated definition", + "Terraform": "```hcl\nresource \"aws_sfn_state_machine\" \"\" {\n name = \"\"\n role_arn = \"\"\n\n definition = jsonencode({\n Comment = \"Example state machine\"\n StartAt = \"MyTask\"\n States = {\n MyTask = {\n Type = \"Task\"\n Resource = \"arn:aws:states:::aws-sdk:secretsmanager:getSecretValue\"\n Parameters = {\n SecretId = \"\" # Reference secret by name, never hardcode value\n }\n End = true\n }\n }\n })\n}\n```" + }, + "Recommendation": { + "Text": "Store secrets outside the state machine definition and retrieve them securely at runtime using **AWS Secrets Manager** or **AWS Systems Manager Parameter Store**.\n- Use the `aws-sdk:secretsmanager:getSecretValue` integration to fetch secrets dynamically\n- Enforce **least privilege** on the state machine IAM role\n- Rotate secrets regularly and never embed them in the definition", + "Url": "https://hub.prowler.com/check/stepfunctions_statemachine_no_secrets_in_definition" + } + }, + "Categories": [ + "secrets" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition.py b/prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition.py new file mode 100644 index 0000000000..db04710029 --- /dev/null +++ b/prowler/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition.py @@ -0,0 +1,45 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.lib.utils.utils import detect_secrets_scan +from prowler.providers.aws.services.stepfunctions.stepfunctions_client import ( + stepfunctions_client, +) + + +class stepfunctions_statemachine_no_secrets_in_definition(Check): + """Check that AWS Step Functions state machine definitions contain no hardcoded secrets.""" + + def execute(self) -> list[Check_Report_AWS]: + findings = [] + secrets_ignore_patterns = stepfunctions_client.audit_config.get( + "secrets_ignore_patterns", [] + ) + for state_machine in stepfunctions_client.state_machines.values(): + report = Check_Report_AWS(metadata=self.metadata(), resource=state_machine) + report.status = "PASS" + report.status_extended = f"No secrets found in Step Functions state machine {state_machine.name} definition." + + if state_machine.definition: + detect_secrets_output = detect_secrets_scan( + data=state_machine.definition, + excluded_secrets=secrets_ignore_patterns, + detect_secrets_plugins=stepfunctions_client.audit_config.get( + "detect_secrets_plugins", + ), + ) + + if detect_secrets_output: + secrets_string = ", ".join( + [ + f"{secret['type']} on line {secret['line_number']}" + for secret in detect_secrets_output + ] + ) + report.status = "FAIL" + report.status_extended = ( + f"Potential {'secrets' if len(detect_secrets_output) > 1 else 'secret'} " + f"found in Step Functions state machine {state_machine.name} definition " + f"-> {secrets_string}." + ) + + findings.append(report) + return findings diff --git a/tests/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/__init__.py b/tests/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition_test.py b/tests/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition_test.py new file mode 100644 index 0000000000..628525e542 --- /dev/null +++ b/tests/providers/aws/services/stepfunctions/stepfunctions_statemachine_no_secrets_in_definition/stepfunctions_statemachine_no_secrets_in_definition_test.py @@ -0,0 +1,180 @@ +from datetime import datetime +from unittest import mock + +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1 + + +class Test_stepfunctions_statemachine_no_secrets_in_definition: + def test_no_statemachines(self): + stepfunctions_client = mock.MagicMock() + stepfunctions_client.state_machines = {} + stepfunctions_client.audit_config = {"secrets_ignore_patterns": []} + + with ( + mock.patch( + "prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions", + stepfunctions_client, + ), + mock.patch( + "prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client", + stepfunctions_client, + ), + ): + from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import ( + stepfunctions_statemachine_no_secrets_in_definition, + ) + + check = stepfunctions_statemachine_no_secrets_in_definition() + result = check.execute() + + assert len(result) == 0 + + def test_statemachine_with_no_definition(self): + stepfunctions_client = mock.MagicMock() + + from prowler.providers.aws.services.stepfunctions.stepfunctions_service import ( + StateMachine, + StateMachineStatus, + StateMachineType, + ) + + statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine" + stepfunctions_client.state_machines = { + statemachine_arn: StateMachine( + id="TestStateMachine", + arn=statemachine_arn, + name="TestStateMachine", + status=StateMachineStatus.ACTIVE, + definition=None, + region=AWS_REGION_US_EAST_1, + type=StateMachineType.STANDARD, + creation_date=datetime.now(), + ) + } + stepfunctions_client.audit_config = {"secrets_ignore_patterns": []} + + with ( + mock.patch( + "prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions", + stepfunctions_client, + ), + mock.patch( + "prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client", + stepfunctions_client, + ), + ): + from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import ( + stepfunctions_statemachine_no_secrets_in_definition, + ) + + check = stepfunctions_statemachine_no_secrets_in_definition() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "No secrets found in Step Functions state machine TestStateMachine definition." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == "TestStateMachine" + assert result[0].resource_arn == statemachine_arn + + def test_statemachine_with_no_secrets_in_definition(self): + stepfunctions_client = mock.MagicMock() + + from prowler.providers.aws.services.stepfunctions.stepfunctions_service import ( + StateMachine, + StateMachineStatus, + StateMachineType, + ) + + statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine" + stepfunctions_client.state_machines = { + statemachine_arn: StateMachine( + id="TestStateMachine", + arn=statemachine_arn, + name="TestStateMachine", + status=StateMachineStatus.ACTIVE, + definition='{"Comment": "A simple example", "StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Pass", "End": true}}}', + region=AWS_REGION_US_EAST_1, + type=StateMachineType.STANDARD, + creation_date=datetime.now(), + ) + } + stepfunctions_client.audit_config = {"secrets_ignore_patterns": []} + + with ( + mock.patch( + "prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions", + stepfunctions_client, + ), + mock.patch( + "prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client", + stepfunctions_client, + ), + ): + from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import ( + stepfunctions_statemachine_no_secrets_in_definition, + ) + + check = stepfunctions_statemachine_no_secrets_in_definition() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "No secrets found in Step Functions state machine TestStateMachine definition." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == "TestStateMachine" + assert result[0].resource_arn == statemachine_arn + + def test_statemachine_with_secrets_in_definition(self): + stepfunctions_client = mock.MagicMock() + + from prowler.providers.aws.services.stepfunctions.stepfunctions_service import ( + StateMachine, + StateMachineStatus, + StateMachineType, + ) + + statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine" + stepfunctions_client.state_machines = { + statemachine_arn: StateMachine( + id="TestStateMachine", + arn=statemachine_arn, + name="TestStateMachine", + status=StateMachineStatus.ACTIVE, + definition='{"Comment": "Example with secret", "StartAt": "MyTask", "States": {"MyTask": {"Type": "Task", "Parameters": {"api_key": "AKIAIOSFODNN7EXAMPLE"}, "End": true}}}', + region=AWS_REGION_US_EAST_1, + type=StateMachineType.STANDARD, + creation_date=datetime.now(), + ) + } + stepfunctions_client.audit_config = {"secrets_ignore_patterns": []} + + with ( + mock.patch( + "prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions", + stepfunctions_client, + ), + mock.patch( + "prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client", + stepfunctions_client, + ), + ): + from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import ( + stepfunctions_statemachine_no_secrets_in_definition, + ) + + check = stepfunctions_statemachine_no_secrets_in_definition() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert "TestStateMachine" in result[0].status_extended + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == "TestStateMachine" + assert result[0].resource_arn == statemachine_arn From 18cfb191f5c5d5b9edfd7305643a7cd7a0c71dd1 Mon Sep 17 00:00:00 2001 From: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:58:35 +0200 Subject: [PATCH 24/65] docs: rename Prowler App to Prowler Cloud in provider headers (#10634) --- .../providers/alibabacloud/getting-started-alibabacloud.mdx | 2 +- docs/user-guide/providers/aws/getting-started-aws.mdx | 4 ++-- docs/user-guide/providers/azure/getting-started-azure.mdx | 6 +++--- docs/user-guide/providers/gcp/getting-started-gcp.mdx | 4 ++-- docs/user-guide/providers/iac/getting-started-iac.mdx | 2 +- .../user-guide/providers/kubernetes/getting-started-k8s.mdx | 2 +- docs/user-guide/providers/oci/getting-started-oci.mdx | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/user-guide/providers/alibabacloud/getting-started-alibabacloud.mdx b/docs/user-guide/providers/alibabacloud/getting-started-alibabacloud.mdx index 0a1d54b079..de8708d867 100644 --- a/docs/user-guide/providers/alibabacloud/getting-started-alibabacloud.mdx +++ b/docs/user-guide/providers/alibabacloud/getting-started-alibabacloud.mdx @@ -37,7 +37,7 @@ Before you begin, make sure you have: ![Get Account ID](/images/providers/alibaba-account-id.png) -### Step 2: Access Prowler Cloud or Prowler App +### Step 2: Access Prowler Cloud 1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app) 2. Go to "Configuration" > "Cloud Providers" diff --git a/docs/user-guide/providers/aws/getting-started-aws.mdx b/docs/user-guide/providers/aws/getting-started-aws.mdx index 7d003d9821..2c94700b15 100644 --- a/docs/user-guide/providers/aws/getting-started-aws.mdx +++ b/docs/user-guide/providers/aws/getting-started-aws.mdx @@ -2,7 +2,7 @@ title: 'Getting Started With AWS on Prowler' --- -## Prowler App +## Prowler Cloud @@ -16,7 +16,7 @@ title: 'Getting Started With AWS on Prowler' ![Account ID detail](/images/providers/aws-account-id.png) -### Step 2: Access Prowler Cloud or Prowler App +### Step 2: Access Prowler Cloud 1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app) 2. Go to "Configuration" > "Cloud Providers" diff --git a/docs/user-guide/providers/azure/getting-started-azure.mdx b/docs/user-guide/providers/azure/getting-started-azure.mdx index a5299c614b..456c226aab 100644 --- a/docs/user-guide/providers/azure/getting-started-azure.mdx +++ b/docs/user-guide/providers/azure/getting-started-azure.mdx @@ -2,7 +2,7 @@ title: 'Getting Started With Azure on Prowler' --- -## Prowler App +## Prowler Cloud > Walkthrough video onboarding an Azure Subscription using Service Principal. @@ -32,7 +32,7 @@ For detailed instructions on how to create the Service Principal and configure p --- -### Step 2: Access Prowler App +### Step 2: Access Prowler Cloud 1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app) 2. Navigate to `Configuration` > `Cloud Providers` @@ -51,7 +51,7 @@ For detailed instructions on how to create the Service Principal and configure p ![Add Subscription ID](/images/providers/add-subscription-id.png) -### Step 3: Add Credentials to Prowler App +### Step 3: Add Credentials to Prowler Cloud For Azure, Prowler App uses a service principal application to authenticate. For more information about the process of creating and adding permissions to a service principal refer to this [section](/user-guide/providers/azure/authentication). When you finish creating and adding the [Entra](/user-guide/providers/azure/create-prowler-service-principal#assigning-proper-permissions) and [Subscription](/user-guide/providers/azure/subscriptions) scope permissions to the service principal, enter the `Tenant ID`, `Client ID` and `Client Secret` of the service principal application. diff --git a/docs/user-guide/providers/gcp/getting-started-gcp.mdx b/docs/user-guide/providers/gcp/getting-started-gcp.mdx index 1cdc587d45..c70250c8a9 100644 --- a/docs/user-guide/providers/gcp/getting-started-gcp.mdx +++ b/docs/user-guide/providers/gcp/getting-started-gcp.mdx @@ -2,7 +2,7 @@ title: 'Getting Started With GCP on Prowler' --- -## Prowler App +## Prowler Cloud ### Step 1: Get the GCP Project ID @@ -11,7 +11,7 @@ title: 'Getting Started With GCP on Prowler' ![Get the Project ID](/images/providers/project-id-console.png) -### Step 2: Access Prowler Cloud or Prowler App +### Step 2: Access Prowler Cloud 1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app) 2. Go to "Configuration" > "Cloud Providers" diff --git a/docs/user-guide/providers/iac/getting-started-iac.mdx b/docs/user-guide/providers/iac/getting-started-iac.mdx index 849571b2c8..2aba3cf551 100644 --- a/docs/user-guide/providers/iac/getting-started-iac.mdx +++ b/docs/user-guide/providers/iac/getting-started-iac.mdx @@ -31,7 +31,7 @@ Prowler IaC provider scans the following Infrastructure as Code configurations f - Mutelist logic ([filtering](https://trivy.dev/latest/docs/configuration/filtering/)) is handled by Trivy, not Prowler. - Results are output in the same formats as other Prowler providers (CSV, JSON, HTML, etc.). -## Prowler App +## Prowler Cloud diff --git a/docs/user-guide/providers/kubernetes/getting-started-k8s.mdx b/docs/user-guide/providers/kubernetes/getting-started-k8s.mdx index c4f4822792..aff63b81a3 100644 --- a/docs/user-guide/providers/kubernetes/getting-started-k8s.mdx +++ b/docs/user-guide/providers/kubernetes/getting-started-k8s.mdx @@ -2,7 +2,7 @@ title: 'Getting Started with Kubernetes' --- -## Prowler App +## Prowler Cloud ### Step 1: Access Prowler Cloud/App diff --git a/docs/user-guide/providers/oci/getting-started-oci.mdx b/docs/user-guide/providers/oci/getting-started-oci.mdx index 8affa2f992..459d9ad685 100644 --- a/docs/user-guide/providers/oci/getting-started-oci.mdx +++ b/docs/user-guide/providers/oci/getting-started-oci.mdx @@ -14,7 +14,7 @@ The following steps apply to Prowler Cloud and the self-hosted Prowler App. 3. Generate or locate the API key fingerprint and private key for that user. Follow the [Config File Authentication steps](/user-guide/providers/oci/authentication#config-file-authentication-manual-api-key-setup) to create or rotate the key pair and copy the fingerprint. 4. Note the **Region** identifier to scan (for example, `us-ashburn-1`). -### Step 2: Access Prowler Cloud or Prowler App +### Step 2: Access Prowler Cloud 1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler App](/user-guide/tutorials/prowler-app). 2. Go to **Configuration** → **Cloud Providers** and click **Add Cloud Provider**. ![Add OCI Cloud Provider](./images/oci-add-cloud-provider.png) From 4e508b69c955d3fde316cc836cda8bd903d17c9a Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:23:50 +0200 Subject: [PATCH 25/65] fix(vercel): use canonical Hub URLs in check metadata (#10636) --- ...thentication_no_stale_tokens.metadata.json | 2 +- ...entication_token_not_expired.metadata.json | 2 +- ...roduction_uses_stable_target.metadata.json | 2 +- ...main_dns_properly_configured.metadata.json | 2 +- ...domain_ssl_certificate_valid.metadata.json | 2 +- .../domain_verified.metadata.json | 2 +- ...o_expose_system_env_disabled.metadata.json | 2 +- ...eployment_protection_enabled.metadata.json | 2 +- ...t_directory_listing_disabled.metadata.json | 2 +- ...nment_no_overly_broad_target.metadata.json | 2 +- ...ent_no_secrets_in_plain_type.metadata.json | 2 +- ...oduction_vars_not_in_preview.metadata.json | 2 +- ..._git_fork_protection_enabled.metadata.json | 2 +- ..._password_protection_enabled.metadata.json | 2 +- ...eployment_protection_enabled.metadata.json | 2 +- ...ject_skew_protection_enabled.metadata.json | 2 +- ...rity_custom_rules_configured.metadata.json | 2 +- ...ip_blocking_rules_configured.metadata.json | 2 +- ...ity_managed_rulesets_enabled.metadata.json | 2 +- ...ity_rate_limiting_configured.metadata.json | 2 +- .../security_waf_enabled.metadata.json | 2 +- .../team_directory_sync_enabled.metadata.json | 2 +- ..._member_role_least_privilege.metadata.json | 2 +- .../team_no_stale_invitations.metadata.json | 2 +- .../team_saml_sso_enabled.metadata.json | 2 +- .../team_saml_sso_enforced.metadata.json | 2 +- tests/lib/check/check_test.py | 28 +++++++++++++++++++ 27 files changed, 54 insertions(+), 26 deletions(-) diff --git a/prowler/providers/vercel/services/authentication/authentication_no_stale_tokens/authentication_no_stale_tokens.metadata.json b/prowler/providers/vercel/services/authentication/authentication_no_stale_tokens/authentication_no_stale_tokens.metadata.json index d94e12f0b9..f4863ada5f 100644 --- a/prowler/providers/vercel/services/authentication/authentication_no_stale_tokens/authentication_no_stale_tokens.metadata.json +++ b/prowler/providers/vercel/services/authentication/authentication_no_stale_tokens/authentication_no_stale_tokens.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Regularly audit API tokens and revoke any that have not been used within 90 days. Implement a token lifecycle management process that includes periodic reviews, automatic expiration dates, and documentation of each token's purpose and owner.", - "Url": "https://hub.prowler.com/checks/vercel/authentication_no_stale_tokens" + "Url": "https://hub.prowler.com/check/authentication_no_stale_tokens" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/authentication/authentication_token_not_expired/authentication_token_not_expired.metadata.json b/prowler/providers/vercel/services/authentication/authentication_token_not_expired/authentication_token_not_expired.metadata.json index dca48e73bf..47e5087cf5 100644 --- a/prowler/providers/vercel/services/authentication/authentication_token_not_expired/authentication_token_not_expired.metadata.json +++ b/prowler/providers/vercel/services/authentication/authentication_token_not_expired/authentication_token_not_expired.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Remove expired tokens and create new ones with appropriate expiration dates. Implement a token rotation schedule to ensure tokens are refreshed before they expire. Update all integrations and automation that depend on the replaced tokens.", - "Url": "https://hub.prowler.com/checks/vercel/authentication_token_not_expired" + "Url": "https://hub.prowler.com/check/authentication_token_not_expired" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/deployment/deployment_production_uses_stable_target/deployment_production_uses_stable_target.metadata.json b/prowler/providers/vercel/services/deployment/deployment_production_uses_stable_target/deployment_production_uses_stable_target.metadata.json index 8a7cc70d40..25416e6882 100644 --- a/prowler/providers/vercel/services/deployment/deployment_production_uses_stable_target/deployment_production_uses_stable_target.metadata.json +++ b/prowler/providers/vercel/services/deployment/deployment_production_uses_stable_target/deployment_production_uses_stable_target.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Configure the production branch to main or master and ensure all production deployments go through the standard merge workflow. Use branch protection rules in your Git provider to prevent direct pushes to the production branch.", - "Url": "https://hub.prowler.com/checks/vercel/deployment_production_uses_stable_target" + "Url": "https://hub.prowler.com/check/deployment_production_uses_stable_target" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/domain/domain_dns_properly_configured/domain_dns_properly_configured.metadata.json b/prowler/providers/vercel/services/domain/domain_dns_properly_configured/domain_dns_properly_configured.metadata.json index f9104ee960..e2450da8f1 100644 --- a/prowler/providers/vercel/services/domain/domain_dns_properly_configured/domain_dns_properly_configured.metadata.json +++ b/prowler/providers/vercel/services/domain/domain_dns_properly_configured/domain_dns_properly_configured.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Update DNS records at your domain registrar to correctly point to Vercel. Use a CNAME record for subdomains or an A record for apex domains. Verify the configuration in the Vercel dashboard after making changes.", - "Url": "https://hub.prowler.com/checks/vercel/domain_dns_properly_configured" + "Url": "https://hub.prowler.com/check/domain_dns_properly_configured" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/domain/domain_ssl_certificate_valid/domain_ssl_certificate_valid.metadata.json b/prowler/providers/vercel/services/domain/domain_ssl_certificate_valid/domain_ssl_certificate_valid.metadata.json index f15892df61..ac683cd71f 100644 --- a/prowler/providers/vercel/services/domain/domain_ssl_certificate_valid/domain_ssl_certificate_valid.metadata.json +++ b/prowler/providers/vercel/services/domain/domain_ssl_certificate_valid/domain_ssl_certificate_valid.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Ensure domain DNS records are properly configured to point to Vercel. Once DNS is validated, Vercel automatically provisions and renews SSL/TLS certificates. Check the domain configuration in the Vercel dashboard if the certificate is not being issued.", - "Url": "https://hub.prowler.com/checks/vercel/domain_ssl_certificate_valid" + "Url": "https://hub.prowler.com/check/domain_ssl_certificate_valid" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/domain/domain_verified/domain_verified.metadata.json b/prowler/providers/vercel/services/domain/domain_verified/domain_verified.metadata.json index f520fb00d8..1e79a433ba 100644 --- a/prowler/providers/vercel/services/domain/domain_verified/domain_verified.metadata.json +++ b/prowler/providers/vercel/services/domain/domain_verified/domain_verified.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Complete domain verification by configuring the required DNS records at your domain registrar. Remove any domains that are no longer needed to reduce the attack surface. Regularly audit domain configurations to ensure all domains remain verified.", - "Url": "https://hub.prowler.com/checks/vercel/domain_verified" + "Url": "https://hub.prowler.com/check/domain_verified" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/project/project_auto_expose_system_env_disabled/project_auto_expose_system_env_disabled.metadata.json b/prowler/providers/vercel/services/project/project_auto_expose_system_env_disabled/project_auto_expose_system_env_disabled.metadata.json index bf7adc2832..21c3f118e1 100644 --- a/prowler/providers/vercel/services/project/project_auto_expose_system_env_disabled/project_auto_expose_system_env_disabled.metadata.json +++ b/prowler/providers/vercel/services/project/project_auto_expose_system_env_disabled/project_auto_expose_system_env_disabled.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Disable automatic exposure of system environment variables and explicitly define only the variables required by your application. This follows the principle of least privilege and reduces the risk of leaking internal infrastructure details through client-side code.", - "Url": "https://hub.prowler.com/checks/vercel/project_auto_expose_system_env_disabled" + "Url": "https://hub.prowler.com/check/project_auto_expose_system_env_disabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/project/project_deployment_protection_enabled/project_deployment_protection_enabled.metadata.json b/prowler/providers/vercel/services/project/project_deployment_protection_enabled/project_deployment_protection_enabled.metadata.json index c704b42784..521610c617 100644 --- a/prowler/providers/vercel/services/project/project_deployment_protection_enabled/project_deployment_protection_enabled.metadata.json +++ b/prowler/providers/vercel/services/project/project_deployment_protection_enabled/project_deployment_protection_enabled.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Enable deployment protection on preview deployments to require authentication before visitors can access preview URLs. Use 'Standard Protection' for Vercel Authentication or configure trusted IP ranges for more granular control.", - "Url": "https://hub.prowler.com/checks/vercel/project_deployment_protection_enabled" + "Url": "https://hub.prowler.com/check/project_deployment_protection_enabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/project/project_directory_listing_disabled/project_directory_listing_disabled.metadata.json b/prowler/providers/vercel/services/project/project_directory_listing_disabled/project_directory_listing_disabled.metadata.json index b9de49aa0e..b477a5984f 100644 --- a/prowler/providers/vercel/services/project/project_directory_listing_disabled/project_directory_listing_disabled.metadata.json +++ b/prowler/providers/vercel/services/project/project_directory_listing_disabled/project_directory_listing_disabled.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Disable directory listing to prevent visitors from browsing the file structure of your deployments. Ensure that all directories either contain an index file or return a 404 response when accessed directly.", - "Url": "https://hub.prowler.com/checks/vercel/project_directory_listing_disabled" + "Url": "https://hub.prowler.com/check/project_directory_listing_disabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/project/project_environment_no_overly_broad_target/project_environment_no_overly_broad_target.metadata.json b/prowler/providers/vercel/services/project/project_environment_no_overly_broad_target/project_environment_no_overly_broad_target.metadata.json index 2467c1b104..c9a418a503 100644 --- a/prowler/providers/vercel/services/project/project_environment_no_overly_broad_target/project_environment_no_overly_broad_target.metadata.json +++ b/prowler/providers/vercel/services/project/project_environment_no_overly_broad_target/project_environment_no_overly_broad_target.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Follow the **principle of least privilege** for environment variable targeting.\n- Assign each variable to only the environments where it is actually needed\n- Use different credentials for production, preview, and development environments\n- Non-sensitive configuration (e.g. feature flags, public URLs) may be acceptable in multiple environments but should still be reviewed\n- Regularly audit environment variable targets to prevent scope creep", - "Url": "https://hub.prowler.com/checks/vercel/project_environment_no_overly_broad_target" + "Url": "https://hub.prowler.com/check/project_environment_no_overly_broad_target" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/project/project_environment_no_secrets_in_plain_type/project_environment_no_secrets_in_plain_type.metadata.json b/prowler/providers/vercel/services/project/project_environment_no_secrets_in_plain_type/project_environment_no_secrets_in_plain_type.metadata.json index f8d5621914..90fea53eea 100644 --- a/prowler/providers/vercel/services/project/project_environment_no_secrets_in_plain_type/project_environment_no_secrets_in_plain_type.metadata.json +++ b/prowler/providers/vercel/services/project/project_environment_no_secrets_in_plain_type/project_environment_no_secrets_in_plain_type.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Use the **Sensitive** type for all environment variables that contain secrets, keys, tokens, or passwords.\n- Sensitive variables are never exposed in the dashboard or API responses after creation\n- Rotate all credentials that were previously stored as plain text\n- Implement naming conventions that make it easy to identify secret variables", - "Url": "https://hub.prowler.com/checks/vercel/project_environment_no_secrets_in_plain_type" + "Url": "https://hub.prowler.com/check/project_environment_no_secrets_in_plain_type" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/project/project_environment_production_vars_not_in_preview/project_environment_production_vars_not_in_preview.metadata.json b/prowler/providers/vercel/services/project/project_environment_production_vars_not_in_preview/project_environment_production_vars_not_in_preview.metadata.json index 55f468fa8a..6fc3e3af79 100644 --- a/prowler/providers/vercel/services/project/project_environment_production_vars_not_in_preview/project_environment_production_vars_not_in_preview.metadata.json +++ b/prowler/providers/vercel/services/project/project_environment_production_vars_not_in_preview/project_environment_production_vars_not_in_preview.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Maintain strict **environment separation** between production and preview deployments.\n- Use dedicated, limited-scope credentials for preview environments\n- Never share production database credentials, API keys, or signing keys with preview builds\n- Enable Vercel's deployment protection features to further restrict access to preview deployments\n- Regularly audit which environment variables target multiple environments", - "Url": "https://hub.prowler.com/checks/vercel/project_environment_production_vars_not_in_preview" + "Url": "https://hub.prowler.com/check/project_environment_production_vars_not_in_preview" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/project/project_git_fork_protection_enabled/project_git_fork_protection_enabled.metadata.json b/prowler/providers/vercel/services/project/project_git_fork_protection_enabled/project_git_fork_protection_enabled.metadata.json index 744a227d61..8e3db04fcd 100644 --- a/prowler/providers/vercel/services/project/project_git_fork_protection_enabled/project_git_fork_protection_enabled.metadata.json +++ b/prowler/providers/vercel/services/project/project_git_fork_protection_enabled/project_git_fork_protection_enabled.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Enable Git fork protection to require explicit authorization before pull requests from forked repositories can trigger deployments. This prevents untrusted contributors from accessing environment variables and secrets through the build process. For open-source projects, review fork PRs manually before allowing builds.", - "Url": "https://hub.prowler.com/checks/vercel/project_git_fork_protection_enabled" + "Url": "https://hub.prowler.com/check/project_git_fork_protection_enabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/project/project_password_protection_enabled/project_password_protection_enabled.metadata.json b/prowler/providers/vercel/services/project/project_password_protection_enabled/project_password_protection_enabled.metadata.json index 58e3e46e41..2db77410d9 100644 --- a/prowler/providers/vercel/services/project/project_password_protection_enabled/project_password_protection_enabled.metadata.json +++ b/prowler/providers/vercel/services/project/project_password_protection_enabled/project_password_protection_enabled.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Enable password protection to add a shared-password gate to your deployments. This is especially recommended for preview deployments shared with external clients or stakeholders who do not have Vercel accounts. Combine with Vercel Authentication for defense-in-depth.", - "Url": "https://hub.prowler.com/checks/vercel/project_password_protection_enabled" + "Url": "https://hub.prowler.com/check/project_password_protection_enabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/project/project_production_deployment_protection_enabled/project_production_deployment_protection_enabled.metadata.json b/prowler/providers/vercel/services/project/project_production_deployment_protection_enabled/project_production_deployment_protection_enabled.metadata.json index 213bc51d07..3760eefb57 100644 --- a/prowler/providers/vercel/services/project/project_production_deployment_protection_enabled/project_production_deployment_protection_enabled.metadata.json +++ b/prowler/providers/vercel/services/project/project_production_deployment_protection_enabled/project_production_deployment_protection_enabled.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Enable deployment protection on production deployments for applications that should not be publicly accessible. This is critical for internal tools, admin dashboards, and pre-launch applications where unauthorized access could lead to data exposure or system compromise.", - "Url": "https://hub.prowler.com/checks/vercel/project_production_deployment_protection_enabled" + "Url": "https://hub.prowler.com/check/project_production_deployment_protection_enabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/project/project_skew_protection_enabled/project_skew_protection_enabled.metadata.json b/prowler/providers/vercel/services/project/project_skew_protection_enabled/project_skew_protection_enabled.metadata.json index b73aaa6991..a0a4f70cee 100644 --- a/prowler/providers/vercel/services/project/project_skew_protection_enabled/project_skew_protection_enabled.metadata.json +++ b/prowler/providers/vercel/services/project/project_skew_protection_enabled/project_skew_protection_enabled.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Enable skew protection to ensure that all client requests during a deployment rollout are routed to the same deployment version that served the initial page. This prevents version mismatch errors and ensures a consistent user experience during deployments.", - "Url": "https://hub.prowler.com/checks/vercel/project_skew_protection_enabled" + "Url": "https://hub.prowler.com/check/project_skew_protection_enabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/security/security_custom_rules_configured/security_custom_rules_configured.metadata.json b/prowler/providers/vercel/services/security/security_custom_rules_configured/security_custom_rules_configured.metadata.json index c3f986b173..a4b53baf4f 100644 --- a/prowler/providers/vercel/services/security/security_custom_rules_configured/security_custom_rules_configured.metadata.json +++ b/prowler/providers/vercel/services/security/security_custom_rules_configured/security_custom_rules_configured.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Configure custom firewall rules to protect application-specific endpoints and enforce security policies. Focus on protecting admin panels, API routes, authentication endpoints, and any paths that handle sensitive data.", - "Url": "https://hub.prowler.com/checks/vercel/security_custom_rules_configured" + "Url": "https://hub.prowler.com/check/security_custom_rules_configured" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/security/security_ip_blocking_rules_configured/security_ip_blocking_rules_configured.metadata.json b/prowler/providers/vercel/services/security/security_ip_blocking_rules_configured/security_ip_blocking_rules_configured.metadata.json index cc7712d4ec..a02cd35324 100644 --- a/prowler/providers/vercel/services/security/security_ip_blocking_rules_configured/security_ip_blocking_rules_configured.metadata.json +++ b/prowler/providers/vercel/services/security/security_ip_blocking_rules_configured/security_ip_blocking_rules_configured.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Configure IP blocking rules to deny traffic from known malicious sources. Maintain a blocklist of IPs identified through security monitoring, threat intelligence feeds, or incident investigation. Regularly review and update the blocklist.", - "Url": "https://hub.prowler.com/checks/vercel/security_ip_blocking_rules_configured" + "Url": "https://hub.prowler.com/check/security_ip_blocking_rules_configured" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/security/security_managed_rulesets_enabled/security_managed_rulesets_enabled.metadata.json b/prowler/providers/vercel/services/security/security_managed_rulesets_enabled/security_managed_rulesets_enabled.metadata.json index 95d7db6d20..ee66a3be21 100644 --- a/prowler/providers/vercel/services/security/security_managed_rulesets_enabled/security_managed_rulesets_enabled.metadata.json +++ b/prowler/providers/vercel/services/security/security_managed_rulesets_enabled/security_managed_rulesets_enabled.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Enable managed WAF rulesets to benefit from Vercel-curated protection against common attack patterns. If you are on a plan that does not support managed rulesets, consider upgrading to the Enterprise plan for enhanced security features.", - "Url": "https://hub.prowler.com/checks/vercel/security_managed_rulesets_enabled" + "Url": "https://hub.prowler.com/check/security_managed_rulesets_enabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/security/security_rate_limiting_configured/security_rate_limiting_configured.metadata.json b/prowler/providers/vercel/services/security/security_rate_limiting_configured/security_rate_limiting_configured.metadata.json index 28a9c44d5f..8a804233a5 100644 --- a/prowler/providers/vercel/services/security/security_rate_limiting_configured/security_rate_limiting_configured.metadata.json +++ b/prowler/providers/vercel/services/security/security_rate_limiting_configured/security_rate_limiting_configured.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Configure rate limiting rules to protect critical endpoints such as authentication, API routes, and form submissions. Start with conservative thresholds and adjust based on traffic patterns to avoid blocking legitimate users.", - "Url": "https://hub.prowler.com/checks/vercel/security_rate_limiting_configured" + "Url": "https://hub.prowler.com/check/security_rate_limiting_configured" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/security/security_waf_enabled/security_waf_enabled.metadata.json b/prowler/providers/vercel/services/security/security_waf_enabled/security_waf_enabled.metadata.json index c758c82f18..7598e7cccd 100644 --- a/prowler/providers/vercel/services/security/security_waf_enabled/security_waf_enabled.metadata.json +++ b/prowler/providers/vercel/services/security/security_waf_enabled/security_waf_enabled.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Enable the Vercel Web Application Firewall to protect your application against common web attacks. Start with managed rulesets for baseline protection and add custom rules as needed based on your application's threat model.", - "Url": "https://hub.prowler.com/checks/vercel/security_waf_enabled" + "Url": "https://hub.prowler.com/check/security_waf_enabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/team/team_directory_sync_enabled/team_directory_sync_enabled.metadata.json b/prowler/providers/vercel/services/team/team_directory_sync_enabled/team_directory_sync_enabled.metadata.json index 37019b79da..fda5c7b94d 100644 --- a/prowler/providers/vercel/services/team/team_directory_sync_enabled/team_directory_sync_enabled.metadata.json +++ b/prowler/providers/vercel/services/team/team_directory_sync_enabled/team_directory_sync_enabled.metadata.json @@ -25,7 +25,7 @@ }, "Recommendation": { "Text": "Enable directory sync (SCIM) to automate user lifecycle management. This ensures that team membership stays synchronized with your identity provider, automatically provisioning new members and revoking access when employees leave or change roles.", - "Url": "https://hub.prowler.com/checks/vercel/team_directory_sync_enabled" + "Url": "https://hub.prowler.com/check/team_directory_sync_enabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/team/team_member_role_least_privilege/team_member_role_least_privilege.metadata.json b/prowler/providers/vercel/services/team/team_member_role_least_privilege/team_member_role_least_privilege.metadata.json index 0e4750d703..37abf769ef 100644 --- a/prowler/providers/vercel/services/team/team_member_role_least_privilege/team_member_role_least_privilege.metadata.json +++ b/prowler/providers/vercel/services/team/team_member_role_least_privilege/team_member_role_least_privilege.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Limit the number of team owners to the minimum required for administration. Assign the least privileged role necessary for each member's responsibilities. Use MEMBER, DEVELOPER, or VIEWER roles for non-administrative team members.", - "Url": "https://hub.prowler.com/checks/vercel/team_member_role_least_privilege" + "Url": "https://hub.prowler.com/check/team_member_role_least_privilege" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/team/team_no_stale_invitations/team_no_stale_invitations.metadata.json b/prowler/providers/vercel/services/team/team_no_stale_invitations/team_no_stale_invitations.metadata.json index d05461c5c1..16a1d942e1 100644 --- a/prowler/providers/vercel/services/team/team_no_stale_invitations/team_no_stale_invitations.metadata.json +++ b/prowler/providers/vercel/services/team/team_no_stale_invitations/team_no_stale_invitations.metadata.json @@ -24,7 +24,7 @@ }, "Recommendation": { "Text": "Regularly review and revoke stale team invitations. Establish a process to follow up on pending invitations within a reasonable timeframe and revoke those that are no longer needed to reduce the risk of unauthorized access.", - "Url": "https://hub.prowler.com/checks/vercel/team_no_stale_invitations" + "Url": "https://hub.prowler.com/check/team_no_stale_invitations" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/team/team_saml_sso_enabled/team_saml_sso_enabled.metadata.json b/prowler/providers/vercel/services/team/team_saml_sso_enabled/team_saml_sso_enabled.metadata.json index ebbe85ebc9..21785bf482 100644 --- a/prowler/providers/vercel/services/team/team_saml_sso_enabled/team_saml_sso_enabled.metadata.json +++ b/prowler/providers/vercel/services/team/team_saml_sso_enabled/team_saml_sso_enabled.metadata.json @@ -25,7 +25,7 @@ }, "Recommendation": { "Text": "Enable SAML SSO for the Vercel team to centralize authentication through your organization's identity provider. This ensures consistent security policies, simplifies user lifecycle management, and enables enforcement of MFA and other access controls.", - "Url": "https://hub.prowler.com/checks/vercel/team_saml_sso_enabled" + "Url": "https://hub.prowler.com/check/team_saml_sso_enabled" } }, "Categories": [ diff --git a/prowler/providers/vercel/services/team/team_saml_sso_enforced/team_saml_sso_enforced.metadata.json b/prowler/providers/vercel/services/team/team_saml_sso_enforced/team_saml_sso_enforced.metadata.json index f4de8e7ebe..feb43bc179 100644 --- a/prowler/providers/vercel/services/team/team_saml_sso_enforced/team_saml_sso_enforced.metadata.json +++ b/prowler/providers/vercel/services/team/team_saml_sso_enforced/team_saml_sso_enforced.metadata.json @@ -25,7 +25,7 @@ }, "Recommendation": { "Text": "Enforce SAML SSO for all team members to ensure authentication is managed exclusively through your identity provider. This prevents credential bypass, enforces MFA policies, and provides centralized access control and audit capabilities.", - "Url": "https://hub.prowler.com/checks/vercel/team_saml_sso_enforced" + "Url": "https://hub.prowler.com/check/team_saml_sso_enforced" } }, "Categories": [ diff --git a/tests/lib/check/check_test.py b/tests/lib/check/check_test.py index 84708b96b1..cda33e8fc6 100644 --- a/tests/lib/check/check_test.py +++ b/tests/lib/check/check_test.py @@ -1048,6 +1048,34 @@ class TestCheck: ) self.verify_metadata_check_id(base_directory) + def test_vercel_checks_metadata_is_valid(self): + base_directory = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../", + "prowler/providers/vercel/services", + ) + ) + self.verify_metadata_check_id(base_directory) + + def test_vercel_checks_metadata_use_canonical_hub_urls(self): + base_directory = pathlib.Path(__file__).resolve().parents[3] / "prowler" + provider_path = base_directory / "providers" / "vercel" / "services" + + invalid_urls = [] + + for metadata_file_path in provider_path.rglob("*.metadata.json"): + with metadata_file_path.open("r") as metadata_file: + data = json.load(metadata_file) + + recommendation = data.get("Remediation", {}).get("Recommendation", {}) + url = recommendation.get("Url", "") + + if url.startswith("https://hub.prowler.com/checks/vercel/"): + invalid_urls.append(f"{metadata_file_path}: {url}") + + assert not invalid_urls, "\n".join(invalid_urls) + def verify_metadata_check_id(self, provider_path): errors = [] # Walk through the base directory to find all service directories From 63174caf985c932f21a08b71f5abccd4e53f9055 Mon Sep 17 00:00:00 2001 From: "Pablo Fernandez Guerra (PFE)" <148432447+pfe-nazaries@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:51:54 +0200 Subject: [PATCH 26/65] docs: add multi-tenant (organizations) management guide (#10638) Co-authored-by: Pablo F.G Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: David --- docs/docs.json | 1 + .../create-organization-button.png | Bin 0 -> 37821 bytes .../create-organization-modal.png | Bin 0 -> 13842 bytes .../delete-active-organization-modal.png | Bin 0 -> 37118 bytes .../delete-organization-modal.png | Bin 0 -> 23433 bytes .../multi-tenant/edit-organization-modal.png | Bin 0 -> 13031 bytes .../multi-tenant/organizations-card.png | Bin 0 -> 102728 bytes .../multi-tenant/sign-in-invitation.png | Bin 0 -> 72756 bytes .../switch-organization-modal.png | Bin 0 -> 14714 bytes .../tutorials/prowler-app-multi-tenant.mdx | 151 ++++++++++++++++++ 10 files changed, 152 insertions(+) create mode 100644 docs/images/prowler-app/multi-tenant/create-organization-button.png create mode 100644 docs/images/prowler-app/multi-tenant/create-organization-modal.png create mode 100644 docs/images/prowler-app/multi-tenant/delete-active-organization-modal.png create mode 100644 docs/images/prowler-app/multi-tenant/delete-organization-modal.png create mode 100644 docs/images/prowler-app/multi-tenant/edit-organization-modal.png create mode 100644 docs/images/prowler-app/multi-tenant/organizations-card.png create mode 100644 docs/images/prowler-app/multi-tenant/sign-in-invitation.png create mode 100644 docs/images/prowler-app/multi-tenant/switch-organization-modal.png create mode 100644 docs/user-guide/tutorials/prowler-app-multi-tenant.mdx diff --git a/docs/docs.json b/docs/docs.json index 2e39c4beac..c76b7174ef 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -98,6 +98,7 @@ ] }, "user-guide/tutorials/prowler-app-rbac", + "user-guide/tutorials/prowler-app-multi-tenant", "user-guide/tutorials/prowler-app-api-keys", "user-guide/tutorials/prowler-app-import-findings", { diff --git a/docs/images/prowler-app/multi-tenant/create-organization-button.png b/docs/images/prowler-app/multi-tenant/create-organization-button.png new file mode 100644 index 0000000000000000000000000000000000000000..70675c334ff3b121597b4f7400e08375d7dda7c9 GIT binary patch literal 37821 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 Rl1&9VfUTDJ(`fvj{ueBa6qEn} literal 0 HcmV?d00001 diff --git a/docs/images/prowler-app/multi-tenant/create-organization-modal.png b/docs/images/prowler-app/multi-tenant/create-organization-modal.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f932035cd121b8790800018cb54ae90853016f GIT binary patch literal 13842 zcmeHtcU)83vhbz}3W5mIAs{GSMCmOQX#yhBYeYb50O@ET2uc&A2?!`CprDk1G(mb5 z5$U}Klny4;03rD{o_EiE@7#NSzx#c^@4ol`IFn5FWbL(P&#b-H%&b|9I7*xa&Ro~f z)&NLI06+`;07Nv9s2<>a7XbA103iSXr~q;jHh==8z$$zhd;Q%FAau&pZSEPTdO)B$>>~A#C0p0^t4Ar!?!K2_`-Hsc>-F>`nX{vIYnOksEu7FaU1Wp62z(rd-Z%>t* zH+7GB{_XmU|L;G$!@o2G21Ji}S*}=IqpIp`XEY1(3m#z0IRI6_0dNNl0B*nx&;_ml5`Z}PvG|iV z62IE00Um%S5D0hy&VVD}1o(ihRlpKYzzyWa4QPWL?SM;w2uNQ6E`um_+}B`R5dPkW ze=qaM2H)on0FcH1z3fgJ091i}$};}4&YPeN=3 zARr$Kl3(yQ#w4U<(@EZ| zw=n1Kc=erP;*&sMz#gmiOS6AXvC#h~&3;$x?|MxDY5?gkAR{FuqaY(Aqc}kU!U?Jq z$3S(8>KB~)51{!4w8udAI}kx8Bp?rRa&k)WdxrWX^_l-`AWne>gpW85(2$XUgNckD zfC6|qhs_4BT8i1j%X8_uiWYxZbMm^M#&usVS5*PI;0yvp@c_DO1|F z#?tSaeFc}fp-9GM&wJ$?uS}KHvg-@Lh7i0iE(m=Z*6vymfNW#RWJ~55p6#uWXsGGu zOQ;m6jWWKDn0FqxBV}`t- z$^Be7hu}or$m?&HJ=d6(6tf#tf=?3K)0tb5525r5{&QK(&Pr@pXvC%Uwe{>lNr|$m z0ehXWbB#mEul=5MH(B0cU+`2xoyXlf6kaWR%+Ir>$y-!{AxeRNP> zTB2Hc@7&#_1$h;D6$P@-owutu>(2G#I-0roFqF~0`V_eOw(TiLjhE}iMcmJ}lr)Pv0DprZrxu<&3WoHQD~UKdk;9$OT;BGudBBW02#9iA->#ElWHg0*BJp(r|M60;R>xSss5lkN=sI^OB1V zG)Vg%E`%;7Gj0Cr9E3K&@Wx27eObRo-Fwkz<1brv=@W+~wOJ#*m=~xMHRNJasIx8^ zz+MvpGUy$dT03W~zaFjtWflNywE@bjquI501lGIjz8gd_ZD0a##jW8lA8nTO$e_++ znOjclXdgX)g{5I*dYrG3DU+oh2P>)04AR&zs*I21jl2DL9b)U_Iq`hGnI_RVbg{GC zbex$8lps5VIk{Fm4Sfw;-@#Bh}@V@AIaC7U&vGRJX=ED zz%^ka+GofRfkBwnj@`bAl}aAS<&P<>car`25@g7{N_K#-cxCSBEjG1(hYeKpZ${Fj z&OySf0+{A>elm?|wXbSu>lV>H55Lh&RHiEaf|7r&mKZ?kh)4 zQ1948QzNi-0D1u^vG1Q-VBu|H^zb{(%x>t>^KIDJR-4V00X*eA9f?b-bh_wt6@SuO z8w(ej9*NVQB%3eo?@Wf_WlpDz?XRkHJ5FZu;Q}!Xv+r=o9P@zjuJ#7{j8>`C`^B$w zt)qN6EQ)&@Iyy98@pn;ulr z@L0qIZfROseV6r_%J~dKwwFKNOula7i2qb~6-}|&s2@?!gEN?mi?Beo#PT+FeCqe{Vh`=M~c}@n^ zK_zb2KAWYg@i&{6&Q%8HD`k_imZ&r(r`>0~JXNGJohVIAM=OdNp^uZsWa(ilh(HTF zr)6U$nfCB`?4UUHCp-7gObg$%+1*M$a@kUO*Y0%kAlZ)}H`U-A$7KZ?a5QM}r(BtA zcoJ8fUK9ET*6lx3d}1oer55qLzOizuU9NO0c4~E-%mNk;;Uw@PxR0{kf(3E@%@+D$ z+fz)Ojyi)VrLK3Q-8oTG zA74)8k{a=mk3HWUm=n6YIWD|Cjh#rDUhHE3af){G1=dRQu}3fm!t*GhRvdG8ucJmk z1ff66>9V^&VCBqR-cV6@iEu}UO}oZyZ~bJ|Ju(&`43=rUE!I3UuHTNNMu^^<4cN9O zYqQ}>|GD02J9qk$s0NGs?%GziXvI)vi1^xNU6onVBnY*(-=^hkj)_S`&7RM{LdQ#UR9O4T32}=^^DD5r{_86+>s=$w6A#^_iEw0xU?}V+$_6 zCK>2Men^)-@g4f?#IBIW&sxtw859%NG#bxZ82ou!0;882xm{Z5f@Iq8fUjF@(hHk) zxs?`8H@4OHWGeKY@(e_Mvk>^W7F>@oz?q{(DQ7QbVB}~0+o2KhPT|mXD5K3}z*DN7 z)84mY+|5#CQdD*WN)+7aJ66lve(=LVmr8gViQCe4_>pDt9w066uCrwATQ&#pcuE<((=NZ4{jfgO8a@y6nf zvfA(kohS0tfyQ}JWG5atv-;(Jq?(9O7+Kh1v=RyKUG^seR0!Tf6PK*Y!3L&sb|OHj zb9y=l{$^q-Wn?jPqjlm5QwjGKM#tIGMv|*fujAveb{$GABhWj@$2RN(JC+QE69Exc zpFZ^$ZSXp;+hqn?TucPo`lvkOKZK~v7a>H|nW-Tj!P82YFdZ?33jxxGn92&r;ugv< zAJ+#_g&TG?M%8iOws;ff?~TaU}NaMkrs*j-;bX+qMoXWb-s{v^t@FJ1N1~Rf2A^|Twz|KIO5%?V~ zpkr)#qsVeX7ylS?@$EnrYrFSy=G6fON6DAk61!3b5$|qHVqI2NRz|Ex;=yKLRig!7 zk^VOz9dY2F_pa~|f`_xdVHW>gmT7=zAD^GQpMGBX zLgSqr$t8WnL0#Mv#0-O3$84viI-C9VJ}cziv6mWy_Tw-1Nd+($pQ^)TQ~fwdThJD3 z705^%iIMV0y%n)G4+Vp8(3=|toe=Uq&2n4272jddT0SzM_bniqEk&n1+-|wY{KD6p zGFS7IX%G`hIY<720k&maaqTMgkg1)m3;copG9Z*si|9`KGG-)DClq?#Yf9EUJC( z`wxXoe|oRo_p3k`DM|!@3pNF zFhcJY5oj)dps1Kr-It|OUS;VlSzr_`s$#+;{5D0jeqcAjlk@8v%#!vN>1BgWNyfSy zYW4<9!b;XC5)F^Cv$3*ZtQT^trYYT-L7+cZ60Uh$Phx9+EDg&vyO#H>!}i& zN~tqoSZY2>pZS`EJgmQ7AJqVLWA6lAM#QcUPC6IDdksczj;l1%f2Nm@3)QwU=j)iZ zr(eJ2T`o=*rbV@(beYgt&z@Y4q9gR&uZnR)^Igm-3i$BzM(uOj7>rqtYVJ;RdKiI? zY?qq|ENf4&8wy1Yci)BuHxFB`DWah=4{85l9_I5rOuC z*%bk5NB|g)DF?Sgwq2n6L10jacZtI}(T#=?Vl|0?SP_f}#F{n@-egk35`plLqe$q| zAae6yhzQ_@AedfADdiDc906w0Km^!OVfn8_g64_9X_CV;$i93c@M8zeIbcCs@?S)O zQ5qWKu?kD7rl7oHnM_F)k(WD*I&_9Tt z^AE!O)r-Hs|G#<>oE3kK#s3vE+GrPC+9Pkn&R(xEjf6VZSvC!OpMe@L*PqH2-u4QWuvH0ER|m4ZO^dh+27jUdhY z$_P%F83LXdbZO6~e|z1v#d8_Eua$1k<|CLdRHB>z%$$^k;bVf*y-fY<`$~2CZAgY} zY?#f|N-#s;{2;1v<^kP=iDx58otkyIWr=*dW`&VWsJy5nx+Q6IhjIxyLzE71EI_LV{MQ)T1i%6Qq^E+;TqEl#34 z4fNL!3ezt}DAvC$(I&N;nz`W!UzxV#6Q@G+JDao1uvgEYt`}O7+LODM&J~;IG(3JI zhqh3(=$41jgU?g~TdWTLLMN2feT``*l=&q+z?4>0VNR+|#fUFEyS%kkhw#LTl=qam zo{;=yz6k~eeXiZnFSKe`N`cLdkd+y<2? z(yBhMb-?<4nYaE_F|Y=gJUSZ$y-}Jj|J

)LCMC-yur$d$?Az_4D%^5Pq7g0}&&^ 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 literal 0 HcmV?d00001 diff --git a/docs/images/prowler-app/multi-tenant/delete-organization-modal.png b/docs/images/prowler-app/multi-tenant/delete-organization-modal.png new file mode 100644 index 0000000000000000000000000000000000000000..dd06f937e9e41bcc92dac4b88085598995547a38 GIT binary patch literal 23433 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 literal 0 HcmV?d00001 diff --git a/docs/images/prowler-app/multi-tenant/edit-organization-modal.png b/docs/images/prowler-app/multi-tenant/edit-organization-modal.png new file mode 100644 index 0000000000000000000000000000000000000000..e0d28c727d870d77d410db4d887db63055483815 GIT binary patch literal 13031 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 VXm4x$aXA04w3Gj&E+xYB{{a04O0NI_ literal 0 HcmV?d00001 diff --git a/docs/images/prowler-app/multi-tenant/organizations-card.png b/docs/images/prowler-app/multi-tenant/organizations-card.png new file mode 100644 index 0000000000000000000000000000000000000000..10ddb4b15bf58cf17d9e6d98a6394986093b9def GIT binary patch literal 102728 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

  • =qe|Se4EuUl2t#hf19D_ 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 literal 0 HcmV?d00001 diff --git a/docs/images/prowler-app/multi-tenant/switch-organization-modal.png b/docs/images/prowler-app/multi-tenant/switch-organization-modal.png new file mode 100644 index 0000000000000000000000000000000000000000..b9e2607a75b281ad51e31446c21363b55d343b20 GIT binary patch literal 14714 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 K>!XUD|Gxl7+B+Wr literal 0 HcmV?d00001 diff --git a/docs/user-guide/tutorials/prowler-app-multi-tenant.mdx b/docs/user-guide/tutorials/prowler-app-multi-tenant.mdx new file mode 100644 index 0000000000..69577c2f0c --- /dev/null +++ b/docs/user-guide/tutorials/prowler-app-multi-tenant.mdx @@ -0,0 +1,151 @@ +--- +title: 'Managing Organizations (Multi-Tenant)' +--- + +import { VersionBadge } from "/snippets/version-badge.mdx" + + + +Prowler App supports multi-tenancy through **Organizations**, allowing users to belong to multiple isolated environments within a single account. Each organization maintains its own providers, scans, findings, and user memberships, ensuring complete data separation between teams or business units. + +## Key Concepts + +* **Organization (Tenant):** An isolated workspace containing its own providers, scans, findings, roles, and users. Every Prowler account operates within at least one organization. +* **Membership:** The association between a user and an organization, including the membership role (`owner` or `member`). +* **Active Organization:** The organization currently in use for the session. All actions (scans, findings, provider management) apply to the active organization. + + +When a new account is created without an invitation, a default organization is automatically provisioned. Accounts created through an invitation join the inviter's organization instead. + + + +## Viewing Organizations + +To view all organizations associated with an account, navigate to the **Profile** page. The **Organizations** card displays every organization the user belongs to, including the role, name, join date, and whether it is the currently active organization. + +Organizations card in profile page + +## Creating an Organization + +To create a new organization: + +1. Navigate to the **Profile** page. + +2. In the **Organizations** card, click the **Create organization** button. + + Create organization button + +3. Enter a name for the new organization (maximum 100 characters). + + Create organization modal + +4. Click **Create**. The session automatically switches to the newly created organization. + + +Creating an organization requires being authenticated. Any user can create a new organization regardless of their current role. + + + +## Switching Between Organizations + +To switch the active organization: + +1. Navigate to the **Profile** page. + +2. In the **Organizations** card, locate the organization to switch to. + +3. Click the **Switch** button next to the desired organization. + +4. Confirm the switch in the dialog. The page reloads with the new organization's context, and all subsequent actions apply to it. + + Switch organization confirmation modal + + +The currently active organization is indicated by an **Active** badge. Switching updates the session tokens, so the page will reload automatically. + + + +## Editing an Organization Name + +Organization owners with the **Manage Account** permission can rename an organization: + +1. Navigate to the **Profile** page. + +2. In the **Organizations** card, click the **Edit** button next to the organization. + +3. Update the name and save the changes. + + Edit organization name modal + +## Deleting an Organization + +Organization owners with the **Manage Account** permission can delete an organization, provided they belong to at least two organizations (the last remaining organization cannot be deleted). + +### Deleting a Non-Active Organization + +1. Navigate to the **Profile** page. + +2. Click the **Delete** button next to the organization to remove. + +3. Type the organization name to confirm deletion. + + Delete organization confirmation modal + +4. Click **Delete**. The organization and all its associated data (providers, scans, findings) are permanently removed. + +### Deleting the Active Organization + +When deleting the currently active organization, an additional step is required: + +1. Navigate to the **Profile** page. + +2. Click the **Delete** button next to the active organization. + +3. Select which organization to switch to after deletion. + +4. Type the organization name to confirm. + + Delete active organization modal with target selection + +5. Click **Delete**. The session switches to the selected organization, and the deleted organization's data is permanently removed. + + +Deleting an organization is irreversible. All providers, scans, findings, and configuration data within the organization are permanently deleted. Users who belong only to the deleted organization will lose access to Prowler. + + +## Accepting an Invitation to an Organization + +When invited to join an organization, the invited user receives a link to accept the invitation. The flow adapts depending on whether the user already has a Prowler account: + +### Existing Users + +1. Open the invitation link. + +2. If already authenticated, the invitation is accepted automatically and the user is redirected to Prowler App. + +3. If not authenticated, choose **I have an account -- Sign in**, authenticate with existing credentials, and the invitation is accepted upon sign-in. + + Sign in screen after choosing I have an account from invitation + +### New Users + +1. Open the invitation link. + +2. Choose **I'm new -- Create an account**. + +3. Complete the sign-up process. Upon account creation, the invitation is accepted and the user joins the inviter's organization. + + +Invitations expire after 7 days. If an invitation has expired, contact the organization administrator to send a new one. For more details on invitation management, see [Managing Users and Role-Based Access Control (RBAC)](/user-guide/tutorials/prowler-app-rbac#invitations). + + + +## Permissions Reference + +| Action | Required Conditions | +|--------|-------------------| +| View organizations | Any authenticated user | +| Create an organization | Any authenticated user | +| Switch organizations | Any authenticated user | +| Edit organization name | Organization owner with **Manage Account** permission | +| Delete an organization | Organization owner with **Manage Account** permission; must belong to more than one organization | From e4b2950436a6b3e46018e1933ff1a9ddd408d2c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Pe=C3=B1a?= Date: Thu, 9 Apr 2026 18:07:43 +0200 Subject: [PATCH 27/65] refactor(api): split finding-groups status from muted state (#10630) --- api/CHANGELOG.md | 3 + api/src/backend/api/filters.py | 5 +- .../0088_finding_group_status_muted_fields.py | 95 ++++++ ...089_backfill_finding_group_status_muted.py | 31 ++ api/src/backend/api/models.py | 34 +- api/src/backend/api/tests/test_views.py | 316 +++++++++++++++++- api/src/backend/api/v1/serializers.py | 19 ++ api/src/backend/api/v1/views.py | 223 +++++++++--- api/src/backend/tasks/jobs/scan.py | 86 ++++- api/src/backend/tasks/tasks.py | 49 ++- api/src/backend/tasks/tests/test_tasks.py | 85 ++++- 11 files changed, 853 insertions(+), 93 deletions(-) create mode 100644 api/src/backend/api/migrations/0088_finding_group_status_muted_fields.py create mode 100644 api/src/backend/api/migrations/0089_backfill_finding_group_status_muted.py diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 33c573924a..0fbb745d3c 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -11,15 +11,18 @@ All notable changes to the **Prowler API** are documented in this file. - `VALKEY_SCHEME`, `VALKEY_USERNAME`, and `VALKEY_PASSWORD` environment variables to configure Celery broker TLS/auth connection details for Valkey/ElastiCache [(#10420)](https://github.com/prowler-cloud/prowler/pull/10420) - `Vercel` provider support [(#10190)](https://github.com/prowler-cloud/prowler/pull/10190) - Finding groups list and latest endpoints support `sort=delta`, ordering by `new_count` then `changed_count` so groups with the most new findings rank highest [(#10606)](https://github.com/prowler-cloud/prowler/pull/10606) +- Finding group resources endpoints (`/finding-groups/{check_id}/resources` and `/finding-groups/latest/{check_id}/resources`) now expose `finding_id` per row, pointing to the most recent matching Finding for each resource. UUIDv7 ordering guarantees `Max(finding__id)` resolves to the latest snapshot [(#10630)](https://github.com/prowler-cloud/prowler/pull/10630) - Handle CIS and CISA SCuBA compliance framework from google workspace [(#10629)](https://github.com/prowler-cloud/prowler/pull/10629) ### 🔄 Changed +- Finding groups list/latest/resources now expose `status` ∈ `{FAIL, PASS, MANUAL}` and `muted: bool` as orthogonal fields. The aggregated `status` reflects the underlying check outcome regardless of mute state, and `muted=true` signals that every finding in the group/resource is muted. New `manual_count` is exposed alongside `pass_count`/`fail_count`, plus `pass_muted_count`/`fail_muted_count`/`manual_muted_count` siblings so clients can isolate the muted half of each status. The `new_*`/`changed_*` deltas are now broken down by status and mute state via 12 new counters (`new_fail_count`, `new_fail_muted_count`, `new_pass_count`, `new_pass_muted_count`, `new_manual_count`, `new_manual_muted_count` and the matching `changed_*` set). New `filter[muted]=true|false` and `sort=status` (FAIL > PASS > MANUAL) / `sort=muted` are supported. `filter[status]=MUTED` is no longer accepted [(#10630)](https://github.com/prowler-cloud/prowler/pull/10630) - Attack Paths: Periodic cleanup of stale scans with dead-worker detection via Celery inspect, marking orphaned `EXECUTING` scans as `FAILED` and recovering `graph_data_ready` [(#10387)](https://github.com/prowler-cloud/prowler/pull/10387) - Attack Paths: Replace `_provider_id` property with `_Provider_{uuid}` label for provider isolation, add regex-based label injection for custom queries [(#10402)](https://github.com/prowler-cloud/prowler/pull/10402) ### 🐞 Fixed +- `reaggregate_all_finding_group_summaries_task` now refreshes finding group daily summaries for every `(provider, day)` combination instead of only the latest scan per provider, matching the unbounded scope of `mute_historical_findings_task`. Mute rule operations no longer leave older daily summaries drifting from the underlying muted findings [(#10630)](https://github.com/prowler-cloud/prowler/pull/10630) - Finding groups list/latest now apply computed status/severity filters and finding-level prefilters (delta, region, service, category, resource group, scan, resource type), plus `check_title` support for sort/filter consistency [(#10428)](https://github.com/prowler-cloud/prowler/pull/10428) - Populate compliance data inside `check_metadata` for findings, which was always returned as `null` [(#10449)](https://github.com/prowler-cloud/prowler/pull/10449) - 403 error for admin users listing tenants due to roles query not using the admin database connection [(#10460)](https://github.com/prowler-cloud/prowler/pull/10460) diff --git a/api/src/backend/api/filters.py b/api/src/backend/api/filters.py index a496a0bf67..fa31289964 100644 --- a/api/src/backend/api/filters.py +++ b/api/src/backend/api/filters.py @@ -1115,13 +1115,14 @@ class FindingGroupAggregatedComputedFilter(FilterSet): STATUS_CHOICES = ( ("FAIL", "Fail"), ("PASS", "Pass"), - ("MUTED", "Muted"), + ("MANUAL", "Manual"), ) status = ChoiceFilter(method="filter_status", choices=STATUS_CHOICES) status__in = CharInFilter(method="filter_status_in", lookup_expr="in") severity = ChoiceFilter(method="filter_severity", choices=SeverityChoices) severity__in = CharInFilter(method="filter_severity_in", lookup_expr="in") + muted = BooleanFilter(field_name="muted") include_muted = BooleanFilter(method="filter_include_muted") def filter_status(self, queryset, name, value): @@ -1198,7 +1199,7 @@ class FindingGroupAggregatedComputedFilter(FilterSet): if value is True: return queryset # include_muted=false: exclude fully-muted groups - return queryset.exclude(fail_count=0, pass_count=0, muted_count__gt=0) + return queryset.exclude(muted=True) class ProviderSecretFilter(FilterSet): diff --git a/api/src/backend/api/migrations/0088_finding_group_status_muted_fields.py b/api/src/backend/api/migrations/0088_finding_group_status_muted_fields.py new file mode 100644 index 0000000000..ff3b981435 --- /dev/null +++ b/api/src/backend/api/migrations/0088_finding_group_status_muted_fields.py @@ -0,0 +1,95 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("api", "0087_vercel_provider"), + ] + + operations = [ + migrations.AddField( + model_name="findinggroupdailysummary", + name="manual_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="pass_muted_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="fail_muted_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="manual_muted_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="muted", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="new_fail_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="new_fail_muted_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="new_pass_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="new_pass_muted_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="new_manual_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="new_manual_muted_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="changed_fail_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="changed_fail_muted_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="changed_pass_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="changed_pass_muted_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="changed_manual_count", + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name="findinggroupdailysummary", + name="changed_manual_muted_count", + field=models.IntegerField(default=0), + ), + ] diff --git a/api/src/backend/api/migrations/0089_backfill_finding_group_status_muted.py b/api/src/backend/api/migrations/0089_backfill_finding_group_status_muted.py new file mode 100644 index 0000000000..501fcf3cb4 --- /dev/null +++ b/api/src/backend/api/migrations/0089_backfill_finding_group_status_muted.py @@ -0,0 +1,31 @@ +from django.db import migrations +from tasks.tasks import backfill_finding_group_summaries_task + +from api.db_router import MainRouter +from api.rls import Tenant + + +def trigger_backfill_task(apps, schema_editor): + """ + Re-dispatch the finding-group backfill task for every tenant so the new + `manual_count` and `muted` columns added in 0088 get populated from the + last 10 days of completed scans. + + The aggregator (`aggregate_finding_group_summaries`) recomputes every + column on each call, so it back-populates the new fields without touching + the existing ones beyond a normal upsert. + """ + tenant_ids = Tenant.objects.using(MainRouter.admin_db).values_list("id", flat=True) + + for tenant_id in tenant_ids: + backfill_finding_group_summaries_task.delay(tenant_id=str(tenant_id), days=10) + + +class Migration(migrations.Migration): + dependencies = [ + ("api", "0088_finding_group_status_muted_fields"), + ] + + operations = [ + migrations.RunPython(trigger_backfill_task, migrations.RunPython.noop), + ] diff --git a/api/src/backend/api/models.py b/api/src/backend/api/models.py index 5e0880be08..7f3131fe7b 100644 --- a/api/src/backend/api/models.py +++ b/api/src/backend/api/models.py @@ -1748,15 +1748,45 @@ class FindingGroupDailySummary(RowLevelSecurityProtectedModel): # Severity stored as integer for MAX aggregation (5=critical, 4=high, etc.) severity_order = models.SmallIntegerField(default=1) - # Finding counts + # Finding counts (inclusive of muted findings; use the `muted` flag to + # tell whether the group has any actionable findings). pass_count = models.IntegerField(default=0) fail_count = models.IntegerField(default=0) + manual_count = models.IntegerField(default=0) muted_count = models.IntegerField(default=0) - # Delta counts + # Status counts restricted to muted findings, so clients can isolate the + # muted half of each status (e.g. `pass_count - pass_muted_count` gives the + # actionable PASS findings). + pass_muted_count = models.IntegerField(default=0) + fail_muted_count = models.IntegerField(default=0) + manual_muted_count = models.IntegerField(default=0) + + # Whether every finding for this (provider, check, day) is muted. + muted = models.BooleanField(default=False) + + # Delta counts (non-muted, kept for convenience and as a "total" view). new_count = models.IntegerField(default=0) changed_count = models.IntegerField(default=0) + # Delta breakdown by (status, muted) so clients can answer questions like + # "how many new failing findings appeared in this scan?" without scanning + # the underlying findings table. Mirrors the existing pass/fail/manual + # naming, with `_muted_count` siblings tracking the muted half of each + # bucket explicitly. + new_fail_count = models.IntegerField(default=0) + new_fail_muted_count = models.IntegerField(default=0) + new_pass_count = models.IntegerField(default=0) + new_pass_muted_count = models.IntegerField(default=0) + new_manual_count = models.IntegerField(default=0) + new_manual_muted_count = models.IntegerField(default=0) + changed_fail_count = models.IntegerField(default=0) + changed_fail_muted_count = models.IntegerField(default=0) + changed_pass_count = models.IntegerField(default=0) + changed_pass_muted_count = models.IntegerField(default=0) + changed_manual_count = models.IntegerField(default=0) + changed_manual_muted_count = models.IntegerField(default=0) + # Resource counts resources_fail = models.IntegerField(default=0) resources_total = models.IntegerField(default=0) diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 439a355193..7457f20f4d 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -15445,10 +15445,16 @@ class TestFindingGroupViewSet: # iam_password_policy has only PASS findings assert data[0]["attributes"]["status"] == "PASS" - def test_finding_groups_status_muted_all( + def test_finding_groups_fully_muted_group_reflects_underlying_status( self, authenticated_client, finding_groups_fixture ): - """Test that MUTED status returned when all findings are muted.""" + """A fully-muted group still surfaces its underlying status (no MUTED). + + rds_encryption has 2 muted FAIL findings, so the group must report + status=FAIL (the orthogonal `muted` boolean signals it isn't actionable). + The status×muted breakdown lets clients answer 'how many failing + findings are muted in this group'. + """ response = authenticated_client.get( reverse("finding-group-list"), {"filter[inserted_at]": TODAY, "filter[check_id]": "rds_encryption"}, @@ -15456,8 +15462,21 @@ class TestFindingGroupViewSet: assert response.status_code == status.HTTP_200_OK data = response.json()["data"] assert len(data) == 1 - # rds_encryption has all muted findings - assert data[0]["attributes"]["status"] == "MUTED" + attrs = data[0]["attributes"] + assert attrs["status"] == "FAIL" + assert attrs["muted"] is True + assert attrs["fail_count"] == 2 + assert attrs["fail_muted_count"] == 2 + assert attrs["pass_muted_count"] == 0 + assert attrs["manual_muted_count"] == 0 + assert attrs["muted_count"] == 2 + # Sanity: the per-status muted counts must add up to muted_count. + assert ( + attrs["pass_muted_count"] + + attrs["fail_muted_count"] + + attrs["manual_muted_count"] + == attrs["muted_count"] + ) def test_finding_groups_status_filter( self, authenticated_client, finding_groups_fixture @@ -15949,7 +15968,7 @@ class TestFindingGroupViewSet: "extra_filters", [ {}, - {"filter[muted]": "include"}, + {"filter[delta]": "new"}, ], ids=["summary_path", "finding_level_path"], ) @@ -15967,7 +15986,8 @@ class TestFindingGroupViewSet: Parametrized to cover both aggregation paths: - summary_path: default, uses _CheckTitleToCheckIdMixin on summaries - - finding_level_path: filter[muted]=include forces CommonFindingFilters + - finding_level_path: filter[delta]=new forces _aggregate_findings via + CommonFindingFilters (delta is finding-level, not summary-level) """ params = { "filter[inserted_at]": TODAY, @@ -16885,3 +16905,287 @@ class TestFindingGroupViewSet: data = response.json()["data"] # Should still return data, not filtered by the old date assert len(data) == 5 + + def test_finding_groups_status_choices_no_muted( + self, authenticated_client, finding_groups_fixture + ): + """Every returned group must have status ∈ {FAIL, PASS, MANUAL}.""" + response = authenticated_client.get( + reverse("finding-group-list"), + {"filter[inserted_at]": TODAY}, + ) + assert response.status_code == status.HTTP_200_OK + statuses = {item["attributes"]["status"] for item in response.json()["data"]} + assert statuses, "fixture should produce at least one group" + assert statuses <= {"FAIL", "PASS", "MANUAL"} + assert "MUTED" not in statuses + + def test_finding_groups_serializer_exposes_muted_and_manual_count( + self, authenticated_client, finding_groups_fixture + ): + """The /finding-groups payload must expose `muted`, `manual_count` and + the per-status muted siblings (`pass_muted_count`/`fail_muted_count`/ + `manual_muted_count`).""" + response = authenticated_client.get( + reverse("finding-group-list"), + {"filter[inserted_at]": TODAY, "filter[check_id]": "iam_password_policy"}, + ) + assert response.status_code == status.HTTP_200_OK + attrs = response.json()["data"][0]["attributes"] + assert "muted" in attrs and isinstance(attrs["muted"], bool) + assert "manual_count" in attrs and isinstance(attrs["manual_count"], int) + assert attrs["muted"] is False # iam_password_policy has only non-muted PASS + assert attrs["manual_count"] == 0 + assert attrs["pass_muted_count"] == 0 + assert attrs["fail_muted_count"] == 0 + assert attrs["manual_muted_count"] == 0 + + @pytest.mark.parametrize( + "endpoint_name", ["finding-group-list", "finding-group-latest"] + ) + def test_finding_groups_filter_status_muted_is_rejected( + self, authenticated_client, finding_groups_fixture, endpoint_name + ): + """`filter[status]=MUTED` is no longer a valid status value.""" + params = {"filter[status]": "MUTED"} + if endpoint_name == "finding-group-list": + params["filter[inserted_at]"] = TODAY + + response = authenticated_client.get(reverse(endpoint_name), params) + assert response.status_code == status.HTTP_400_BAD_REQUEST + + @pytest.mark.parametrize( + "endpoint_name", ["finding-group-list", "finding-group-latest"] + ) + def test_finding_groups_filter_muted_true( + self, authenticated_client, finding_groups_fixture, endpoint_name + ): + """`filter[muted]=true` returns only fully-muted groups.""" + params = {"filter[muted]": "true"} + if endpoint_name == "finding-group-list": + params["filter[inserted_at]"] = TODAY + + response = authenticated_client.get(reverse(endpoint_name), params) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + check_ids = {item["id"] for item in data} + # Only rds_encryption is fully muted in the fixture + assert check_ids == {"rds_encryption"} + assert all(item["attributes"]["muted"] is True for item in data) + + @pytest.mark.parametrize( + "endpoint_name", ["finding-group-list", "finding-group-latest"] + ) + def test_finding_groups_filter_muted_false( + self, authenticated_client, finding_groups_fixture, endpoint_name + ): + """`filter[muted]=false` returns only groups with actionable findings.""" + params = {"filter[muted]": "false"} + if endpoint_name == "finding-group-list": + params["filter[inserted_at]"] = TODAY + + response = authenticated_client.get(reverse(endpoint_name), params) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + check_ids = {item["id"] for item in data} + assert "rds_encryption" not in check_ids + assert check_ids == { + "s3_bucket_public_access", + "ec2_instance_public_ip", + "iam_password_policy", + "cloudtrail_enabled", + } + assert all(item["attributes"]["muted"] is False for item in data) + + @pytest.mark.parametrize( + "endpoint_name", ["finding-group-list", "finding-group-latest"] + ) + def test_finding_groups_sort_by_status( + self, authenticated_client, finding_groups_fixture, endpoint_name + ): + """sort=status orders by aggregated status (FAIL > PASS > MANUAL).""" + priority = {"FAIL": 3, "PASS": 2, "MANUAL": 1} + params = {"sort": "-status"} + if endpoint_name == "finding-group-list": + params["filter[inserted_at]"] = TODAY + + response = authenticated_client.get(reverse(endpoint_name), params) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + assert data, "fixture should produce groups" + + desc_keys = [priority[item["attributes"]["status"]] for item in data] + assert desc_keys == sorted(desc_keys, reverse=True) + + params["sort"] = "status" + response = authenticated_client.get(reverse(endpoint_name), params) + assert response.status_code == status.HTTP_200_OK + asc_keys = [ + priority[item["attributes"]["status"]] for item in response.json()["data"] + ] + assert asc_keys == sorted(asc_keys) + + @pytest.mark.parametrize( + "endpoint_name", ["finding-group-list", "finding-group-latest"] + ) + def test_finding_groups_sort_by_muted( + self, authenticated_client, finding_groups_fixture, endpoint_name + ): + """sort=muted orders by the boolean muted attribute.""" + # Need include_muted=true so the fully-muted group is part of the result + params = {"sort": "-muted", "filter[include_muted]": "true"} + if endpoint_name == "finding-group-list": + params["filter[inserted_at]"] = TODAY + + response = authenticated_client.get(reverse(endpoint_name), params) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + assert data, "fixture should produce groups" + + muted_values = [item["attributes"]["muted"] for item in data] + # Descending boolean: True (1) before False (0) + assert muted_values == sorted(muted_values, reverse=True) + + @pytest.mark.parametrize( + "endpoint_name", ["finding-group-list", "finding-group-latest"] + ) + def test_finding_groups_delta_status_breakdown( + self, authenticated_client, finding_groups_fixture, endpoint_name + ): + """`new_*` and `changed_*` counters split by status and mute state. + + s3_bucket_public_access has 1 new FAIL and 1 changed FAIL (both + non-muted) so the breakdown must reflect exactly that and the totals + must equal the sum of the buckets. + """ + params = {"filter[check_id]": "s3_bucket_public_access"} + if endpoint_name == "finding-group-list": + params["filter[inserted_at]"] = TODAY + + response = authenticated_client.get(reverse(endpoint_name), params) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + assert len(data) == 1 + attrs = data[0]["attributes"] + + assert attrs["new_fail_count"] == 1 + assert attrs["new_fail_muted_count"] == 0 + assert attrs["new_pass_count"] == 0 + assert attrs["new_pass_muted_count"] == 0 + assert attrs["new_manual_count"] == 0 + assert attrs["new_manual_muted_count"] == 0 + assert attrs["changed_fail_count"] == 1 + assert attrs["changed_fail_muted_count"] == 0 + assert attrs["changed_pass_count"] == 0 + assert attrs["changed_pass_muted_count"] == 0 + assert attrs["changed_manual_count"] == 0 + assert attrs["changed_manual_muted_count"] == 0 + + new_total = ( + attrs["new_fail_count"] + + attrs["new_fail_muted_count"] + + attrs["new_pass_count"] + + attrs["new_pass_muted_count"] + + attrs["new_manual_count"] + + attrs["new_manual_muted_count"] + ) + changed_total = ( + attrs["changed_fail_count"] + + attrs["changed_fail_muted_count"] + + attrs["changed_pass_count"] + + attrs["changed_pass_muted_count"] + + attrs["changed_manual_count"] + + attrs["changed_manual_muted_count"] + ) + # The non-muted variants of the breakdown must sum to the legacy + # totals (new_count/changed_count are stored as non-muted). + assert ( + attrs["new_fail_count"] + + attrs["new_pass_count"] + + attrs["new_manual_count"] + == attrs["new_count"] + ) + assert ( + attrs["changed_fail_count"] + + attrs["changed_pass_count"] + + attrs["changed_manual_count"] + == attrs["changed_count"] + ) + # And the *full* breakdown (including the muted halves) is exposed + # so clients can also count muted-only deltas without losing data. + assert new_total >= attrs["new_count"] + assert changed_total >= attrs["changed_count"] + + def test_finding_groups_resources_serializer_exposes_muted( + self, authenticated_client, finding_groups_fixture + ): + """The /finding-groups//resources payload must expose `muted`.""" + response = authenticated_client.get( + reverse( + "finding-group-resources", + kwargs={"pk": "rds_encryption"}, + ), + {"filter[inserted_at]": TODAY}, + ) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + assert data, "rds_encryption should expose its resources" + for item in data: + attrs = item["attributes"] + assert "muted" in attrs and isinstance(attrs["muted"], bool) + # rds_encryption has all muted findings + assert attrs["muted"] is True + # Status reflects the underlying check outcome (FAIL), not MUTED + assert attrs["status"] == "FAIL" + + def test_finding_groups_resources_exposes_finding_id( + self, authenticated_client, finding_groups_fixture + ): + """The /resources payload exposes the most recent matching finding_id. + + rds_encryption has 2 findings, one per resource. Each resource row must + report the UUID of its corresponding Finding (UUIDv7 ordering means + Max(finding__id) resolves to the latest snapshot in time). + """ + response = authenticated_client.get( + reverse( + "finding-group-resources", + kwargs={"pk": "rds_encryption"}, + ), + {"filter[inserted_at]": TODAY}, + ) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + assert data, "rds_encryption should expose its resources" + + rds_finding_ids = { + str(f.id) for f in finding_groups_fixture if f.check_id == "rds_encryption" + } + assert rds_finding_ids, "fixture sanity" + + for item in data: + attrs = item["attributes"] + assert "finding_id" in attrs + assert attrs["finding_id"] in rds_finding_ids + + def test_finding_groups_latest_resources_exposes_finding_id( + self, authenticated_client, finding_groups_fixture + ): + """The /latest/.../resources payload also exposes finding_id.""" + response = authenticated_client.get( + reverse( + "finding-group-latest_resources", + kwargs={"check_id": "rds_encryption"}, + ), + ) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + assert data, "rds_encryption should expose its resources via /latest" + + rds_finding_ids = { + str(f.id) for f in finding_groups_fixture if f.check_id == "rds_encryption" + } + for item in data: + attrs = item["attributes"] + assert "finding_id" in attrs + assert attrs["finding_id"] in rds_finding_ids diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py index 6af4d01000..52ec47f4f5 100644 --- a/api/src/backend/api/v1/serializers.py +++ b/api/src/backend/api/v1/serializers.py @@ -4185,6 +4185,7 @@ class FindingGroupSerializer(BaseSerializerV1): check_description = serializers.CharField(required=False, allow_null=True) severity = serializers.CharField() status = serializers.CharField() + muted = serializers.BooleanField() impacted_providers = serializers.ListField( child=serializers.CharField(), required=False ) @@ -4192,9 +4193,25 @@ class FindingGroupSerializer(BaseSerializerV1): resources_total = serializers.IntegerField() pass_count = serializers.IntegerField() fail_count = serializers.IntegerField() + manual_count = serializers.IntegerField() + pass_muted_count = serializers.IntegerField() + fail_muted_count = serializers.IntegerField() + manual_muted_count = serializers.IntegerField() muted_count = serializers.IntegerField() new_count = serializers.IntegerField() changed_count = serializers.IntegerField() + new_fail_count = serializers.IntegerField() + new_fail_muted_count = serializers.IntegerField() + new_pass_count = serializers.IntegerField() + new_pass_muted_count = serializers.IntegerField() + new_manual_count = serializers.IntegerField() + new_manual_muted_count = serializers.IntegerField() + changed_fail_count = serializers.IntegerField() + changed_fail_muted_count = serializers.IntegerField() + changed_pass_count = serializers.IntegerField() + changed_pass_muted_count = serializers.IntegerField() + changed_manual_count = serializers.IntegerField() + changed_manual_muted_count = serializers.IntegerField() first_seen_at = serializers.DateTimeField(required=False, allow_null=True) last_seen_at = serializers.DateTimeField(required=False, allow_null=True) failing_since = serializers.DateTimeField(required=False, allow_null=True) @@ -4214,8 +4231,10 @@ class FindingGroupResourceSerializer(BaseSerializerV1): id = serializers.UUIDField(source="resource_id") resource = serializers.SerializerMethodField() provider = serializers.SerializerMethodField() + finding_id = serializers.UUIDField() status = serializers.CharField() severity = serializers.CharField() + muted = serializers.BooleanField() delta = serializers.CharField(required=False, allow_null=True) first_seen_at = serializers.DateTimeField(required=False, allow_null=True) last_seen_at = serializers.DateTimeField(required=False, allow_null=True) diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index 2392b818ac..23cfe17f2d 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -26,10 +26,11 @@ from config.settings.social_login import ( ) from dj_rest_auth.registration.views import SocialLoginView from django.conf import settings as django_settings -from django.contrib.postgres.aggregates import ArrayAgg, StringAgg +from django.contrib.postgres.aggregates import ArrayAgg, BoolAnd, StringAgg from django.contrib.postgres.search import SearchQuery from django.db import transaction from django.db.models import ( + BooleanField, Case, CharField, Count, @@ -7076,9 +7077,29 @@ class FindingGroupViewSet(BaseRLSViewSet): severity_order=Max("severity_order"), pass_count=Sum("pass_count"), fail_count=Sum("fail_count"), + manual_count=Sum("manual_count"), + pass_muted_count=Sum("pass_muted_count"), + fail_muted_count=Sum("fail_muted_count"), + manual_muted_count=Sum("manual_muted_count"), muted_count=Sum("muted_count"), + # The group is muted only if every contributing daily summary is + # itself fully muted. BoolAnd returns False as soon as one row has + # at least one actionable finding. + muted=BoolAnd("muted"), new_count=Sum("new_count"), changed_count=Sum("changed_count"), + new_fail_count=Sum("new_fail_count"), + new_fail_muted_count=Sum("new_fail_muted_count"), + new_pass_count=Sum("new_pass_count"), + new_pass_muted_count=Sum("new_pass_muted_count"), + new_manual_count=Sum("new_manual_count"), + new_manual_muted_count=Sum("new_manual_muted_count"), + changed_fail_count=Sum("changed_fail_count"), + changed_fail_muted_count=Sum("changed_fail_muted_count"), + changed_pass_count=Sum("changed_pass_count"), + changed_pass_muted_count=Sum("changed_pass_muted_count"), + changed_manual_count=Sum("changed_manual_count"), + changed_manual_muted_count=Sum("changed_manual_muted_count"), resources_total=Sum("resources_total"), resources_fail=Sum("resources_fail"), impacted_providers_str=StringAgg( @@ -7104,39 +7125,95 @@ class FindingGroupViewSet(BaseRLSViewSet): output_field=IntegerField(), ) - return queryset.values("check_id").annotate( - severity_order=Max(severity_case), - pass_count=Count("id", filter=Q(status="PASS", muted=False)), - fail_count=Count("id", filter=Q(status="FAIL", muted=False)), - muted_count=Count("id", filter=Q(muted=True)), - new_count=Count("id", filter=Q(delta="new", muted=False)), - changed_count=Count("id", filter=Q(delta="changed", muted=False)), - resources_total=Count("resources__id", distinct=True), - resources_fail=Count( - "resources__id", - distinct=True, - filter=Q(status="FAIL", muted=False), - ), - impacted_providers_str=StringAgg( - Cast("scan__provider__provider", CharField()), - delimiter=",", - distinct=True, - default="", - ), - agg_first_seen_at=Min("first_seen_at"), - agg_last_seen_at=Max("inserted_at"), - agg_failing_since=Min( - "first_seen_at", filter=Q(status="FAIL", muted=False) - ), - check_title=Coalesce( - Max(KeyTextTransform("checktitle", "check_metadata")), - Max(KeyTextTransform("CheckTitle", "check_metadata")), - Max(KeyTextTransform("Checktitle", "check_metadata")), - ), - check_description=Coalesce( - Max(KeyTextTransform("description", "check_metadata")), - Max(KeyTextTransform("Description", "check_metadata")), - ), + # `pass_count`, `fail_count` and `manual_count` count *every* finding + # for the check (muted or not) so the aggregated `status` reflects the + # underlying check outcome regardless of mute state. Whether the group + # is actionable is signalled by the orthogonal `muted` flag below. + return ( + queryset.values("check_id") + .annotate( + severity_order=Max(severity_case), + pass_count=Count("id", filter=Q(status="PASS")), + fail_count=Count("id", filter=Q(status="FAIL")), + manual_count=Count("id", filter=Q(status="MANUAL")), + pass_muted_count=Count("id", filter=Q(status="PASS", muted=True)), + fail_muted_count=Count("id", filter=Q(status="FAIL", muted=True)), + manual_muted_count=Count("id", filter=Q(status="MANUAL", muted=True)), + muted_count=Count("id", filter=Q(muted=True)), + nonmuted_count=Count("id", filter=Q(muted=False)), + new_count=Count("id", filter=Q(delta="new", muted=False)), + changed_count=Count("id", filter=Q(delta="changed", muted=False)), + new_fail_count=Count( + "id", filter=Q(delta="new", status="FAIL", muted=False) + ), + new_fail_muted_count=Count( + "id", filter=Q(delta="new", status="FAIL", muted=True) + ), + new_pass_count=Count( + "id", filter=Q(delta="new", status="PASS", muted=False) + ), + new_pass_muted_count=Count( + "id", filter=Q(delta="new", status="PASS", muted=True) + ), + new_manual_count=Count( + "id", filter=Q(delta="new", status="MANUAL", muted=False) + ), + new_manual_muted_count=Count( + "id", filter=Q(delta="new", status="MANUAL", muted=True) + ), + changed_fail_count=Count( + "id", filter=Q(delta="changed", status="FAIL", muted=False) + ), + changed_fail_muted_count=Count( + "id", filter=Q(delta="changed", status="FAIL", muted=True) + ), + changed_pass_count=Count( + "id", filter=Q(delta="changed", status="PASS", muted=False) + ), + changed_pass_muted_count=Count( + "id", filter=Q(delta="changed", status="PASS", muted=True) + ), + changed_manual_count=Count( + "id", filter=Q(delta="changed", status="MANUAL", muted=False) + ), + changed_manual_muted_count=Count( + "id", filter=Q(delta="changed", status="MANUAL", muted=True) + ), + resources_total=Count("resources__id", distinct=True), + resources_fail=Count( + "resources__id", + distinct=True, + filter=Q(status="FAIL", muted=False), + ), + impacted_providers_str=StringAgg( + Cast("scan__provider__provider", CharField()), + delimiter=",", + distinct=True, + default="", + ), + agg_first_seen_at=Min("first_seen_at"), + agg_last_seen_at=Max("inserted_at"), + agg_failing_since=Min( + "first_seen_at", filter=Q(status="FAIL", muted=False) + ), + check_title=Coalesce( + Max(KeyTextTransform("checktitle", "check_metadata")), + Max(KeyTextTransform("CheckTitle", "check_metadata")), + Max(KeyTextTransform("Checktitle", "check_metadata")), + ), + check_description=Coalesce( + Max(KeyTextTransform("description", "check_metadata")), + Max(KeyTextTransform("Description", "check_metadata")), + ), + ) + .annotate( + # Group is muted only if it has zero non-muted findings. + muted=Case( + When(nonmuted_count=0, then=Value(True)), + default=Value(False), + output_field=BooleanField(), + ), + ) ) def _split_computed_aggregate_filters( @@ -7148,6 +7225,7 @@ class FindingGroupViewSet(BaseRLSViewSet): "status__in", "severity", "severity__in", + "muted", "include_muted", } finding_params = QueryDict(mutable=True) @@ -7179,7 +7257,8 @@ class FindingGroupViewSet(BaseRLSViewSet): Post-process aggregation results to add computed fields. - Converts severity integer back to string - - Computes aggregated status (FAIL > PASS > MUTED) + - Computes aggregated status (FAIL > PASS > MANUAL); the orthogonal + ``muted`` boolean is already on the row from the SQL aggregation - Converts provider string to list """ results = [] @@ -7197,13 +7276,19 @@ class FindingGroupViewSet(BaseRLSViewSet): if "agg_failing_since" in row: row["failing_since"] = row.pop("agg_failing_since") - # Compute aggregated status + # Drop the helper count we use to derive `muted` in the + # finding-level aggregation path. + row.pop("nonmuted_count", None) + + # Compute aggregated status. Counts are inclusive of muted findings, + # so the underlying check outcome surfaces even when the group is + # fully muted. if row.get("fail_count", 0) > 0: row["status"] = "FAIL" elif row.get("pass_count", 0) > 0: row["status"] = "PASS" else: - row["status"] = "MUTED" + row["status"] = "MANUAL" # Convert provider string to list providers_str = row.pop("impacted_providers_str", "") or "" @@ -7219,9 +7304,12 @@ class FindingGroupViewSet(BaseRLSViewSet): "check_id": "check_id", "check_title": "check_title", "severity": "severity_order", + "status": "status_order", + "muted": "muted", "delta": "delta_order", "fail_count": "fail_count", "pass_count": "pass_count", + "manual_count": "manual_count", "muted_count": "muted_count", "new_count": "new_count", "changed_count": "changed_count", @@ -7276,7 +7364,7 @@ class FindingGroupViewSet(BaseRLSViewSet): return ordering def _apply_aggregated_computed_filters(self, queryset, computed_params: QueryDict): - """Apply computed filters (status/severity) on aggregated finding-group rows.""" + """Apply computed filters (status/severity/muted) on aggregated finding-group rows.""" if not computed_params: return queryset @@ -7285,14 +7373,16 @@ class FindingGroupViewSet(BaseRLSViewSet): aggregated_status=Case( When(fail_count__gt=0, then=Value("FAIL")), When(pass_count__gt=0, then=Value("PASS")), - default=Value("MUTED"), + default=Value("MANUAL"), output_field=CharField(), ) ) - # Exclude fully-muted groups by default unless include_muted is set - if "include_muted" not in computed_params: - queryset = queryset.exclude(fail_count=0, pass_count=0, muted_count__gt=0) + # Exclude fully-muted groups by default unless the caller has opted in + # via either `include_muted` or an explicit `muted` filter (the latter + # gives the caller direct control over the column). + if "include_muted" not in computed_params and "muted" not in computed_params: + queryset = queryset.exclude(muted=True) filterset = FindingGroupAggregatedComputedFilter( computed_params, queryset=queryset @@ -7347,18 +7437,14 @@ class FindingGroupViewSet(BaseRLSViewSet): provider_type=Max("resource__provider__provider"), provider_uid=Max("resource__provider__uid"), provider_alias=Max("resource__provider__alias"), + # status_order considers ALL findings (muted or not) so it + # surfaces FAIL/PASS/MANUAL based on the underlying check + # outcome. Whether the resource is actionable is signalled by + # the orthogonal `muted` flag below. status_order=Max( Case( - When( - finding__status="FAIL", - finding__muted=False, - then=Value(3), - ), - When( - finding__status="PASS", - finding__muted=False, - then=Value(2), - ), + When(finding__status="FAIL", then=Value(3)), + When(finding__status="PASS", then=Value(2)), default=Value(1), output_field=IntegerField(), ) @@ -7390,6 +7476,8 @@ class FindingGroupViewSet(BaseRLSViewSet): ), first_seen_at=Min("finding__first_seen_at"), last_seen_at=Max("finding__inserted_at"), + # True only if every finding for this resource+check is muted. + muted=BoolAnd("finding__muted"), # Max() on muted_reason / check_metadata is safe because # all findings for the same resource+check share identical # values (mute rules and metadata are applied per-check). @@ -7397,6 +7485,12 @@ class FindingGroupViewSet(BaseRLSViewSet): resource_group=Max( KeyTextTransform("resourcegroup", "finding__check_metadata") ), + # Most recent matching Finding for this (resource, check): + # Finding.id is a UUIDv7 (time-ordered in its high 48 bits). + # Cast to text first because PostgreSQL has no built-in + # `max(uuid)` aggregate; on the canonical lowercase form a + # lexicographic Max() still resolves to the latest snapshot. + finding_id=Max(Cast("finding__id", output_field=CharField())), ) .filter(resource_id__isnull=False) ) @@ -7405,8 +7499,8 @@ class FindingGroupViewSet(BaseRLSViewSet): _RESOURCE_SORT_ANNOTATIONS = { "status_order": lambda: Max( Case( - When(finding__status="FAIL", finding__muted=False, then=Value(3)), - When(finding__status="PASS", finding__muted=False, then=Value(2)), + When(finding__status="FAIL", then=Value(3)), + When(finding__status="PASS", then=Value(2)), default=Value(1), output_field=IntegerField(), ) @@ -7480,7 +7574,7 @@ class FindingGroupViewSet(BaseRLSViewSet): elif status_order == 2: status = "PASS" else: - status = "MUTED" + status = "MANUAL" delta_order = row.get("delta_order", 0) if delta_order == 2: @@ -7508,8 +7602,12 @@ class FindingGroupViewSet(BaseRLSViewSet): "delta": delta, "first_seen_at": row["first_seen_at"], "last_seen_at": row["last_seen_at"], + "muted": bool(row.get("muted", False)), "muted_reason": row.get("muted_reason"), "resource_group": row.get("resource_group", ""), + "finding_id": ( + str(row["finding_id"]) if row.get("finding_id") else None + ), } ) @@ -7570,6 +7668,21 @@ class FindingGroupViewSet(BaseRLSViewSet): sort_param, self._FINDING_GROUP_SORT_MAP ) if ordering: + # status_order is annotated on demand so groups can be sorted by + # their aggregated status (FAIL > PASS > MANUAL), mirroring the + # priority used in _post_process_aggregation. Counts are + # inclusive of muted findings, so the underlying check outcome + # surfaces even for fully muted groups. + if any(field.lstrip("-") == "status_order" for field in ordering): + aggregated_queryset = aggregated_queryset.annotate( + status_order=Case( + When(fail_count__gt=0, then=Value(3)), + When(pass_count__gt=0, then=Value(2)), + default=Value(1), + output_field=IntegerField(), + ) + ) + # delta_order is a virtual sort field: expand it to a # lexicographic ordering by (new_count, changed_count) so groups # with more new findings rank higher, with changed_count as the diff --git a/api/src/backend/tasks/jobs/scan.py b/api/src/backend/tasks/jobs/scan.py index 364b12d146..2c73c97f2f 100644 --- a/api/src/backend/tasks/jobs/scan.py +++ b/api/src/backend/tasks/jobs/scan.py @@ -1803,7 +1803,12 @@ def aggregate_finding_group_summaries(tenant_id: str, scan_id: str): output_field=IntegerField(), ) - # Aggregate findings by check_id for this scan + # Aggregate findings by check_id for this scan. + # `pass_count`, `fail_count` and `manual_count` count *every* finding + # in this group, regardless of mute state, so the aggregated `status` + # always reflects the underlying check outcome (FAIL > PASS > MANUAL) + # even when the group is fully muted. The orthogonal `muted` flag is + # what tells whether the group has any actionable (non-muted) findings. aggregated = ( Finding.objects.filter( tenant_id=tenant_id, @@ -1812,11 +1817,52 @@ def aggregate_finding_group_summaries(tenant_id: str, scan_id: str): .values("check_id") .annotate( severity_order=Max(severity_case), - pass_count=Count("id", filter=Q(status="PASS", muted=False)), - fail_count=Count("id", filter=Q(status="FAIL", muted=False)), + pass_count=Count("id", filter=Q(status="PASS")), + fail_count=Count("id", filter=Q(status="FAIL")), + manual_count=Count("id", filter=Q(status="MANUAL")), + pass_muted_count=Count("id", filter=Q(status="PASS", muted=True)), + fail_muted_count=Count("id", filter=Q(status="FAIL", muted=True)), + manual_muted_count=Count("id", filter=Q(status="MANUAL", muted=True)), muted_count=Count("id", filter=Q(muted=True)), + nonmuted_count=Count("id", filter=Q(muted=False)), new_count=Count("id", filter=Q(delta="new", muted=False)), changed_count=Count("id", filter=Q(delta="changed", muted=False)), + new_fail_count=Count( + "id", filter=Q(delta="new", status="FAIL", muted=False) + ), + new_fail_muted_count=Count( + "id", filter=Q(delta="new", status="FAIL", muted=True) + ), + new_pass_count=Count( + "id", filter=Q(delta="new", status="PASS", muted=False) + ), + new_pass_muted_count=Count( + "id", filter=Q(delta="new", status="PASS", muted=True) + ), + new_manual_count=Count( + "id", filter=Q(delta="new", status="MANUAL", muted=False) + ), + new_manual_muted_count=Count( + "id", filter=Q(delta="new", status="MANUAL", muted=True) + ), + changed_fail_count=Count( + "id", filter=Q(delta="changed", status="FAIL", muted=False) + ), + changed_fail_muted_count=Count( + "id", filter=Q(delta="changed", status="FAIL", muted=True) + ), + changed_pass_count=Count( + "id", filter=Q(delta="changed", status="PASS", muted=False) + ), + changed_pass_muted_count=Count( + "id", filter=Q(delta="changed", status="PASS", muted=True) + ), + changed_manual_count=Count( + "id", filter=Q(delta="changed", status="MANUAL", muted=False) + ), + changed_manual_muted_count=Count( + "id", filter=Q(delta="changed", status="MANUAL", muted=True) + ), resources_total=Count("resources__id", distinct=True), resources_fail=Count( "resources__id", @@ -1895,9 +1941,26 @@ def aggregate_finding_group_summaries(tenant_id: str, scan_id: str): severity_order=row["severity_order"] or 1, pass_count=row["pass_count"], fail_count=row["fail_count"], + manual_count=row["manual_count"], + pass_muted_count=row["pass_muted_count"], + fail_muted_count=row["fail_muted_count"], + manual_muted_count=row["manual_muted_count"], muted_count=row["muted_count"], + muted=row["nonmuted_count"] == 0, new_count=row["new_count"], changed_count=row["changed_count"], + new_fail_count=row["new_fail_count"], + new_fail_muted_count=row["new_fail_muted_count"], + new_pass_count=row["new_pass_count"], + new_pass_muted_count=row["new_pass_muted_count"], + new_manual_count=row["new_manual_count"], + new_manual_muted_count=row["new_manual_muted_count"], + changed_fail_count=row["changed_fail_count"], + changed_fail_muted_count=row["changed_fail_muted_count"], + changed_pass_count=row["changed_pass_count"], + changed_pass_muted_count=row["changed_pass_muted_count"], + changed_manual_count=row["changed_manual_count"], + changed_manual_muted_count=row["changed_manual_muted_count"], resources_total=row["resources_total"], resources_fail=row["resources_fail"], first_seen_at=row["agg_first_seen_at"], @@ -1917,9 +1980,26 @@ def aggregate_finding_group_summaries(tenant_id: str, scan_id: str): "severity_order", "pass_count", "fail_count", + "manual_count", + "pass_muted_count", + "fail_muted_count", + "manual_muted_count", "muted_count", + "muted", "new_count", "changed_count", + "new_fail_count", + "new_fail_muted_count", + "new_pass_count", + "new_pass_muted_count", + "new_manual_count", + "new_manual_muted_count", + "changed_fail_count", + "changed_fail_muted_count", + "changed_pass_count", + "changed_pass_muted_count", + "changed_manual_count", + "changed_manual_muted_count", "resources_total", "resources_fail", "first_seen_at", diff --git a/api/src/backend/tasks/tasks.py b/api/src/backend/tasks/tasks.py index fbd0440af8..bbf4d89772 100644 --- a/api/src/backend/tasks/tasks.py +++ b/api/src/backend/tasks/tasks.py @@ -771,26 +771,49 @@ def aggregate_finding_group_summaries_task(tenant_id: str, scan_id: str): ) @set_tenant(keep_tenant=True) def reaggregate_all_finding_group_summaries_task(tenant_id: str): - """Reaggregate finding group summaries for all providers' latest completed scans.""" - latest_scan_ids = list( - Scan.objects.filter(tenant_id=tenant_id, state=StateChoices.COMPLETED) - .order_by("provider_id", "-completed_at", "-inserted_at") - .distinct("provider_id") - .values_list("id", flat=True) + """Reaggregate finding group summaries for every (provider, day) combination. + + Mirrors the unbounded scope of `mute_historical_findings_task`: that task + rewrites every Finding row whose UID matches a mute rule, with no time + limit. To keep the daily summaries consistent with that update, this task + re-runs the aggregator on the latest completed scan of every (provider, + day) pair that exists in the database. Tasks are dispatched in parallel + via a Celery group so the wallclock scales with the worker pool, not with + the number of pairs. + """ + completed_scans = list( + Scan.objects.filter( + tenant_id=tenant_id, + state=StateChoices.COMPLETED, + completed_at__isnull=False, + ) + .order_by("-completed_at") + .values("id", "completed_at", "provider_id") ) - if latest_scan_ids: + + # Keep the latest scan per (provider, day) pair so the daily summary row + # the aggregator writes is the most recent snapshot of that day for that + # provider. Iterating from most recent to oldest means the first scan we + # see for a given key wins. + latest_scans: dict[tuple, str] = {} + for scan in completed_scans: + key = (scan["provider_id"], scan["completed_at"].date()) + if key not in latest_scans: + latest_scans[key] = str(scan["id"]) + + scan_ids = list(latest_scans.values()) + if scan_ids: logger.info( - "Reaggregating finding group summaries for %d scans: %s", - len(latest_scan_ids), - latest_scan_ids, + "Reaggregating finding group summaries for %d scans (provider x day)", + len(scan_ids), ) group( aggregate_finding_group_summaries_task.si( - tenant_id=tenant_id, scan_id=str(scan_id) + tenant_id=tenant_id, scan_id=scan_id ) - for scan_id in latest_scan_ids + for scan_id in scan_ids ).apply_async() - return {"scans_reaggregated": len(latest_scan_ids)} + return {"scans_reaggregated": len(scan_ids)} @shared_task(base=RLSTask, name="lighthouse-connection-check") diff --git a/api/src/backend/tasks/tests/test_tasks.py b/api/src/backend/tasks/tests/test_tasks.py index 8469b7db09..4a4108607e 100644 --- a/api/src/backend/tasks/tests/test_tasks.py +++ b/api/src/backend/tasks/tests/test_tasks.py @@ -1,6 +1,6 @@ import uuid from contextlib import contextmanager -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from unittest.mock import MagicMock, patch import openai @@ -2362,35 +2362,96 @@ class TestReaggregateAllFindingGroupSummaries: @patch("tasks.tasks.group") @patch("tasks.tasks.aggregate_finding_group_summaries_task") @patch("tasks.tasks.Scan.objects.filter") - def test_dispatches_subtasks_for_each_provider( + def test_dispatches_subtasks_for_each_provider_per_day( self, mock_scan_filter, mock_agg_task, mock_group ): - scan_id_1 = uuid.uuid4() - scan_id_2 = uuid.uuid4() + provider_id_1 = uuid.uuid4() + provider_id_2 = uuid.uuid4() + scan_id_today_p1 = uuid.uuid4() + scan_id_yesterday_p1 = uuid.uuid4() + scan_id_today_p2 = uuid.uuid4() + today = datetime.now(tz=timezone.utc) + yesterday = today - timedelta(days=1) + mock_group_result = MagicMock() mock_group.side_effect = lambda gen: (list(gen), mock_group_result)[1] - mock_scan_filter.return_value.order_by.return_value.distinct.return_value.values_list.return_value = [ - scan_id_1, - scan_id_2, + mock_scan_filter.return_value.order_by.return_value.values.return_value = [ + { + "id": scan_id_today_p1, + "completed_at": today, + "provider_id": provider_id_1, + }, + { + "id": scan_id_today_p2, + "completed_at": today, + "provider_id": provider_id_2, + }, + { + "id": scan_id_yesterday_p1, + "completed_at": yesterday, + "provider_id": provider_id_1, + }, ] result = reaggregate_all_finding_group_summaries_task(tenant_id=self.tenant_id) - assert result == {"scans_reaggregated": 2} - assert mock_agg_task.si.call_count == 2 + assert result == {"scans_reaggregated": 3} + assert mock_agg_task.si.call_count == 3 mock_agg_task.si.assert_any_call( - tenant_id=self.tenant_id, scan_id=str(scan_id_1) + tenant_id=self.tenant_id, scan_id=str(scan_id_today_p1) ) mock_agg_task.si.assert_any_call( - tenant_id=self.tenant_id, scan_id=str(scan_id_2) + tenant_id=self.tenant_id, scan_id=str(scan_id_today_p2) + ) + mock_agg_task.si.assert_any_call( + tenant_id=self.tenant_id, scan_id=str(scan_id_yesterday_p1) + ) + mock_group_result.apply_async.assert_called_once() + + @patch("tasks.tasks.group") + @patch("tasks.tasks.aggregate_finding_group_summaries_task") + @patch("tasks.tasks.Scan.objects.filter") + def test_dedupes_scans_to_latest_per_provider_per_day( + self, mock_scan_filter, mock_agg_task, mock_group + ): + """When several scans run on the same day for the same provider, only + the latest one is dispatched (matching the daily summary unique key).""" + provider_id = uuid.uuid4() + latest_scan_today = uuid.uuid4() + earlier_scan_today = uuid.uuid4() + today_late = datetime.now(tz=timezone.utc) + today_early = today_late - timedelta(hours=4) + + mock_group_result = MagicMock() + mock_group.side_effect = lambda gen: (list(gen), mock_group_result)[1] + + # Returned ordered by `-completed_at`, so the most recent comes first. + mock_scan_filter.return_value.order_by.return_value.values.return_value = [ + { + "id": latest_scan_today, + "completed_at": today_late, + "provider_id": provider_id, + }, + { + "id": earlier_scan_today, + "completed_at": today_early, + "provider_id": provider_id, + }, + ] + + result = reaggregate_all_finding_group_summaries_task(tenant_id=self.tenant_id) + + assert result == {"scans_reaggregated": 1} + mock_agg_task.si.assert_called_once_with( + tenant_id=self.tenant_id, scan_id=str(latest_scan_today) ) mock_group_result.apply_async.assert_called_once() @patch("tasks.tasks.group") @patch("tasks.tasks.Scan.objects.filter") def test_no_completed_scans_skips_dispatch(self, mock_scan_filter, mock_group): - mock_scan_filter.return_value.order_by.return_value.distinct.return_value.values_list.return_value = [] + mock_scan_filter.return_value.order_by.return_value.values.return_value = [] result = reaggregate_all_finding_group_summaries_task(tenant_id=self.tenant_id) From 0e8080f09cbcbf10f48179d55c98b6a5f3fd7ade Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:44:10 +0200 Subject: [PATCH 28/65] fix(ui): findings groups fixes (#10633) --- .../finding-groups.adapter.test.ts | 24 ++ .../finding-groups/finding-groups.adapter.ts | 48 +++- .../finding-groups/finding-groups.test.ts | 24 +- ui/actions/finding-groups/finding-groups.ts | 7 +- .../findings/findings-by-resource.test.ts | 258 ++++-------------- ui/actions/findings/findings-by-resource.ts | 177 ++---------- .../compliance/[compliancetitle]/page.tsx | 41 ++- ui/components/compliance/compliance-card.tsx | 12 - .../compliance/threatscore-badge.tsx | 12 - ui/components/findings/findings-filters.tsx | 14 +- .../findings/findings-filters.utils.test.ts | 18 ++ .../findings/findings-filters.utils.ts | 10 + .../findings/floating-mute-button.test.tsx | 107 +++++++- .../findings/floating-mute-button.tsx | 42 ++- .../findings/mute-findings-modal.tsx | 172 +++++++++--- .../table/column-finding-groups.test.tsx | 44 ++- .../findings/table/column-finding-groups.tsx | 24 +- .../table/column-finding-resources.test.tsx | 75 ++++- .../table/column-finding-resources.tsx | 20 +- .../findings/table/column-findings.tsx | 1 + .../table/data-table-row-actions.test.tsx | 182 ++++++++++++ .../findings/table/data-table-row-actions.tsx | 72 ++++- .../table/finding-group-selection.test.ts | 13 +- .../findings/table/finding-group-selection.ts | 20 +- .../table/findings-group-drill-down.tsx | 32 +-- .../findings/table/findings-group-table.tsx | 17 +- .../table/inline-resource-container.tsx | 102 ++++--- .../inline-resource-container.utils.test.ts | 101 +++++++ .../table/inline-resource-container.utils.ts | 55 ++++ .../findings/table/notification-indicator.tsx | 110 +++++--- .../resource-detail-drawer-content.test.tsx | 4 +- .../resource-detail-drawer-content.tsx | 24 -- .../table/resource-findings-columns.tsx | 1 + ui/lib/findings-groups.test.ts | 102 +++++++ ui/lib/findings-groups.ts | 77 ++++++ ui/types/findings-table.ts | 19 ++ 36 files changed, 1409 insertions(+), 652 deletions(-) create mode 100644 ui/components/findings/table/data-table-row-actions.test.tsx create mode 100644 ui/components/findings/table/inline-resource-container.utils.test.ts create mode 100644 ui/components/findings/table/inline-resource-container.utils.ts create mode 100644 ui/lib/findings-groups.test.ts create mode 100644 ui/lib/findings-groups.ts diff --git a/ui/actions/finding-groups/finding-groups.adapter.test.ts b/ui/actions/finding-groups/finding-groups.adapter.test.ts index 1d5d04e62d..c9b4a0314c 100644 --- a/ui/actions/finding-groups/finding-groups.adapter.test.ts +++ b/ui/actions/finding-groups/finding-groups.adapter.test.ts @@ -78,14 +78,31 @@ describe("adaptFindingGroupsResponse — malformed input", () => { check_description: null, severity: "critical", status: "FAIL", + muted: true, impacted_providers: ["aws"], resources_total: 5, resources_fail: 3, pass_count: 2, fail_count: 3, + manual_count: 1, + pass_muted_count: 0, + fail_muted_count: 3, + manual_muted_count: 0, muted_count: 0, new_count: 1, changed_count: 0, + new_fail_count: 0, + new_fail_muted_count: 1, + new_pass_count: 0, + new_pass_muted_count: 0, + new_manual_count: 0, + new_manual_muted_count: 0, + changed_fail_count: 0, + changed_fail_muted_count: 0, + changed_pass_count: 0, + changed_pass_muted_count: 0, + changed_manual_count: 0, + changed_manual_muted_count: 0, first_seen_at: null, last_seen_at: "2024-01-01T00:00:00Z", failing_since: null, @@ -101,6 +118,9 @@ describe("adaptFindingGroupsResponse — malformed input", () => { expect(result).toHaveLength(1); expect(result[0].checkId).toBe("s3_bucket_public_access"); expect(result[0].checkTitle).toBe("S3 Bucket Public Access"); + expect(result[0].muted).toBe(true); + expect(result[0].manualCount).toBe(1); + expect(result[0].newFailMutedCount).toBe(1); }); }); @@ -149,6 +169,7 @@ describe("adaptFindingGroupResourcesResponse — malformed input", () => { id: "resource-row-1", type: "finding-group-resources", attributes: { + finding_id: "real-finding-uuid", resource: { uid: "arn:aws:s3:::my-bucket", name: "my-bucket", @@ -163,6 +184,7 @@ describe("adaptFindingGroupResourcesResponse — malformed input", () => { alias: "production", }, status: "FAIL", + muted: true, delta: "new", severity: "critical", first_seen_at: null, @@ -177,8 +199,10 @@ describe("adaptFindingGroupResourcesResponse — malformed input", () => { // Then expect(result).toHaveLength(1); + expect(result[0].findingId).toBe("real-finding-uuid"); expect(result[0].checkId).toBe("s3_check"); expect(result[0].resourceName).toBe("my-bucket"); expect(result[0].delta).toBe("new"); + expect(result[0].isMuted).toBe(true); }); }); diff --git a/ui/actions/finding-groups/finding-groups.adapter.ts b/ui/actions/finding-groups/finding-groups.adapter.ts index 2e3964522e..7f260c8aa6 100644 --- a/ui/actions/finding-groups/finding-groups.adapter.ts +++ b/ui/actions/finding-groups/finding-groups.adapter.ts @@ -19,15 +19,32 @@ interface FindingGroupAttributes { check_title: string | null; check_description: string | null; severity: string; - status: string; // "FAIL" | "PASS" | "MUTED" (already uppercase) + status: string; // "FAIL" | "PASS" | "MANUAL" (already uppercase) + muted?: boolean; impacted_providers: string[]; resources_total: number; resources_fail: number; pass_count: number; fail_count: number; + manual_count?: number; + pass_muted_count?: number; + fail_muted_count?: number; + manual_muted_count?: number; muted_count: number; new_count: number; changed_count: number; + new_fail_count?: number; + new_fail_muted_count?: number; + new_pass_count?: number; + new_pass_muted_count?: number; + new_manual_count?: number; + new_manual_muted_count?: number; + changed_fail_count?: number; + changed_fail_muted_count?: number; + changed_pass_count?: number; + changed_pass_muted_count?: number; + changed_manual_count?: number; + changed_manual_muted_count?: number; first_seen_at: string | null; last_seen_at: string | null; failing_since: string | null; @@ -62,10 +79,33 @@ export function adaptFindingGroupsResponse( checkTitle: item.attributes.check_title || item.attributes.check_id, severity: item.attributes.severity as Severity, status: item.attributes.status as FindingStatus, + muted: + item.attributes.muted ?? + (item.attributes.muted_count > 0 && + (item.attributes.muted_count === item.attributes.resources_fail || + item.attributes.muted_count === item.attributes.resources_total)), resourcesTotal: item.attributes.resources_total, resourcesFail: item.attributes.resources_fail, + passCount: item.attributes.pass_count, + failCount: item.attributes.fail_count, + manualCount: item.attributes.manual_count ?? 0, + passMutedCount: item.attributes.pass_muted_count ?? 0, + failMutedCount: item.attributes.fail_muted_count ?? 0, + manualMutedCount: item.attributes.manual_muted_count ?? 0, newCount: item.attributes.new_count, changedCount: item.attributes.changed_count, + newFailCount: item.attributes.new_fail_count ?? 0, + newFailMutedCount: item.attributes.new_fail_muted_count ?? 0, + newPassCount: item.attributes.new_pass_count ?? 0, + newPassMutedCount: item.attributes.new_pass_muted_count ?? 0, + newManualCount: item.attributes.new_manual_count ?? 0, + newManualMutedCount: item.attributes.new_manual_muted_count ?? 0, + changedFailCount: item.attributes.changed_fail_count ?? 0, + changedFailMutedCount: item.attributes.changed_fail_muted_count ?? 0, + changedPassCount: item.attributes.changed_pass_count ?? 0, + changedPassMutedCount: item.attributes.changed_pass_muted_count ?? 0, + changedManualCount: item.attributes.changed_manual_count ?? 0, + changedManualMutedCount: item.attributes.changed_manual_muted_count ?? 0, mutedCount: item.attributes.muted_count, providers: (item.attributes.impacted_providers || []) as ProviderType[], updatedAt: item.attributes.last_seen_at || "", @@ -95,9 +135,11 @@ interface ProviderInfo { } interface FindingGroupResourceAttributes { + finding_id: string; resource: ResourceInfo; provider: ProviderInfo; status: string; + muted?: boolean; delta?: string | null; severity: string; first_seen_at: string | null; @@ -132,7 +174,7 @@ export function adaptFindingGroupResourcesResponse( return data.map((item) => ({ id: item.id, rowType: FINDINGS_ROW_TYPE.RESOURCE, - findingId: item.id, + findingId: item.attributes.finding_id || item.id, checkId, providerType: (item.attributes.provider?.type || "aws") as ProviderType, providerAlias: item.attributes.provider?.alias || "", @@ -146,7 +188,7 @@ export function adaptFindingGroupResourcesResponse( severity: (item.attributes.severity || "informational") as Severity, status: item.attributes.status, delta: item.attributes.delta || null, - isMuted: item.attributes.status === "MUTED", + isMuted: item.attributes.muted ?? item.attributes.status === "MUTED", mutedReason: item.attributes.muted_reason || undefined, firstSeenAt: item.attributes.first_seen_at, lastSeenAt: item.attributes.last_seen_at, diff --git a/ui/actions/finding-groups/finding-groups.test.ts b/ui/actions/finding-groups/finding-groups.test.ts index 9f4bdc5830..de19cb1567 100644 --- a/ui/actions/finding-groups/finding-groups.test.ts +++ b/ui/actions/finding-groups/finding-groups.test.ts @@ -187,7 +187,9 @@ describe("getFindingGroupResources — Blocker 1: FAIL-first sort", () => { // Then — the URL must contain the composite sort const calledUrl = fetchMock.mock.calls[0][0] as string; const url = new URL(calledUrl); - expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); + expect(url.searchParams.get("sort")).toBe( + "-status,-delta,-severity,-last_seen_at", + ); }); it("should not force filter[status]=FAIL so PASS resources can also be shown", async () => { @@ -223,7 +225,9 @@ describe("getLatestFindingGroupResources — Blocker 1: FAIL-first sort", () => // Then const calledUrl = fetchMock.mock.calls[0][0] as string; const url = new URL(calledUrl); - expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); + expect(url.searchParams.get("sort")).toBe( + "-status,-delta,-severity,-last_seen_at", + ); }); it("should not force filter[status]=FAIL so PASS resources can also be shown", async () => { @@ -265,7 +269,9 @@ describe("getFindingGroupResources — triangulation: params coexist", () => { const url = new URL(calledUrl); expect(url.searchParams.get("page[number]")).toBe("2"); expect(url.searchParams.get("page[size]")).toBe("50"); - expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); + expect(url.searchParams.get("sort")).toBe( + "-status,-delta,-severity,-last_seen_at", + ); expect(url.searchParams.get("filter[status]")).toBeNull(); }); }); @@ -291,7 +297,9 @@ describe("getLatestFindingGroupResources — triangulation: params coexist", () const url = new URL(calledUrl); expect(url.searchParams.get("page[number]")).toBe("3"); expect(url.searchParams.get("page[size]")).toBe("20"); - expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); + expect(url.searchParams.get("sort")).toBe( + "-status,-delta,-severity,-last_seen_at", + ); expect(url.searchParams.get("filter[status]")).toBeNull(); }); }); @@ -360,7 +368,9 @@ describe("getFindingGroupResources — caller filters are preserved", () => { // Then const calledUrl = fetchMock.mock.calls[0][0] as string; const url = new URL(calledUrl); - expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); + expect(url.searchParams.get("sort")).toBe( + "-status,-delta,-severity,-last_seen_at", + ); expect(url.searchParams.get("filter[name__icontains]")).toBe("bucket-prod"); expect(url.searchParams.get("filter[severity__in]")).toBe("high"); }); @@ -426,7 +436,9 @@ describe("getLatestFindingGroupResources — caller filters are preserved", () = // Then const calledUrl = fetchMock.mock.calls[0][0] as string; const url = new URL(calledUrl); - expect(url.searchParams.get("sort")).toBe("-severity,-delta,-last_seen_at"); + expect(url.searchParams.get("sort")).toBe( + "-status,-delta,-severity,-last_seen_at", + ); expect(url.searchParams.get("filter[name__icontains]")).toBe( "instance-prod", ); diff --git a/ui/actions/finding-groups/finding-groups.ts b/ui/actions/finding-groups/finding-groups.ts index b08293c882..e4549d08a9 100644 --- a/ui/actions/finding-groups/finding-groups.ts +++ b/ui/actions/finding-groups/finding-groups.ts @@ -62,7 +62,10 @@ function normalizeFindingGroupResourceFilters( } const DEFAULT_FINDING_GROUPS_SORT = - "-severity,-delta,-fail_count,-last_seen_at"; + "-status,-severity,-delta,-fail_count,-last_seen_at"; + +const DEFAULT_FINDING_GROUP_RESOURCES_SORT = + "-status,-delta,-severity,-last_seen_at"; interface FetchFindingGroupsParams { page?: number; @@ -133,7 +136,7 @@ async function fetchFindingGroupResourcesEndpoint( if (page) url.searchParams.append("page[number]", page.toString()); if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); - url.searchParams.append("sort", "-severity,-delta,-last_seen_at"); + url.searchParams.append("sort", DEFAULT_FINDING_GROUP_RESOURCES_SORT); appendSanitizedProviderFilters(url, normalizedFilters); diff --git a/ui/actions/findings/findings-by-resource.test.ts b/ui/actions/findings/findings-by-resource.test.ts index c637e5f707..4aff44a2cf 100644 --- a/ui/actions/findings/findings-by-resource.test.ts +++ b/ui/actions/findings/findings-by-resource.test.ts @@ -43,7 +43,6 @@ vi.mock("@/actions/finding-groups", () => ({ })); import { - resolveFindingIds, resolveFindingIdsByCheckIds, resolveFindingIdsByVisibleGroupResources, } from "./findings-by-resource"; @@ -142,47 +141,6 @@ describe("resolveFindingIdsByCheckIds", () => { }); }); -describe("resolveFindingIds", () => { - beforeEach(() => { - vi.clearAllMocks(); - vi.stubGlobal("fetch", fetchMock); - getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" }); - }); - - it("should use the dated findings endpoint when date or scan filters are active", async () => { - // Given - fetchMock.mockResolvedValue(new Response("", { status: 200 })); - handleApiResponseMock.mockResolvedValue({ - data: [{ id: "finding-1" }, { id: "finding-2" }], - }); - - // When - const result = await resolveFindingIds({ - checkId: "check-1", - resourceUids: ["resource-1", "resource-2"], - hasDateOrScanFilter: true, - filters: { - "filter[scan__in]": "scan-1", - "filter[inserted_at__gte]": "2026-03-01", - }, - }); - - // Then - expect(result).toEqual(["finding-1", "finding-2"]); - - const calledUrl = new URL(fetchMock.mock.calls[0][0]); - expect(calledUrl.pathname).toBe("/api/v1/findings"); - expect(calledUrl.searchParams.get("filter[check_id]")).toBe("check-1"); - expect(calledUrl.searchParams.get("filter[resource_uid__in]")).toBe( - "resource-1,resource-2", - ); - expect(calledUrl.searchParams.get("filter[scan__in]")).toBe("scan-1"); - expect(calledUrl.searchParams.get("filter[inserted_at__gte]")).toBe( - "2026-03-01", - ); - }); -}); - describe("resolveFindingIdsByVisibleGroupResources", () => { beforeEach(() => { vi.clearAllMocks(); @@ -190,22 +148,18 @@ describe("resolveFindingIdsByVisibleGroupResources", () => { getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" }); }); - it("should resolve finding IDs from the group's visible resource UIDs instead of muting the whole check", async () => { - // Given + it("extracts finding_id directly from group resources without a second resolution round-trip", async () => { + // Given — the group resources endpoint returns finding_id in each resource getLatestFindingGroupResourcesMock .mockResolvedValueOnce({ data: [ { id: "resource-row-1", - attributes: { - resource: { uid: "resource-1" }, - }, + attributes: { finding_id: "finding-1" }, }, { id: "resource-row-2", - attributes: { - resource: { uid: "resource-2" }, - }, + attributes: { finding_id: "finding-2" }, }, ], meta: { pagination: { pages: 2 } }, @@ -214,19 +168,12 @@ describe("resolveFindingIdsByVisibleGroupResources", () => { data: [ { id: "resource-row-3", - attributes: { - resource: { uid: "resource-3" }, - }, + attributes: { finding_id: "finding-3" }, }, ], meta: { pagination: { pages: 2 } }, }); - fetchMock.mockResolvedValue(new Response("", { status: 200 })); - handleApiResponseMock.mockResolvedValue({ - data: [{ id: "finding-1" }, { id: "finding-2" }, { id: "finding-3" }], - }); - // When const result = await resolveFindingIdsByVisibleGroupResources({ checkId: "check-1", @@ -236,8 +183,13 @@ describe("resolveFindingIdsByVisibleGroupResources", () => { resourceSearch: "visible subset", }); - // Then + // Then — finding IDs come directly from the group resources response expect(result).toEqual(["finding-1", "finding-2", "finding-3"]); + + // No second round-trip to /findings/latest + expect(fetchMock).not.toHaveBeenCalled(); + + // Group resources endpoint was paginated with correct filters expect(getLatestFindingGroupResourcesMock).toHaveBeenCalledTimes(2); expect(getLatestFindingGroupResourcesMock).toHaveBeenNthCalledWith(1, { checkId: "check-1", @@ -246,6 +198,8 @@ describe("resolveFindingIdsByVisibleGroupResources", () => { filters: { "filter[provider_type__in]": "aws", "filter[name__icontains]": "visible subset", + "filter[status]": "FAIL", + "filter[muted]": "false", }, }); expect(getLatestFindingGroupResourcesMock).toHaveBeenNthCalledWith(2, { @@ -255,168 +209,56 @@ describe("resolveFindingIdsByVisibleGroupResources", () => { filters: { "filter[provider_type__in]": "aws", "filter[name__icontains]": "visible subset", + "filter[status]": "FAIL", + "filter[muted]": "false", }, }); - - const calledUrl = new URL(fetchMock.mock.calls[0][0]); - expect(calledUrl.pathname).toBe("/api/v1/findings/latest"); - expect(calledUrl.searchParams.get("filter[check_id]")).toBe("check-1"); - expect(calledUrl.searchParams.get("filter[check_id__in]")).toBeNull(); - expect(calledUrl.searchParams.get("filter[resource_uid__in]")).toBe( - "resource-1,resource-2,resource-3", - ); - }); -}); - -// --------------------------------------------------------------------------- -// Blocker 3: Muting a group mutes ALL historical findings, not just FAIL ones -// -// The fix: resolveFindingIds must include filter[status]=FAIL so only active -// (failing) findings are resolved for mute, not historical/passing ones. -// --------------------------------------------------------------------------- - -describe("resolveFindingIds — Blocker 3: only resolve FAIL findings for mute", () => { - beforeEach(() => { - vi.clearAllMocks(); - vi.stubGlobal("fetch", fetchMock); - getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" }); }); - it("should include filter[status]=FAIL in the findings resolution URL for mute", async () => { + it("deduplicates finding IDs across pages", async () => { + // Given — same finding_id appears on both pages + getLatestFindingGroupResourcesMock + .mockResolvedValueOnce({ + data: [ + { id: "r-1", attributes: { finding_id: "finding-1" } }, + { id: "r-2", attributes: { finding_id: "finding-2" } }, + ], + meta: { pagination: { pages: 2 } }, + }) + .mockResolvedValueOnce({ + data: [{ id: "r-3", attributes: { finding_id: "finding-2" } }], + meta: { pagination: { pages: 2 } }, + }); + + // When + const result = await resolveFindingIdsByVisibleGroupResources({ + checkId: "check-1", + }); + + // Then — no duplicates + expect(result).toEqual(["finding-1", "finding-2"]); + expect(fetchMock).not.toHaveBeenCalled(); + }); + + it("uses the dated endpoint when date or scan filters are active", async () => { // Given - fetchMock.mockResolvedValue(new Response("", { status: 200 })); - handleApiResponseMock.mockResolvedValue({ - data: [{ id: "finding-1" }, { id: "finding-2" }], + getFindingGroupResourcesMock.mockResolvedValueOnce({ + data: [{ id: "r-1", attributes: { finding_id: "finding-1" } }], + meta: { pagination: { pages: 1 } }, }); // When - await resolveFindingIds({ + await resolveFindingIdsByVisibleGroupResources({ checkId: "check-1", - resourceUids: ["resource-1", "resource-2"], - }); - - // Then — the URL must filter to only FAIL status findings - const calledUrl = new URL(fetchMock.mock.calls[0][0]); - expect(calledUrl.searchParams.get("filter[status]")).toBe("FAIL"); - }); - - it("should include filter[status]=FAIL even when date or scan filters are active", async () => { - // Given - fetchMock.mockResolvedValue(new Response("", { status: 200 })); - handleApiResponseMock.mockResolvedValue({ - data: [{ id: "finding-1" }], - }); - - // When - await resolveFindingIds({ - checkId: "check-1", - resourceUids: ["resource-1"], hasDateOrScanFilter: true, filters: { - "filter[inserted_at__gte]": "2026-01-01", + "filter[scan__in]": "scan-1", }, }); - // Then - const calledUrl = new URL(fetchMock.mock.calls[0][0]); - expect(calledUrl.pathname).toBe("/api/v1/findings"); - expect(calledUrl.searchParams.get("filter[status]")).toBe("FAIL"); - }); - - it("should override caller filter[status] with FAIL — no duplicate params", async () => { - // Given — caller passes filter[status]=PASS via filters dict - fetchMock.mockResolvedValue(new Response("", { status: 200 })); - handleApiResponseMock.mockResolvedValue({ - data: [{ id: "finding-1" }], - }); - - // When - await resolveFindingIds({ - checkId: "check-1", - resourceUids: ["resource-1"], - filters: { - "filter[status]": "PASS", - }, - }); - - // Then — hardcoded FAIL must win, exactly 1 value - const calledUrl = new URL(fetchMock.mock.calls[0][0] as string); - const statusValues = calledUrl.searchParams.getAll("filter[status]"); - expect(statusValues).toHaveLength(1); - expect(statusValues[0]).toBe("FAIL"); - }); -}); - -// --------------------------------------------------------------------------- -// Fix 4: Unbounded page[size] cap -// -// The bug: createResourceFindingResolutionUrl sets page[size]=resourceUids.length -// with no upper bound guard. The production fix adds Math.min(resourceUids.length, MAX_PAGE_SIZE) -// with MAX_PAGE_SIZE=500 as an explicit defensive cap. -// --------------------------------------------------------------------------- - -describe("resolveFindingIds — Fix 4: page[size] explicit cap at MAX_PAGE_SIZE=500", () => { - beforeEach(() => { - vi.clearAllMocks(); - vi.stubGlobal("fetch", fetchMock); - getAuthHeadersMock.mockResolvedValue({ Authorization: "Bearer token" }); - }); - - it("should use resourceUids.length as page[size] for a small batch (under 500)", async () => { - // Given — 3 resources, well under the cap - fetchMock.mockResolvedValue(new Response("", { status: 200 })); - handleApiResponseMock.mockResolvedValue({ - data: [{ id: "finding-1" }, { id: "finding-2" }, { id: "finding-3" }], - }); - - // When - await resolveFindingIds({ - checkId: "check-1", - resourceUids: ["resource-1", "resource-2", "resource-3"], - }); - - // Then — page[size] should equal the number of resourceUids (3) - const calledUrl = new URL(fetchMock.mock.calls[0][0]); - expect(calledUrl.searchParams.get("page[size]")).toBe("3"); - }); - - it("should cap page[size] at 500 when the chunk has exactly 500 UIDs (boundary value)", async () => { - // Given — exactly 500 unique UIDs (at the cap boundary) - const resourceUids = Array.from({ length: 500 }, (_, i) => `resource-${i}`); - fetchMock.mockResolvedValue(new Response("", { status: 200 })); - handleApiResponseMock.mockResolvedValue({ data: [] }); - - // When - await resolveFindingIds({ - checkId: "check-1", - resourceUids, - }); - - // Then — page[size] must be exactly 500 (not capped lower) - const firstUrl = new URL(fetchMock.mock.calls[0][0] as string); - expect(firstUrl.searchParams.get("page[size]")).toBe("500"); - }); - - it("should cap page[size] at 500 even when a chunk would exceed 500 — Math.min guard in URL builder", async () => { - // Given — 501 UIDs. The chunker splits into [500, 1]. - // The FIRST chunk has 500 UIDs → page[size] should be 500 (Math.min(500, 500)). - // The SECOND chunk has 1 UID → page[size] should be 1 (Math.min(1, 500)). - // This proves the Math.min cap fires correctly on every chunk. - const resourceUids = Array.from({ length: 501 }, (_, i) => `resource-${i}`); - fetchMock.mockResolvedValue(new Response("", { status: 200 })); - handleApiResponseMock.mockResolvedValue({ data: [] }); - - // When - await resolveFindingIds({ - checkId: "check-1", - resourceUids, - }); - - // Then — two fetch calls: one for 500 UIDs, one for 1 UID - expect(fetchMock).toHaveBeenCalledTimes(2); - const firstUrl = new URL(fetchMock.mock.calls[0][0] as string); - const secondUrl = new URL(fetchMock.mock.calls[1][0] as string); - expect(firstUrl.searchParams.get("page[size]")).toBe("500"); - expect(secondUrl.searchParams.get("page[size]")).toBe("1"); + // Then — uses getFindingGroupResources (dated), not getLatestFindingGroupResources + expect(getFindingGroupResourcesMock).toHaveBeenCalledTimes(1); + expect(getLatestFindingGroupResourcesMock).not.toHaveBeenCalled(); + expect(fetchMock).not.toHaveBeenCalled(); }); }); diff --git a/ui/actions/findings/findings-by-resource.ts b/ui/actions/findings/findings-by-resource.ts index 4c69d2e5ef..344c5607a0 100644 --- a/ui/actions/findings/findings-by-resource.ts +++ b/ui/actions/findings/findings-by-resource.ts @@ -14,22 +14,12 @@ const FINDING_IDS_RESOLUTION_CONCURRENCY = 4; const FINDING_GROUP_RESOURCES_RESOLUTION_PAGE_SIZE = 500; const FINDING_FIELDS = "uid"; -/** Explicit upper bound for page[size] in resource-finding resolution requests. */ -const MAX_RESOURCE_FINDING_PAGE_SIZE = 500; - interface ResolveFindingIdsByCheckIdsParams { checkIds: string[]; filters?: Record; hasDateOrScanFilter?: boolean; } -interface ResolveFindingIdsParams { - checkId: string; - resourceUids: string[]; - filters?: Record; - hasDateOrScanFilter?: boolean; -} - interface ResolveFindingIdsByVisibleGroupResourcesParams { checkId: string; filters?: Record; @@ -42,8 +32,8 @@ interface FindingIdsPageResponse { totalPages: number; } -interface FindingGroupResourceUidsPageResponse { - resourceUids: string[]; +interface FindingGroupResourceFindingIdsPageResponse { + findingIds: string[]; totalPages: number; } @@ -100,78 +90,7 @@ async function fetchFindingIdsPage({ }; } -function chunkValues(values: T[], chunkSize: number): T[][] { - const chunks: T[][] = []; - for (let index = 0; index < values.length; index += chunkSize) { - chunks.push(values.slice(index, index + chunkSize)); - } - return chunks; -} - -function createResourceFindingResolutionUrl({ - checkId, - resourceUids, - filters = {}, - hasDateOrScanFilter = false, -}: ResolveFindingIdsParams): URL { - const endpoint = hasDateOrScanFilter ? "findings" : "findings/latest"; - const url = new URL(`${apiBaseUrl}/${endpoint}`); - - url.searchParams.append("filter[check_id]", checkId); - url.searchParams.append("filter[resource_uid__in]", resourceUids.join(",")); - url.searchParams.append("filter[muted]", "false"); - url.searchParams.append( - "page[size]", - Math.min(resourceUids.length, MAX_RESOURCE_FINDING_PAGE_SIZE).toString(), - ); - - appendSanitizedProviderTypeFilters(url, filters); - - // Hardcoded FAIL filter AFTER appendSanitizedProviderTypeFilters — .set() - // guarantees this wins even if the caller passes filter[status] in filters. - url.searchParams.set("filter[status]", "FAIL"); - - return url; -} - -async function fetchFindingIdsForResourceUids({ - headers, - ...params -}: ResolveFindingIdsParams & { - headers: HeadersInit; -}): Promise { - const response = await fetch( - createResourceFindingResolutionUrl(params).toString(), - { - headers, - }, - ); - const data = await handleApiResponse(response); - - if (!data?.data || !Array.isArray(data.data)) { - return []; - } - - return data.data - .map((item: { id?: string }) => item.id) - .filter((id: string | undefined): id is string => Boolean(id)); -} - -function buildFindingGroupResourceFilters({ - filters = {}, - resourceSearch, -}: Pick< - ResolveFindingIdsByVisibleGroupResourcesParams, - "filters" | "resourceSearch" ->): Record { - const nextFilters = { ...filters }; - if (resourceSearch) { - nextFilters["filter[name__icontains]"] = resourceSearch; - } - return nextFilters; -} - -async function fetchFindingGroupResourceUidsPage({ +async function fetchFindingGroupResourceFindingIdsPage({ checkId, filters = {}, hasDateOrScanFilter = false, @@ -179,77 +98,44 @@ async function fetchFindingGroupResourceUidsPage({ resourceSearch, }: ResolveFindingIdsByVisibleGroupResourcesParams & { page: number; -}): Promise { +}): Promise { const fetchFn = hasDateOrScanFilter ? getFindingGroupResources : getLatestFindingGroupResources; + const resolvedFilters: Record = { + ...filters, + "filter[status]": "FAIL", + "filter[muted]": "false", + }; + if (resourceSearch) { + resolvedFilters["filter[name__icontains]"] = resourceSearch; + } + const response = await fetchFn({ checkId, page, pageSize: FINDING_GROUP_RESOURCES_RESOLUTION_PAGE_SIZE, - filters: buildFindingGroupResourceFilters({ filters, resourceSearch }), + filters: resolvedFilters, }); const data = response?.data; if (!data || !Array.isArray(data)) { - return { resourceUids: [], totalPages: 1 }; + return { findingIds: [], totalPages: 1 }; } return { - resourceUids: data + findingIds: data .map( - (item: { attributes?: { resource?: { uid?: string } } }) => - item.attributes?.resource?.uid, + (item: { attributes?: { finding_id?: string } }) => + item.attributes?.finding_id, ) - .filter((uid: string | undefined): uid is string => Boolean(uid)), + .filter((id: string | undefined): id is string => Boolean(id)), totalPages: response?.meta?.pagination?.pages ?? 1, }; } -/** - * Resolves resource UIDs + check ID into actual finding UUIDs. - * Uses /findings/latest (or /findings when date/scan filters are active) - * with check_id and resource_uid__in filters to batch-resolve actual finding IDs. - */ -export const resolveFindingIds = async ({ - checkId, - resourceUids, - filters = {}, - hasDateOrScanFilter = false, -}: ResolveFindingIdsParams): Promise => { - if (resourceUids.length === 0) { - return []; - } - - const headers = await getAuthHeaders({ contentType: false }); - const resourceUidChunks = chunkValues( - Array.from(new Set(resourceUids)), - FINDING_IDS_RESOLUTION_PAGE_SIZE, - ); - - try { - const results = await runWithConcurrencyLimit( - resourceUidChunks, - FINDING_IDS_RESOLUTION_CONCURRENCY, - (resourceUidChunk) => - fetchFindingIdsForResourceUids({ - checkId, - resourceUids: resourceUidChunk, - filters, - hasDateOrScanFilter, - headers, - }), - ); - - return Array.from(new Set(results.flat())); - } catch (error) { - console.error("Error resolving finding IDs:", error); - return []; - } -}; - /** * Resolves check IDs into actual finding UUIDs. * Used at the group level where each row represents a check_id. @@ -305,8 +191,12 @@ export const resolveFindingIdsByCheckIds = async ({ }; /** - * Resolves a finding-group row to the actual findings for the resources + * Resolves a finding-group row to the actual finding UUIDs for the resources * currently visible in that group. + * + * Extracts finding_id directly from the group resources endpoint response, + * filtering server-side by status=FAIL and muted=false. No second resolution + * round-trip to /findings/latest is needed. */ export const resolveFindingIdsByVisibleGroupResources = async ({ checkId, @@ -315,7 +205,7 @@ export const resolveFindingIdsByVisibleGroupResources = async ({ resourceSearch, }: ResolveFindingIdsByVisibleGroupResourcesParams): Promise => { try { - const firstPage = await fetchFindingGroupResourceUidsPage({ + const firstPage = await fetchFindingGroupResourceFindingIdsPage({ checkId, filters, hasDateOrScanFilter, @@ -332,7 +222,7 @@ export const resolveFindingIdsByVisibleGroupResources = async ({ remainingPages, FINDING_IDS_RESOLUTION_CONCURRENCY, (page) => - fetchFindingGroupResourceUidsPage({ + fetchFindingGroupResourceFindingIdsPage({ checkId, filters, hasDateOrScanFilter, @@ -341,19 +231,12 @@ export const resolveFindingIdsByVisibleGroupResources = async ({ }), ); - const resourceUids = Array.from( + return Array.from( new Set([ - ...firstPage.resourceUids, - ...remainingResults.flatMap((result) => result.resourceUids), + ...firstPage.findingIds, + ...remainingResults.flatMap((result) => result.findingIds), ]), ); - - return resolveFindingIds({ - checkId, - resourceUids, - filters, - hasDateOrScanFilter, - }); } catch (error) { console.error( "Error resolving finding IDs from visible group resources:", @@ -381,7 +264,7 @@ export const getLatestFindingsByResourceUid = async ({ url.searchParams.append("filter[resource_uid]", resourceUid); url.searchParams.append("filter[status]", "FAIL"); url.searchParams.append("filter[muted]", "include"); - url.searchParams.append("sort", "-severity,status,-updated_at"); + url.searchParams.append("sort", "-severity,-updated_at"); if (page) url.searchParams.append("page[number]", page.toString()); if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); diff --git a/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx b/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx index f8a1122dfd..d33168efc8 100644 --- a/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx +++ b/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx @@ -7,6 +7,7 @@ import { getComplianceRequirements, } from "@/actions/compliances"; import { getThreatScore } from "@/actions/overview"; +import { getScan } from "@/actions/scans"; import { ClientAccordionWrapper, ComplianceDownloadContainer, @@ -37,7 +38,6 @@ interface ComplianceDetailSearchParams { complianceId: string; version?: string; scanId?: string; - scanData?: string; "filter[region__in]"?: string; "filter[cis_profile_level]"?: string; page?: string; @@ -53,7 +53,7 @@ export default async function ComplianceDetail({ }) { const { compliancetitle } = await params; const resolvedSearchParams = await searchParams; - const { complianceId, version, scanId, scanData } = resolvedSearchParams; + const { complianceId, version, scanId } = resolvedSearchParams; const regionFilter = resolvedSearchParams["filter[region__in]"]; const cisProfileFilter = resolvedSearchParams["filter[cis_profile_level]"]; const logoPath = getComplianceIcon(compliancetitle); @@ -72,21 +72,34 @@ export default async function ComplianceDetail({ : `${formattedTitle}`; let selectedScan: ScanEntity | null = null; + const selectedScanId = scanId || null; - if (scanData) { - selectedScan = JSON.parse(decodeURIComponent(scanData)); - } + const [metadataInfoData, attributesData, selectedScanResponse] = + await Promise.all([ + getComplianceOverviewMetadataInfo({ + filters: { + "filter[scan_id]": selectedScanId, + }, + }), + getComplianceAttributes(complianceId), + selectedScanId ? getScan(selectedScanId) : Promise.resolve(null), + ]); - const selectedScanId = scanId || selectedScan?.id || null; - - const [metadataInfoData, attributesData] = await Promise.all([ - getComplianceOverviewMetadataInfo({ - filters: { - "filter[scan_id]": selectedScanId, + if (selectedScanResponse?.data) { + const scan = selectedScanResponse.data; + selectedScan = { + id: scan.id, + providerInfo: { + provider: scan.providerInfo?.provider || "aws", + alias: scan.providerInfo?.alias, + uid: scan.providerInfo?.uid, }, - }), - getComplianceAttributes(complianceId), - ]); + attributes: { + name: scan.attributes.name, + completed_at: scan.attributes.completed_at, + }, + }; + } const uniqueRegions = metadataInfoData?.data?.attributes?.regions || []; diff --git a/ui/components/compliance/compliance-card.tsx b/ui/components/compliance/compliance-card.tsx index d700821ed6..5d1b0425ba 100644 --- a/ui/components/compliance/compliance-card.tsx +++ b/ui/components/compliance/compliance-card.tsx @@ -32,7 +32,6 @@ export const ComplianceCard: React.FC = ({ scanId, complianceId, id, - selectedScan, }) => { const searchParams = useSearchParams(); const router = useRouter(); @@ -65,17 +64,6 @@ export const ComplianceCard: React.FC = ({ params.set("version", version); params.set("scanId", scanId); - if (selectedScan) { - params.set( - "scanData", - JSON.stringify({ - id: selectedScan.id, - providerInfo: selectedScan.providerInfo, - attributes: selectedScan.attributes, - }), - ); - } - const regionFilter = searchParams.get("filter[region__in]"); if (regionFilter) { params.set("filter[region__in]", regionFilter); diff --git a/ui/components/compliance/threatscore-badge.tsx b/ui/components/compliance/threatscore-badge.tsx index a047f91bdd..feb2aec91b 100644 --- a/ui/components/compliance/threatscore-badge.tsx +++ b/ui/components/compliance/threatscore-badge.tsx @@ -40,7 +40,6 @@ export const ThreatScoreBadge = ({ score, scanId, provider, - selectedScan, sectionScores, }: ThreatScoreBadgeProps) => { const router = useRouter(); @@ -62,17 +61,6 @@ export const ThreatScoreBadge = ({ params.set("version", version); params.set("scanId", scanId); - if (selectedScan) { - params.set( - "scanData", - JSON.stringify({ - id: selectedScan.id, - providerInfo: selectedScan.providerInfo, - attributes: selectedScan.attributes, - }), - ); - } - const regionFilter = searchParams.get("filter[region__in]"); if (regionFilter) { params.set("filter[region__in]", regionFilter); diff --git a/ui/components/findings/findings-filters.tsx b/ui/components/findings/findings-filters.tsx index 526b6240f3..90c59368b0 100644 --- a/ui/components/findings/findings-filters.tsx +++ b/ui/components/findings/findings-filters.tsx @@ -86,7 +86,14 @@ export const FindingsFilters = ({ // Custom filters for the expandable section (removed Provider - now using AccountsSelector) const customFilters = [ - ...filterFindings, + ...filterFindings.map((filter) => ({ + ...filter, + labelFormatter: (value: string) => + getFindingsFilterDisplayValue(`filter[${filter.key}]`, value, { + providers, + scans: scanDetails, + }), + })), { key: FilterType.REGION, labelCheckboxGroup: "Regions", @@ -124,6 +131,11 @@ export const FindingsFilters = ({ labelCheckboxGroup: "Scan ID", values: completedScanIds, valueLabelMapping: scanDetails, + labelFormatter: (value: string) => + getFindingsFilterDisplayValue(`filter[${FilterType.SCAN}]`, value, { + providers, + scans: scanDetails, + }), index: 7, }, ]; diff --git a/ui/components/findings/findings-filters.utils.test.ts b/ui/components/findings/findings-filters.utils.test.ts index 86a3124b0a..dcfc093cfd 100644 --- a/ui/components/findings/findings-filters.utils.test.ts +++ b/ui/components/findings/findings-filters.utils.test.ts @@ -101,6 +101,24 @@ describe("getFindingsFilterDisplayValue", () => { ).toBe("Scan Account"); }); + it("normalizes finding statuses for display", () => { + expect(getFindingsFilterDisplayValue("filter[status__in]", "FAIL")).toBe( + "Fail", + ); + }); + + it("normalizes severities for display", () => { + expect( + getFindingsFilterDisplayValue("filter[severity__in]", "critical"), + ).toBe("Critical"); + }); + + it("formats delta values for display", () => { + expect(getFindingsFilterDisplayValue("filter[delta__in]", "new")).toBe( + "New", + ); + }); + it("falls back to the scan provider uid when the alias is missing", () => { expect( getFindingsFilterDisplayValue("filter[scan__in]", "scan-2", { diff --git a/ui/components/findings/findings-filters.utils.ts b/ui/components/findings/findings-filters.utils.ts index 6cb9c19b28..cae4763a10 100644 --- a/ui/components/findings/findings-filters.utils.ts +++ b/ui/components/findings/findings-filters.utils.ts @@ -9,6 +9,11 @@ interface GetFindingsFilterDisplayValueOptions { scans?: Array<{ [scanId: string]: ScanEntity }>; } +const FINDING_DELTA_DISPLAY_NAMES: Record = { + new: "New", + changed: "Changed", +}; + function getProviderAccountDisplayValue( providerId: string, providers: ProviderProps[], @@ -62,6 +67,11 @@ export function getFindingsFilterDisplayValue( ] ?? formatLabel(value) ); } + if (filterKey === "filter[delta__in]") { + return ( + FINDING_DELTA_DISPLAY_NAMES[value.toLowerCase()] ?? formatLabel(value) + ); + } if (filterKey === "filter[category__in]") { return getCategoryLabel(value); } diff --git a/ui/components/findings/floating-mute-button.test.tsx b/ui/components/findings/floating-mute-button.test.tsx index 0123e2e44e..b953b47b43 100644 --- a/ui/components/findings/floating-mute-button.test.tsx +++ b/ui/components/findings/floating-mute-button.test.tsx @@ -24,6 +24,17 @@ vi.mock("next/navigation", () => ({ import { FloatingMuteButton } from "./floating-mute-button"; +function deferredPromise() { + let resolve!: (value: T) => void; + let reject!: (reason?: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + return { promise, resolve, reject }; +} + // --------------------------------------------------------------------------- // Fix 3: onBeforeOpen rejection resets isResolving // --------------------------------------------------------------------------- @@ -31,7 +42,6 @@ import { FloatingMuteButton } from "./floating-mute-button"; describe("FloatingMuteButton — onBeforeOpen error handling", () => { beforeEach(() => { vi.clearAllMocks(); - vi.spyOn(console, "error").mockImplementation(() => {}); }); it("should reset isResolving (re-enable button) when onBeforeOpen rejects", async () => { @@ -58,11 +68,9 @@ describe("FloatingMuteButton — onBeforeOpen error handling", () => { }); }); - it("should log the error when onBeforeOpen rejects", async () => { + it("should show the preparation error in the modal when onBeforeOpen rejects", async () => { // Given - const error = new Error("Fetch failed"); - const onBeforeOpen = vi.fn().mockRejectedValue(error); - const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const onBeforeOpen = vi.fn().mockRejectedValue(new Error("Fetch failed")); const user = userEvent.setup(); render( @@ -76,9 +84,26 @@ describe("FloatingMuteButton — onBeforeOpen error handling", () => { // When await user.click(screen.getByRole("button")); - // Then — error was logged + // Then await waitFor(() => { - expect(consoleSpy).toHaveBeenCalled(); + const lastCall = ( + MuteFindingsModalMock.mock.calls as unknown as Array< + [ + { + isOpen: boolean; + isPreparing?: boolean; + preparationError?: string | null; + }, + ] + > + ).at(-1); + + expect(lastCall?.[0]).toMatchObject({ + isOpen: true, + isPreparing: false, + preparationError: + "We couldn't prepare this mute action. Please try again.", + }); }); }); @@ -102,10 +127,76 @@ describe("FloatingMuteButton — onBeforeOpen error handling", () => { await waitFor(() => { const lastCall = ( MuteFindingsModalMock.mock.calls as unknown as Array< - [{ isOpen: boolean; findingIds: string[] }] + [ + { + isOpen: boolean; + findingIds: string[]; + isPreparing?: boolean; + }, + ] > ).at(-1); expect(lastCall?.[0]?.isOpen).toBe(true); }); }); + + it("should open the modal immediately in preparing state while IDs are still resolving", async () => { + // Given + const deferred = deferredPromise(); + const onBeforeOpen = vi.fn().mockReturnValue(deferred.promise); + const user = userEvent.setup(); + + render( + , + ); + + // When + await user.click(screen.getByRole("button")); + + // Then + const preparingCall = ( + MuteFindingsModalMock.mock.calls as unknown as Array< + [ + { + isOpen: boolean; + findingIds: string[]; + isPreparing?: boolean; + }, + ] + > + ).at(-1); + + expect(preparingCall?.[0]).toMatchObject({ + isOpen: true, + isPreparing: true, + findingIds: [], + }); + + // And when the IDs resolve + deferred.resolve(["id-1", "id-2"]); + + await waitFor(() => { + const resolvedCall = ( + MuteFindingsModalMock.mock.calls as unknown as Array< + [ + { + isOpen: boolean; + findingIds: string[]; + isPreparing?: boolean; + }, + ] + > + ).at(-1); + + expect(resolvedCall?.[0]).toMatchObject({ + isOpen: true, + isPreparing: false, + findingIds: ["id-1", "id-2"], + }); + }); + }); }); diff --git a/ui/components/findings/floating-mute-button.tsx b/ui/components/findings/floating-mute-button.tsx index f3b7933ce8..b6c779370e 100644 --- a/ui/components/findings/floating-mute-button.tsx +++ b/ui/components/findings/floating-mute-button.tsx @@ -31,22 +31,46 @@ export function FloatingMuteButton({ const [isModalOpen, setIsModalOpen] = useState(false); const [resolvedIds, setResolvedIds] = useState([]); const [isResolving, setIsResolving] = useState(false); + const [isPreparingMuteModal, setIsPreparingMuteModal] = useState(false); + const [mutePreparationError, setMutePreparationError] = useState< + string | null + >(null); + + const handleModalOpenChange = ( + nextOpen: boolean | ((previousOpen: boolean) => boolean), + ) => { + const resolvedOpen = + typeof nextOpen === "function" ? nextOpen(isModalOpen) : nextOpen; + setIsModalOpen(resolvedOpen); + + if (!resolvedOpen) { + setResolvedIds([]); + setIsPreparingMuteModal(false); + setMutePreparationError(null); + } + }; const handleClick = async () => { if (onBeforeOpen) { + setResolvedIds([]); + setMutePreparationError(null); + setIsPreparingMuteModal(true); + setIsModalOpen(true); setIsResolving(true); try { const ids = await onBeforeOpen(); setResolvedIds(ids); - if (ids.length > 0) { - setIsModalOpen(true); - } - } catch (error) { - console.error( - "FloatingMuteButton: failed to resolve finding IDs", - error, + setMutePreparationError( + ids.length === 0 + ? "No findings could be resolved for this selection. Try refreshing the page and trying again." + : null, + ); + } catch { + setMutePreparationError( + "We couldn't prepare this mute action. Please try again.", ); } finally { + setIsPreparingMuteModal(false); setIsResolving(false); } } else { @@ -65,10 +89,12 @@ export function FloatingMuteButton({ <>
    diff --git a/ui/components/findings/mute-findings-modal.tsx b/ui/components/findings/mute-findings-modal.tsx index 4f4e4e2ccb..5026aa5ad5 100644 --- a/ui/components/findings/mute-findings-modal.tsx +++ b/ui/components/findings/mute-findings-modal.tsx @@ -5,7 +5,10 @@ import { Dispatch, SetStateAction, useState, useTransition } from "react"; import { createMuteRule } from "@/actions/mute-rules"; import { MuteRuleActionState } from "@/actions/mute-rules/types"; +import { Button } from "@/components/shadcn"; import { Modal } from "@/components/shadcn/modal"; +import { Skeleton } from "@/components/shadcn/skeleton/skeleton"; +import { Spinner } from "@/components/shadcn/spinner/spinner"; import { useToast } from "@/components/ui"; import { FormButtons } from "@/components/ui/form"; @@ -15,6 +18,8 @@ interface MuteFindingsModalProps { findingIds: string[]; onComplete?: () => void; isBulkOperation?: boolean; + isPreparing?: boolean; + preparationError?: string | null; } export function MuteFindingsModal({ @@ -23,6 +28,8 @@ export function MuteFindingsModal({ findingIds, onComplete, isBulkOperation = false, + isPreparing = false, + preparationError = null, }: MuteFindingsModalProps) { const { toast } = useToast(); const [state, setState] = useState(null); @@ -32,6 +39,12 @@ export function MuteFindingsModal({ onOpenChange(false); }; + const isSubmitDisabled = + isPending || + isPreparing || + findingIds.length === 0 || + Boolean(preparationError); + return ( { e.preventDefault(); + if (isSubmitDisabled) { + return; + } + const formData = new FormData(e.currentTarget); startTransition(() => { @@ -77,52 +94,121 @@ export function MuteFindingsModal({ value={JSON.stringify(findingIds)} /> -
    -

    - You are about to mute{" "} - - {findingIds.length} - {" "} - {findingIds.length === 1 ? "finding" : "findings"}. -

    -

    - Muted findings will be hidden by default but can be shown using - filters. -

    -
    + {isPreparing ? ( + <> +
    +
    + +
    +

    + Preparing findings to mute... +

    +

    + Large finding groups can take a few seconds while we gather + the matching findings. +

    +
    +
    +
    - + -