From 7cb5072bbe993524c57fb49c20e1c505cd9979cf Mon Sep 17 00:00:00 2001 From: johnjaysonlpz Date: Tue, 17 Feb 2026 01:38:59 +0800 Subject: [PATCH 1/3] ci(local): validate tool versions and lock Python test deps --- .ci-local.sh | 86 +++++- .ci-tool-versions.sh | 2 +- python-django/requirements.test.lock | 429 +++++++++++++++++++++++++++ 3 files changed, 511 insertions(+), 6 deletions(-) create mode 100644 python-django/requirements.test.lock diff --git a/.ci-local.sh b/.ci-local.sh index 97d8aee..34c4f04 100755 --- a/.ci-local.sh +++ b/.ci-local.sh @@ -230,7 +230,79 @@ trap 'on_err "$?" "$LINENO" "${BASH_COMMAND:-}" "${FUNCNAME[0]:-main}"' ERR VERSIONS_FILE="${ROOT_DIR}/.ci-tool-versions.sh" ensure_file "$VERSIONS_FILE" -source "$VERSIONS_FILE" + +load_tool_versions() { + local file="$1" + + local allowed_keys_re='^(TRIVY_VERSION|GOIMPORTS_VERSION|GOVULNCHECK_VERSION|GOLANGCI_LINT_VERSION|GO_MODULE|DEPENDENCY_CHECK_MAVEN_VERSION|DEPENDENCY_CHECK_FAIL_CVSS|PIP_AUDIT_VERSION|PIP_TOOLS_VERSION|PIP_MAX_VERSION)$' + local base_value_re='[A-Za-z0-9][A-Za-z0-9._+/:@-]*' + + local unquoted_line_re="^([A-Z][A-Z0-9_]*)=(${base_value_re})$" + local quoted_line_re="^([A-Z][A-Z0-9_]*)=\"(${base_value_re})\"$" + + local semverish_re='^v?[0-9]+(\.[0-9]+){0,3}([.-][0-9A-Za-z][0-9A-Za-z.-]*)?(\+[0-9A-Za-z][0-9A-Za-z.-]*)?$' + local numeric_version_re='^[0-9]+(\.[0-9]+){0,3}$' + local cvss_re='^(10(\.0)?|[0-9](\.[0-9])?)$' + local go_module_re='^[A-Za-z0-9.-]+(/[A-Za-z0-9._-]+)+$' + + declare -A key_value_re=( + ["TRIVY_VERSION"]="$semverish_re" + ["GOIMPORTS_VERSION"]="$semverish_re" + ["GOVULNCHECK_VERSION"]="$semverish_re" + ["GOLANGCI_LINT_VERSION"]="$semverish_re" + ["DEPENDENCY_CHECK_MAVEN_VERSION"]="$semverish_re" + ["PIP_AUDIT_VERSION"]="$semverish_re" + ["PIP_TOOLS_VERSION"]="$semverish_re" + ["PIP_MAX_VERSION"]="$numeric_version_re" + ["DEPENDENCY_CHECK_FAIL_CVSS"]="$cvss_re" + ["GO_MODULE"]="$go_module_re" + ) + + _trim_ws() { + local s="$1" + s="${s#"${s%%[![:space:]]*}"}" + s="${s%"${s##*[![:space:]]}"}" + printf '%s' "$s" + } + + while IFS= read -r raw || [[ -n "$raw" ]]; do + local line key value expected_re + line="${raw%$'\r'}" + line="$(_trim_ws "$line")" + + [[ -z "$line" ]] && continue + [[ "$line" =~ ^# ]] && continue + + key="" + value="" + + if [[ "$line" =~ $unquoted_line_re ]]; then + key="${BASH_REMATCH[1]}" + value="${BASH_REMATCH[2]}" + elif [[ "$line" =~ $quoted_line_re ]]; then + key="${BASH_REMATCH[1]}" + value="${BASH_REMATCH[2]}" + else + die "invalid line in ${file}: must be KEY=value or KEY=\"value\"; no spaces; KEY=[A-Z][A-Z0-9_]*; value charset=${base_value_re}" 2 + fi + + if [[ ! "$key" =~ $allowed_keys_re ]]; then + die "disallowed key in ${file}: ${key} (allowed: ${allowed_keys_re})" 2 + fi + + expected_re="${key_value_re[$key]:-}" + [[ -n "$expected_re" ]] || die "internal config missing per-key validator for: ${key}" 2 + + if [[ ! "$value" =~ $expected_re ]]; then + die "invalid value for ${key} in ${file} (expected: ${expected_re}; got: ${value})" 2 + fi + + printf -v "$key" '%s' "$value" + export "$key" + done < "$file" + } + + load_tool_versions "$VERSIONS_FILE" # ------------------------------------------------------------------------------ # Net helpers @@ -373,7 +445,6 @@ doctor_check_go() { doctor_require_env GOIMPORTS_VERSION || issues=$((issues+1)) doctor_require_env GOVULNCHECK_VERSION || issues=$((issues+1)) doctor_require_env GOLANGCI_LINT_VERSION || issues=$((issues+1)) - doctor_require_env GO_MODULE || issues=$((issues+1)) doctor_need_cmd go || issues=$((issues+1)) doctor_need_cmd curl || issues=$((issues+1)) @@ -414,6 +485,7 @@ doctor_check_python() { doctor_require_file "${ROOT_DIR}/python-django/requirements.txt" || issues=$((issues+1)) doctor_require_file "${ROOT_DIR}/python-django/requirements.test.txt" || issues=$((issues+1)) doctor_require_file "${ROOT_DIR}/python-django/requirements.lock" || issues=$((issues+1)) + doctor_require_file "${ROOT_DIR}/python-django/requirements.test.lock" || issues=$((issues+1)) doctor_require_dir "${ROOT_DIR}/python-django/app" || issues=$((issues+1)) doctor_require_env PIP_MAX_VERSION || issues=$((issues+1)) @@ -426,6 +498,7 @@ doctor_check_python() { "${ROOT_DIR}/python-django/requirements.txt" \ "${ROOT_DIR}/python-django/requirements.test.txt" \ "${ROOT_DIR}/python-django/requirements.lock" \ + "${ROOT_DIR}/python-django/requirements.test.lock" \ >/dev/null || issues=$((issues+1)) printf "%s\n" "$issues" @@ -602,7 +675,6 @@ run_go() { require_env GOIMPORTS_VERSION require_env GOVULNCHECK_VERSION require_env GOLANGCI_LINT_VERSION - require_env GO_MODULE need_cmd go @@ -635,7 +707,11 @@ run_go() { done <<<"$go_dirs" | grep -q . && exit 1 while IFS= read -r d; do - goimports -l -local "${GO_MODULE}" "$d" + if [[ -n "${GO_MODULE:-}" ]]; then + goimports -l -local "${GO_MODULE}" "$d" + else + goimports -l "$d" + fi done <<<"$go_dirs" | grep -q . && exit 1 fi @@ -875,7 +951,7 @@ run_python() { say "Install - runtime + test dependencies (locked)" python -m pip install -U "pip<${PIP_MAX_VERSION}" python -m pip install --require-hashes -r requirements.lock - python -m pip install -r requirements.test.txt + python -m pip install --require-hashes -r requirements.test.lock need_cmd ruff need_cmd mypy diff --git a/.ci-tool-versions.sh b/.ci-tool-versions.sh index cbdb3a2..56936a4 100644 --- a/.ci-tool-versions.sh +++ b/.ci-tool-versions.sh @@ -1,6 +1,6 @@ # shellcheck shell=bash -# This file is sourced by CI/local scripts. Do not execute. +# This file is parsed by CI and may also be sourced; keep it KEY=value only. # Go + Gin CI GOIMPORTS_VERSION=v0.41.0 diff --git a/python-django/requirements.test.lock b/python-django/requirements.test.lock new file mode 100644 index 0000000..8be2bab --- /dev/null +++ b/python-django/requirements.test.lock @@ -0,0 +1,429 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --generate-hashes --no-strip-extras --output-file=requirements.test.lock requirements.test.txt +# +asgiref==3.11.1 \ + --hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \ + --hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133 + # via django +black==26.1.0 \ + --hash=sha256:101540cb2a77c680f4f80e628ae98bd2bd8812fb9d72ade4f8995c5ff019e82c \ + --hash=sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede \ + --hash=sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b \ + --hash=sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9 \ + --hash=sha256:3cee1487a9e4c640dc7467aaa543d6c0097c391dc8ac74eb313f2fbf9d7a7cb5 \ + --hash=sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af \ + --hash=sha256:5e8e75dabb6eb83d064b0db46392b25cabb6e784ea624219736e8985a6b3675d \ + --hash=sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954 \ + --hash=sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0 \ + --hash=sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f \ + --hash=sha256:6f3977a16e347f1b115662be07daa93137259c711e526402aa444d7a88fdc9d4 \ + --hash=sha256:7ed300200918147c963c87700ccf9966dceaefbbb7277450a8d646fc5646bf24 \ + --hash=sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b \ + --hash=sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a \ + --hash=sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115 \ + --hash=sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791 \ + --hash=sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79 \ + --hash=sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304 \ + --hash=sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca \ + --hash=sha256:c5b7713daea9bf943f79f8c3b46f361cc5229e0e604dcef6a8bb6d1c37d9df89 \ + --hash=sha256:ca699710dece84e3ebf6e92ee15f5b8f72870ef984bf944a57a777a48357c168 \ + --hash=sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58 \ + --hash=sha256:d62d14ca31c92adf561ebb2e5f2741bf8dea28aef6deb400d49cca011d186c68 \ + --hash=sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6 \ + --hash=sha256:eb07665d9a907a1a645ee41a0df8a25ffac8ad9c26cdb557b7b88eeeeec934e0 \ + --hash=sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f \ + --hash=sha256:fb1dafbbaa3b1ee8b4550a84425aac8874e5f390200f5502cf3aee4a2acb2f14 + # via -r requirements.test.txt +click==8.3.1 \ + --hash=sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a \ + --hash=sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6 + # via black +coverage[toml]==7.13.4 \ + --hash=sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246 \ + --hash=sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459 \ + --hash=sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129 \ + --hash=sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6 \ + --hash=sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415 \ + --hash=sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf \ + --hash=sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80 \ + --hash=sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11 \ + --hash=sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0 \ + --hash=sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b \ + --hash=sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9 \ + --hash=sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b \ + --hash=sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f \ + --hash=sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505 \ + --hash=sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47 \ + --hash=sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55 \ + --hash=sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def \ + --hash=sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689 \ + --hash=sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012 \ + --hash=sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5 \ + --hash=sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3 \ + --hash=sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95 \ + --hash=sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9 \ + --hash=sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601 \ + --hash=sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997 \ + --hash=sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c \ + --hash=sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac \ + --hash=sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c \ + --hash=sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa \ + --hash=sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750 \ + --hash=sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3 \ + --hash=sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d \ + --hash=sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12 \ + --hash=sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a \ + --hash=sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932 \ + --hash=sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356 \ + --hash=sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92 \ + --hash=sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148 \ + --hash=sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39 \ + --hash=sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634 \ + --hash=sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6 \ + --hash=sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72 \ + --hash=sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98 \ + --hash=sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef \ + --hash=sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3 \ + --hash=sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9 \ + --hash=sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0 \ + --hash=sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a \ + --hash=sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9 \ + --hash=sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552 \ + --hash=sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc \ + --hash=sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f \ + --hash=sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525 \ + --hash=sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940 \ + --hash=sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a \ + --hash=sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23 \ + --hash=sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f \ + --hash=sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc \ + --hash=sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b \ + --hash=sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056 \ + --hash=sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7 \ + --hash=sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb \ + --hash=sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a \ + --hash=sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd \ + --hash=sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea \ + --hash=sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126 \ + --hash=sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299 \ + --hash=sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9 \ + --hash=sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b \ + --hash=sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00 \ + --hash=sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf \ + --hash=sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda \ + --hash=sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2 \ + --hash=sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5 \ + --hash=sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d \ + --hash=sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9 \ + --hash=sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9 \ + --hash=sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b \ + --hash=sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa \ + --hash=sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092 \ + --hash=sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58 \ + --hash=sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea \ + --hash=sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26 \ + --hash=sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea \ + --hash=sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9 \ + --hash=sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053 \ + --hash=sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f \ + --hash=sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0 \ + --hash=sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3 \ + --hash=sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256 \ + --hash=sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a \ + --hash=sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903 \ + --hash=sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91 \ + --hash=sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd \ + --hash=sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505 \ + --hash=sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7 \ + --hash=sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0 \ + --hash=sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2 \ + --hash=sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a \ + --hash=sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71 \ + --hash=sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985 \ + --hash=sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242 \ + --hash=sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d \ + --hash=sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af \ + --hash=sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c \ + --hash=sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0 + # via pytest-cov +django==6.0.2 \ + --hash=sha256:3046a53b0e40d4b676c3b774c73411d7184ae2745fe8ce5e45c0f33d3ddb71a7 \ + --hash=sha256:610dd3b13d15ec3f1e1d257caedd751db8033c5ad8ea0e2d1219a8acf446ecc6 + # via + # django-stubs + # django-stubs-ext +django-stubs[compatible-mypy]==5.2.9 \ + --hash=sha256:2317a7130afdaa76f6ff7f623650d7f3bf1b6c86a60f95840e14e6ec6de1a7cd \ + --hash=sha256:c192257120b08785cfe6f2f1c91f1797aceae8e9daa689c336e52c91e8f6a493 + # via -r requirements.test.txt +django-stubs-ext==5.2.9 \ + --hash=sha256:230c51575551b0165be40177f0f6805f1e3ebf799b835c85f5d64c371ca6cf71 \ + --hash=sha256:6db4054d1580657b979b7d391474719f1a978773e66c7070a5e246cd445a25a9 + # via django-stubs +iniconfig==2.3.0 \ + --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ + --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + # via pytest +librt==0.8.0 \ + --hash=sha256:001e5330093d887b8b9165823eca6c5c4db183fe4edea4fdc0680bbac5f46944 \ + --hash=sha256:015e2dde6e096d27c10238bf9f6492ba6c65822dfb69d2bf74c41a8e88b7ddef \ + --hash=sha256:02a9fe85410cc9bef045e7cb7fd26fdde6669e6d173f99df659aa7f6335961e9 \ + --hash=sha256:0583aef7e9a720dd40f26a2ad5a1bf2ccbb90059dac2b32ac516df232c701db3 \ + --hash=sha256:05a3dd3f116747f7e1a2b475ccdc6fb637fd4987126d109e03013a79d40bf9e6 \ + --hash=sha256:0a33335eb59921e77c9acc05d0e654e4e32e45b014a4d61517897c11591094f8 \ + --hash=sha256:0ce33a9778e294507f3a0e3468eccb6a698b5166df7db85661543eca1cfc5369 \ + --hash=sha256:1566dbb9d1eb0987264c9b9460d212e809ba908d2f4a3999383a84d765f2f3f1 \ + --hash=sha256:17269dd2745dbe8e42475acb28e419ad92dfa38214224b1b01020b8cac70b645 \ + --hash=sha256:178707cda89d910c3b28bf5aa5f69d3d4734e0f6ae102f753ad79edef83a83c7 \ + --hash=sha256:1c25a131013eadd3c600686a0c0333eb2896483cbc7f65baa6a7ee761017aef9 \ + --hash=sha256:1e6811cac1dcb27ca4c74e0ca4a5917a8e06db0d8408d30daee3a41724bfde7a \ + --hash=sha256:1ef3bd856373cf8e7382402731f43bfe978a8613b4039e49e166e1e0dc590216 \ + --hash=sha256:20f73d4fecba969efc15cdefd030e382502d56bb6f1fc66b580cce582836c9fa \ + --hash=sha256:21b14464bee0b604d80a638cf1ee3148d84ca4cc163dcdcecb46060c1b3605e4 \ + --hash=sha256:24a01c13a2a9bdad20997a4443ebe6e329df063d1978bbe2ebbf637878a46d1e \ + --hash=sha256:25b3e667cbfc9000c4740b282df599ebd91dbdcc1aa6785050e4c1d6be5329ab \ + --hash=sha256:25e82d920d4d62ad741592fcf8d0f3bda0e3fc388a184cb7d2f566c681c5f7b9 \ + --hash=sha256:2b37437e7e4ef5e15a297b36ba9e577f73e29564131d86dd75875705e97402b5 \ + --hash=sha256:2ccdd20d9a72c562ffb73098ac411de351b53a6fbb3390903b2d33078ef90447 \ + --hash=sha256:2e0ffe88ebb5962f8fb0ddcbaaff30f1ea06a79501069310e1e030eafb1ad787 \ + --hash=sha256:375eda7acfce1f15f5ed56cfc960669eefa1ec8732e3e9087c3c4c3f2066759c \ + --hash=sha256:3d2720335020219197380ccfa5c895f079ac364b4c429e96952cd6509934d8eb \ + --hash=sha256:3e36a8da17134ffc29373775d88c04832f9ecfab1880470661813e6c7991ef79 \ + --hash=sha256:3e8b77b5f54d0937b26512774916041756c9eb3e66f1031971e626eea49d0bf4 \ + --hash=sha256:41064a0c07b4cc7a81355ccc305cb097d6027002209ffca51306e65ee8293630 \ + --hash=sha256:421765e8c6b18e64d21c8ead315708a56fc24f44075059702e421d164575fdda \ + --hash=sha256:4831c44b8919e75ca0dfb52052897c1ef59fdae19d3589893fbd068f1e41afbf \ + --hash=sha256:48f84830a8f8ad7918afd743fd7c4eb558728bceab7b0e38fd5a5cf78206a556 \ + --hash=sha256:4b705f85311ee76acec5ee70806990a51f0deb519ea0c29c1d1652d79127604d \ + --hash=sha256:4d3e38797eb482485b486898f89415a6ab163bc291476bd95712e42cf4383c05 \ + --hash=sha256:4eb5e06ebcc668677ed6389164f52f13f71737fc8be471101fa8b4ce77baeb0c \ + --hash=sha256:4f6e975377fbc4c9567cb33ea9ab826031b6c7ec0515bfae66a4fb110d40d6da \ + --hash=sha256:4f764b2424cb04524ff7a486b9c391e93f93dc1bd8305b2136d25e582e99aa2f \ + --hash=sha256:507e94f4bec00b2f590fbe55f48cd518a208e2474a3b90a60aa8f29136ddbada \ + --hash=sha256:56901f1eec031396f230db71c59a01d450715cbbef9856bf636726994331195d \ + --hash=sha256:5cb11061a736a9db45e3c1293cfcb1e3caf205912dfa085734ba750f2197ff9a \ + --hash=sha256:5d0f76fc73480d42285c609c0ea74d79856c160fa828ff9aceab574ea4ecfd7b \ + --hash=sha256:667e2513cf69bfd1e1ed9a00d6c736d5108714ec071192afb737987955888a25 \ + --hash=sha256:671a6152edf3b924d98a5ed5e6982ec9cb30894085482acadce0975f031d4c5c \ + --hash=sha256:693697133c3b32aa9b27f040e3691be210e9ac4d905061859a9ed519b1d5a376 \ + --hash=sha256:6a3146c63220d814c4a2c7d6a1eacc8d5c14aed0ff85115c1dfea868080cd18f \ + --hash=sha256:6b6caff69e25d80c269b1952be8493b4d94ef745f438fa619d7931066bdd26de \ + --hash=sha256:6e1af31a710e17891d9adf0dbd9a5fcd94901a3922a96499abdbf7ce658f4e01 \ + --hash=sha256:70defb797c4d5402166787a6b3c66dfb3fa7f93d118c0509ffafa35a392f4258 \ + --hash=sha256:71fc517efc14f75c2f74b1f0a5d5eb4a8e06aa135c34d18eaf3522f4a53cd62d \ + --hash=sha256:75d1a8cab20b2043f03f7aab730551e9e440adc034d776f15f6f8d582b0a5ad4 \ + --hash=sha256:789911e8fa40a2e82f41120c936b1965f3213c67f5a483fc5a41f5839a05dcbb \ + --hash=sha256:7bd68cebf3131bb920d5984f75fe302d758db33264e44b45ad139385662d7bc3 \ + --hash=sha256:7ce0a8cb67e702dcb06342b2aaaa3da9fb0ddc670417879adfa088b44cf7b3b6 \ + --hash=sha256:7f820210e21e3a8bf8fde2ae3c3d10106d4de9ead28cbfdf6d0f0f41f5b12fa1 \ + --hash=sha256:8070aa3368559de81061ef752770d03ca1f5fc9467d4d512d405bd0483bfffe6 \ + --hash=sha256:82e61cd1c563745ad495387c3b65806bfd453badb4adbc019df3389dddee1bf6 \ + --hash=sha256:82fb4602d1b3e303a58bfe6165992b5a78d823ec646445356c332cd5f5bbaa61 \ + --hash=sha256:88c6e75540f1f10f5e0fc5e87b4b6c290f0e90d1db8c6734f670840494764af8 \ + --hash=sha256:8992ca186a1678107b0af3d0c9303d8c7305981b9914989b9788319ed4d89546 \ + --hash=sha256:92249938ab744a5890580d3cb2b22042f0dce71cdaa7c1369823df62bedf7cbc \ + --hash=sha256:95a719a049f0eefaf1952673223cf00d442952273cbd20cf2ed7ec423a0ef58d \ + --hash=sha256:9646178cd794704d722306c2c920c221abbf080fede3ba539d5afdec16c46dad \ + --hash=sha256:9726305d3e53419d27fc8cdfcd3f9571f0ceae22fa6b5ea1b3662c2e538f833e \ + --hash=sha256:995d24caa6bbb34bcdd4a41df98ac6d1af637cfa8975cb0790e47d6623e70e3e \ + --hash=sha256:9ab3a3475a55b89b87ffd7e6665838e8458e0b596c22e0177e0f961434ec474a \ + --hash=sha256:9f09d4884f882baa39a7e36bbf3eae124c4ca2a223efb91e567381d1c55c6b06 \ + --hash=sha256:a512c88900bdb1d448882f5623a0b1ad27ba81a9bd75dacfe17080b72272ca1f \ + --hash=sha256:a905091a13e0884701226860836d0386b88c72ce5c2fdfba6618e14c72be9f25 \ + --hash=sha256:aaadec87f45a3612b6818d1db5fbfe93630669b7ee5d6bdb6427ae08a1aa2141 \ + --hash=sha256:b055bb3abaf69abed25743d8fc1ab691e4f51a912ee0a6f9a6c84f4bbddb283d \ + --hash=sha256:b4bb00bd71b448f16749909b08a0ff16f58b079e2261c2e1000f2bbb2a4f0a45 \ + --hash=sha256:b9aef96d7593584e31ef6ac1eb9775355b0099fee7651fae3a15bc8657b67b52 \ + --hash=sha256:ba581acad5ac8f33e2ff1746e8a57e001b47c6721873121bf8bbcf7ba8bd3aa4 \ + --hash=sha256:ba9d9e60651615bc614be5e21a82cdb7b1769a029369cf4b4d861e4f19686fb6 \ + --hash=sha256:bbebd2bba5c6ae02907df49150e55870fdd7440d727b6192c46b6f754723dde9 \ + --hash=sha256:bd32add59b58fba3439d48d6f36ac695830388e3da3e92e4fc26d2d02670d19c \ + --hash=sha256:bdab762e2c0b48bab76f1a08acb3f4c77afd2123bedac59446aeaaeed3d086cf \ + --hash=sha256:c5512aae4648152abaf4d48b59890503fcbe86e85abc12fb9b096fe948bdd816 \ + --hash=sha256:c6e4c10761ddbc0d67d2f6e2753daf99908db85d8b901729bf2bf5eaa60e0567 \ + --hash=sha256:cb4b3ad543084ed79f186741470b251b9d269cd8b03556f15a8d1a99a64b7de5 \ + --hash=sha256:cb74cdcbc0103fc988e04e5c58b0b31e8e5dd2babb9182b6f9490488eb36324b \ + --hash=sha256:cc3d107f603b5ee7a79b6aa6f166551b99b32fb4a5303c4dfcb4222fc6a0335e \ + --hash=sha256:d920789eca7ef71df7f31fd547ec0d3002e04d77f30ba6881e08a630e7b2c30e \ + --hash=sha256:daae5e955764be8fd70a93e9e5133c75297f8bce1e802e1d3683b98f77e1c5ab \ + --hash=sha256:db63cf3586a24241e89ca1ce0b56baaec9d371a328bd186c529b27c914c9a1ef \ + --hash=sha256:db953b675079884ffda33d1dca7189fb961b6d372153750beb81880384300817 \ + --hash=sha256:de076eaba208d16efb5962f99539867f8e2c73480988cb513fcf1b5dbb0c9dcf \ + --hash=sha256:e79dbc8f57de360f0ed987dc7de7be814b4803ef0e8fc6d3ff86e16798c99935 \ + --hash=sha256:e9a3a38eb4134ad33122a6d575e6324831f930a771d951a15ce232e0237412c2 \ + --hash=sha256:f04ca50e847abc486fa8f4107250566441e693779a5374ba211e96e238f298b9 \ + --hash=sha256:f1178e0de0c271231a660fbef9be6acdfa1d596803464706862bef6644cc1cae \ + --hash=sha256:f4617cef654fca552f00ce5ffdf4f4b68770f18950e4246ce94629b789b92467 \ + --hash=sha256:fa37f99bff354ff191c6bcdffbc9d7cdd4fc37faccfc9be0ef3a4fd5613977da + # via mypy +mypy==1.19.1 \ + --hash=sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd \ + --hash=sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b \ + --hash=sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1 \ + --hash=sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba \ + --hash=sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b \ + --hash=sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045 \ + --hash=sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac \ + --hash=sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6 \ + --hash=sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a \ + --hash=sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24 \ + --hash=sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957 \ + --hash=sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042 \ + --hash=sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e \ + --hash=sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec \ + --hash=sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3 \ + --hash=sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718 \ + --hash=sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f \ + --hash=sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331 \ + --hash=sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1 \ + --hash=sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1 \ + --hash=sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13 \ + --hash=sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67 \ + --hash=sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2 \ + --hash=sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a \ + --hash=sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b \ + --hash=sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8 \ + --hash=sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376 \ + --hash=sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef \ + --hash=sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288 \ + --hash=sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75 \ + --hash=sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74 \ + --hash=sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250 \ + --hash=sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab \ + --hash=sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6 \ + --hash=sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247 \ + --hash=sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925 \ + --hash=sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e \ + --hash=sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e + # via + # -r requirements.test.txt + # django-stubs +mypy-extensions==1.1.0 \ + --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ + --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 + # via + # black + # mypy +packaging==26.0 \ + --hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \ + --hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529 + # via + # black + # pytest +pathspec==1.0.4 \ + --hash=sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645 \ + --hash=sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723 + # via + # black + # mypy +platformdirs==4.9.2 \ + --hash=sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd \ + --hash=sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291 + # via black +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via + # pytest + # pytest-cov +pygments==2.19.2 \ + --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ + --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + # via pytest +pytest==9.0.2 \ + --hash=sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b \ + --hash=sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11 + # via + # -r requirements.test.txt + # pytest-cov + # pytest-django +pytest-cov==7.0.0 \ + --hash=sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1 \ + --hash=sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861 + # via -r requirements.test.txt +pytest-django==4.11.1 \ + --hash=sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10 \ + --hash=sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991 + # via -r requirements.test.txt +pytokens==0.4.1 \ + --hash=sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1 \ + --hash=sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009 \ + --hash=sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083 \ + --hash=sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1 \ + --hash=sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de \ + --hash=sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2 \ + --hash=sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a \ + --hash=sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1 \ + --hash=sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5 \ + --hash=sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a \ + --hash=sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3 \ + --hash=sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db \ + --hash=sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68 \ + --hash=sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037 \ + --hash=sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321 \ + --hash=sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc \ + --hash=sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7 \ + --hash=sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f \ + --hash=sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918 \ + --hash=sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9 \ + --hash=sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c \ + --hash=sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1 \ + --hash=sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1 \ + --hash=sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3 \ + --hash=sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b \ + --hash=sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb \ + --hash=sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1 \ + --hash=sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a \ + --hash=sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4 \ + --hash=sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa \ + --hash=sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78 \ + --hash=sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe \ + --hash=sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9 \ + --hash=sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d \ + --hash=sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975 \ + --hash=sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440 \ + --hash=sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16 \ + --hash=sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc \ + --hash=sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d \ + --hash=sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6 \ + --hash=sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6 \ + --hash=sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324 + # via black +ruff==0.15.0 \ + --hash=sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3 \ + --hash=sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662 \ + --hash=sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621 \ + --hash=sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a \ + --hash=sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3 \ + --hash=sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179 \ + --hash=sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d \ + --hash=sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16 \ + --hash=sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78 \ + --hash=sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e \ + --hash=sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9 \ + --hash=sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455 \ + --hash=sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1 \ + --hash=sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4 \ + --hash=sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a \ + --hash=sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce \ + --hash=sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d \ + --hash=sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18 + # via -r requirements.test.txt +sqlparse==0.5.5 \ + --hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \ + --hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e + # via django +types-pyyaml==6.0.12.20250915 \ + --hash=sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3 \ + --hash=sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6 + # via django-stubs +typing-extensions==4.15.0 \ + --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ + --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 + # via + # django-stubs + # django-stubs-ext + # mypy From 17e8c99f400fd11b1c2490e5172136d041f329b7 Mon Sep 17 00:00:00 2001 From: johnjaysonlpz Date: Tue, 17 Feb 2026 02:16:43 +0800 Subject: [PATCH 2/3] ci(local): validate tool versions and lock Python test deps --- .ci-local.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.ci-local.sh b/.ci-local.sh index 34c4f04..9b0a374 100755 --- a/.ci-local.sh +++ b/.ci-local.sh @@ -1,7 +1,6 @@ #!/usr/bin/env bash # ------------------------------------------------------------------------------ -# .ci-local.sh -# Runs a fixed set of checks that mirror CI intent, without CI event emulation. +# .ci-local.sh - runs a fixed set of checks that mirror CI workflow intent # ------------------------------------------------------------------------------ set -Eeuo pipefail @@ -948,6 +947,11 @@ run_python() { pip-compile --no-strip-extras --generate-hashes --output-file=requirements.lock requirements.txt git diff --exit-code -- requirements.lock + say "Validate - requirements.test.lock matches requirements.test.txt" + test -f requirements.test.txt + pip-compile --no-strip-extras --generate-hashes --output-file=requirements.test.lock requirements.test.txt + git diff --exit-code -- requirements.test.lock + say "Install - runtime + test dependencies (locked)" python -m pip install -U "pip<${PIP_MAX_VERSION}" python -m pip install --require-hashes -r requirements.lock From 5f04b056565bff20d318699f3326989851d3e7a0 Mon Sep 17 00:00:00 2001 From: johnjaysonlpz Date: Tue, 17 Feb 2026 02:18:27 +0800 Subject: [PATCH 3/3] ci: harden workflows --- .github/workflows/_dependency_check.yaml | 25 +-- .github/workflows/_govulncheck.yaml | 29 ++-- .github/workflows/_pip_audit.yaml | 21 +-- .github/workflows/_trivy_image.yaml | 33 ++-- .github/workflows/_trivy_repo.yaml | 28 ++-- .github/workflows/ci.yaml | 188 ++++++++++++++--------- 6 files changed, 187 insertions(+), 137 deletions(-) diff --git a/.github/workflows/_dependency_check.yaml b/.github/workflows/_dependency_check.yaml index 0ec1c21..1b5eb30 100644 --- a/.github/workflows/_dependency_check.yaml +++ b/.github/workflows/_dependency_check.yaml @@ -23,11 +23,12 @@ permissions: jobs: dependency_check: name: Security - OWASP Dependency-Check - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 40 defaults: run: working-directory: ${{ inputs.working_directory }} + shell: bash env: DEPENDENCY_CHECK_MAVEN_VERSION: "" DEPENDENCY_CHECK_FAIL_CVSS: "" @@ -35,7 +36,7 @@ jobs: ODC_CACHE_EPOCH: "unset" steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false @@ -43,25 +44,25 @@ jobs: uses: ./.github/actions/load-tool-versions - name: Validate - tool versions loaded - shell: bash run: | set -Eeuo pipefail : "${DEPENDENCY_CHECK_MAVEN_VERSION:?DEPENDENCY_CHECK_MAVEN_VERSION is empty}" : "${DEPENDENCY_CHECK_FAIL_CVSS:?DEPENDENCY_CHECK_FAIL_CVSS is empty}" - name: Setup - JDK 25 (Temurin) - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: "25" cache: maven - name: Compute cache epoch (weekly) - shell: bash - run: echo "ODC_CACHE_EPOCH=$(date -u +%G-%V)" >> "$GITHUB_ENV" + run: | + set -Eeuo pipefail + echo "ODC_CACHE_EPOCH=$(date -u +%G-%V)" >> "$GITHUB_ENV" - name: Cache - OWASP Dependency-Check data - uses: actions/cache@v5 + uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: ${{ env.ODC_DATA_DIR }} key: dependency-check-${{ runner.os }}-${{ env.DEPENDENCY_CHECK_MAVEN_VERSION }}-${{ env.ODC_CACHE_EPOCH }} @@ -70,10 +71,11 @@ jobs: - name: Security - OWASP Dependency-Check scan id: depcheck - continue-on-error: ${{ !inputs.gate }} + continue-on-error: true env: NVD_API_KEY: ${{ secrets.NVD_API_KEY }} run: | + set -Eeuo pipefail mvn -B -ntp org.owasp:dependency-check-maven:${DEPENDENCY_CHECK_MAVEN_VERSION}:check \ -DfailBuildOnCVSS=${DEPENDENCY_CHECK_FAIL_CVSS} \ -Dformats=HTML,JSON \ @@ -82,7 +84,7 @@ jobs: - name: Artifact - upload Dependency-Check reports if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact_name }} path: | @@ -93,5 +95,6 @@ jobs: - name: Gate - fail if Dependency-Check found issues if: ${{ inputs.gate && steps.depcheck.outcome == 'failure' }} - shell: bash - run: exit 1 + run: | + set -Eeuo pipefail + exit 1 diff --git a/.github/workflows/_govulncheck.yaml b/.github/workflows/_govulncheck.yaml index 9fab8f3..ba76c4b 100644 --- a/.github/workflows/_govulncheck.yaml +++ b/.github/workflows/_govulncheck.yaml @@ -20,16 +20,17 @@ permissions: jobs: govulncheck: name: Security - govulncheck - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 20 defaults: run: working-directory: ${{ inputs.working_directory }} + shell: bash env: GOVULNCHECK_VERSION: "" steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false @@ -37,14 +38,13 @@ jobs: uses: ./.github/actions/load-tool-versions - name: Validate - tool versions loaded - shell: bash run: | set -Eeuo pipefail : "${GOVULNCHECK_VERSION:?GOVULNCHECK_VERSION is empty}" - name: Setup - Go toolchain id: go - uses: actions/setup-go@v6 + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: ${{ inputs.working_directory }}/go.mod cache-dependency-path: ${{ inputs.working_directory }}/go.sum @@ -52,7 +52,7 @@ jobs: - name: Cache - Go tools (govulncheck) id: cache_go_tools - uses: actions/cache@v5 + uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: | ~/go/bin @@ -60,12 +60,13 @@ jobs: - name: Setup - install govulncheck (cache miss) if: steps.cache_go_tools.outputs.cache-hit != 'true' - shell: bash - run: go install "golang.org/x/vuln/cmd/govulncheck@${GOVULNCHECK_VERSION}" + run: | + set -Eeuo pipefail + go install "golang.org/x/vuln/cmd/govulncheck@${GOVULNCHECK_VERSION}" - name: Setup - add Go tools to PATH - shell: bash run: | + set -Eeuo pipefail GOBIN="$(go env GOBIN)" if [[ -z "$GOBIN" ]]; then GOPATH_FIRST="$(go env GOPATH)" @@ -77,15 +78,14 @@ jobs: - name: Security - govulncheck (with tests) id: govuln - continue-on-error: ${{ !inputs.gate }} - shell: bash + continue-on-error: true run: | - set -o pipefail + set -Eeuo pipefail govulncheck -test ./... | tee govulncheck.txt - name: Artifact - upload govulncheck report if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact_name }} path: ${{ inputs.working_directory }}/govulncheck.txt @@ -94,5 +94,6 @@ jobs: - name: Gate - fail if govulncheck found issues if: ${{ inputs.gate && steps.govuln.outcome == 'failure' }} - shell: bash - run: exit 1 + run: | + set -Eeuo pipefail + exit 1 diff --git a/.github/workflows/_pip_audit.yaml b/.github/workflows/_pip_audit.yaml index 08d749e..b9ea4ee 100644 --- a/.github/workflows/_pip_audit.yaml +++ b/.github/workflows/_pip_audit.yaml @@ -20,17 +20,18 @@ permissions: jobs: pip_audit: name: Security - pip-audit - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 20 defaults: run: working-directory: ${{ inputs.working_directory }} + shell: bash env: PIP_AUDIT_VERSION: "" PIP_MAX_VERSION: "" steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false @@ -38,7 +39,6 @@ jobs: uses: ./.github/actions/load-tool-versions - name: Validate - tool versions loaded - shell: bash run: | set -Eeuo pipefail : "${PIP_AUDIT_VERSION:?PIP_AUDIT_VERSION is empty}" @@ -46,7 +46,7 @@ jobs: test -f requirements.lock - name: Setup - Python 3.12 - uses: actions/setup-python@v6 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: "3.12" cache: pip @@ -54,8 +54,8 @@ jobs: ${{ inputs.working_directory }}/requirements.lock - name: Setup - venv + install pip-audit - shell: bash run: | + set -Eeuo pipefail python -m venv .venv-audit . .venv-audit/bin/activate python -m pip install -U "pip<${PIP_MAX_VERSION}" @@ -63,16 +63,16 @@ jobs: - name: Security - pip-audit (locked deps) id: audit - continue-on-error: ${{ !inputs.gate }} - shell: bash + continue-on-error: true run: | + set -Eeuo pipefail . .venv-audit/bin/activate pip-audit -r requirements.lock --format=json --output=pip-audit.json pip-audit -r requirements.lock - name: Artifact - upload pip-audit report if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact_name }} path: ${{ inputs.working_directory }}/pip-audit.json @@ -81,5 +81,6 @@ jobs: - name: Gate - fail if pip-audit found issues if: ${{ inputs.gate && steps.audit.outcome == 'failure' }} - shell: bash - run: exit 1 + run: | + set -Eeuo pipefail + exit 1 diff --git a/.github/workflows/_trivy_image.yaml b/.github/workflows/_trivy_image.yaml index 5e5cee9..e7af215 100644 --- a/.github/workflows/_trivy_image.yaml +++ b/.github/workflows/_trivy_image.yaml @@ -40,17 +40,20 @@ permissions: jobs: trivy_image: name: Security - Trivy (image) + SARIF - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 60 permissions: contents: read security-events: write + defaults: + run: + shell: bash env: TRIVY_VERSION: "" TRIVY_CACHE_EPOCH: "" steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false @@ -58,17 +61,17 @@ jobs: uses: ./.github/actions/load-tool-versions - name: Validate - tool versions loaded - shell: bash run: | set -Eeuo pipefail : "${TRIVY_VERSION:?TRIVY_VERSION is empty (load-tool-versions failed?)}" - name: Compute - Trivy cache epoch (weekly) - shell: bash - run: echo "TRIVY_CACHE_EPOCH=$(date -u +%G-%V)" >> "$GITHUB_ENV" + run: | + set -Eeuo pipefail + echo "TRIVY_CACHE_EPOCH=$(date -u +%G-%V)" >> "$GITHUB_ENV" - name: Cache - Trivy DB/cache - uses: actions/cache@v5 + uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: ${{ github.workspace }}/.cache/trivy key: trivy-${{ runner.os }}-${{ env.TRIVY_VERSION }}-${{ env.TRIVY_CACHE_EPOCH }} @@ -76,11 +79,11 @@ jobs: trivy-${{ runner.os }}-${{ env.TRIVY_VERSION }}- - name: Setup - Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - name: Build - image (local for scan) if: ${{ inputs.mode == 'local' }} - uses: docker/build-push-action@v6 + uses: docker/build-push-action@c382f710d39a5bb4e430307530a720f50c2d3318 # v6.0.0 with: context: ${{ inputs.context }} load: true @@ -97,7 +100,6 @@ jobs: - name: Compute - scan ref id: scanref - shell: bash run: | set -Eeuo pipefail if [[ "${{ inputs.mode }}" == "digest" ]]; then @@ -110,7 +112,7 @@ jobs: - name: Security - Trivy scan (image) (table) id: trivy_img_table continue-on-error: true - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # v0.34.0 with: version: ${{ env.TRIVY_VERSION }} cache-dir: ${{ github.workspace }}/.cache/trivy @@ -126,7 +128,7 @@ jobs: - name: Trivy - scan image -> SARIF if: ${{ inputs.upload_sarif && always() }} - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # v0.34.0 with: version: ${{ env.TRIVY_VERSION }} cache-dir: ${{ github.workspace }}/.cache/trivy @@ -142,14 +144,14 @@ jobs: - name: Upload - SARIF to GitHub Code Scanning (image) if: ${{ inputs.upload_sarif && always() }} - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4 with: sarif_file: trivy-image.sarif category: ${{ inputs.sarif_category }} - name: Artifact - upload Trivy image report (table) if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact_name }} path: trivy-image.txt @@ -158,5 +160,6 @@ jobs: - name: Gate - fail if Trivy image scan found HIGH/CRITICAL if: ${{ inputs.gate_on_findings && steps.trivy_img_table.outcome == 'failure' }} - shell: bash - run: exit 1 + run: | + set -Eeuo pipefail + exit 1 diff --git a/.github/workflows/_trivy_repo.yaml b/.github/workflows/_trivy_repo.yaml index ae5da44..d67059c 100644 --- a/.github/workflows/_trivy_repo.yaml +++ b/.github/workflows/_trivy_repo.yaml @@ -27,17 +27,20 @@ permissions: jobs: trivy_repo: name: Security - Trivy (repo) + SARIF - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 20 permissions: contents: read security-events: write + defaults: + run: + shell: bash env: TRIVY_VERSION: "" TRIVY_CACHE_EPOCH: "" steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false @@ -45,17 +48,17 @@ jobs: uses: ./.github/actions/load-tool-versions - name: Validate - tool versions loaded - shell: bash run: | set -Eeuo pipefail : "${TRIVY_VERSION:?TRIVY_VERSION is empty (load-tool-versions failed?)}" - name: Compute - Trivy cache epoch (weekly) - shell: bash - run: echo "TRIVY_CACHE_EPOCH=$(date -u +%G-%V)" >> "$GITHUB_ENV" + run: | + set -Eeuo pipefail + echo "TRIVY_CACHE_EPOCH=$(date -u +%G-%V)" >> "$GITHUB_ENV" - name: Cache - Trivy DB/cache - uses: actions/cache@v5 + uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: ${{ github.workspace }}/.cache/trivy key: trivy-${{ runner.os }}-${{ env.TRIVY_VERSION }}-${{ env.TRIVY_CACHE_EPOCH }} @@ -65,7 +68,7 @@ jobs: - name: Security - Trivy scan (fs) (table) id: trivy_repo_table continue-on-error: true - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # v0.34.0 with: version: ${{ env.TRIVY_VERSION }} cache-dir: ${{ github.workspace }}/.cache/trivy @@ -81,7 +84,7 @@ jobs: - name: Security - Trivy scan (fs) -> SARIF if: ${{ inputs.upload_sarif && always() }} - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # v0.34.0 with: version: ${{ env.TRIVY_VERSION }} cache-dir: ${{ github.workspace }}/.cache/trivy @@ -106,14 +109,14 @@ jobs: github.event.pull_request.head.repo.full_name == github.repository ) }} - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4 with: sarif_file: trivy-repo.sarif category: ${{ inputs.sarif_category }} - name: Artifact - upload Trivy repo report (table) if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact_name }} path: trivy-repo.txt @@ -122,5 +125,6 @@ jobs: - name: Gate - fail if Trivy repo scan found HIGH/CRITICAL if: ${{ inputs.gate_on_findings && steps.trivy_repo_table.outcome == 'failure' }} - shell: bash - run: exit 1 + run: | + set -Eeuo pipefail + exit 1 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 10389ee..41d0775 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,8 +24,11 @@ env: jobs: changes: name: Changes - detect touched areas - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 5 + defaults: + run: + shell: bash outputs: ci: ${{ steps.final.outputs.ci }} security: ${{ steps.final.outputs.security }} @@ -35,8 +38,8 @@ jobs: steps: - name: Detect - tag release or manual run (force run) id: tag - shell: bash run: | + set -Eeuo pipefail if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]] || [[ "${GITHUB_REF}" == refs/tags/v* ]]; then echo "all=true" >> "$GITHUB_OUTPUT" else @@ -45,13 +48,13 @@ jobs: - name: Notice - force run skips checkout/path filter if: ${{ steps.tag.outputs.all == 'true' }} - shell: bash run: | + set -Eeuo pipefail echo "Force run: skipping checkout + paths-filter; all jobs enabled." - name: Checkout (push/PR) if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && steps.tag.outputs.all != 'true' }} - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 0 persist-credentials: false @@ -59,7 +62,7 @@ jobs: - name: Filter - changed paths id: paths if: ${{ steps.tag.outputs.all != 'true' }} - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0 with: token: ${{ github.token }} filters: | @@ -89,7 +92,6 @@ jobs: - name: Compute - final changed areas outputs id: final - shell: bash env: FORCE_ALL: ${{ steps.tag.outputs.all }} PATHS_CI: ${{ steps.paths.outputs.ci }} @@ -134,8 +136,11 @@ jobs: policy: name: Policy - protect CI control files - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 5 + defaults: + run: + shell: bash steps: - name: Policy - noop (non-PR) if: ${{ github.event_name != 'pull_request' }} @@ -143,14 +148,13 @@ jobs: - name: Checkout (PR) if: ${{ github.event_name == 'pull_request' }} - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 0 persist-credentials: false - name: Block PRs that modify CI control files (fork-safe; deterministic; fail-closed) if: ${{ github.event_name == 'pull_request' }} - shell: bash env: ACTOR: ${{ github.actor }} MAINTAINERS: ${{ env.CI_MAINTAINERS }} @@ -170,7 +174,6 @@ jobs: base_sha="${BASE_SHA}" head_sha="${HEAD_SHA}" - # Fetch exact commits (fork-safe: base repo can fetch these objects) git fetch --no-tags --prune --depth=50 origin "${base_sha}" "${head_sha}" || { echo "ERROR: unable to fetch base/head SHAs (fail-closed)." >&2 exit 2 @@ -203,7 +206,7 @@ jobs: dependency_review: name: Security - dependency review (PR only) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 10 needs: [ policy ] if: ${{ github.event_name == 'pull_request' }} @@ -212,13 +215,14 @@ jobs: pull-requests: read steps: - name: Dependency Review - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@4901385134134e04cec5fbe5ddfe3b2c5bd5d976 # v4.0.0 with: fail-on-severity: moderate trivy_repo: name: Security - Trivy (repo) + SARIF - needs: [ policy ] + needs: [ policy, changes ] + if: startsWith(github.ref, 'refs/tags/v') || needs.changes.outputs.security == 'true' permissions: contents: read security-events: write @@ -232,13 +236,14 @@ jobs: go: name: Go / Gin - quality, tests, security - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 30 needs: [ policy, changes ] if: needs.changes.outputs.golang == 'true' defaults: run: working-directory: golang-gin + shell: bash env: GOIMPORTS_VERSION: "" GOVULNCHECK_VERSION: "" @@ -246,7 +251,7 @@ jobs: GO_MODULE: "" steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false @@ -254,7 +259,6 @@ jobs: uses: ./.github/actions/load-tool-versions - name: Validate - tool versions loaded - shell: bash run: | set -Eeuo pipefail : "${GOIMPORTS_VERSION:?GOIMPORTS_VERSION is empty}" @@ -263,7 +267,7 @@ jobs: - name: Setup - Go toolchain id: go - uses: actions/setup-go@v6 + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version-file: golang-gin/go.mod cache-dependency-path: golang-gin/go.sum @@ -271,7 +275,7 @@ jobs: - name: Cache - Go tools (goimports, govulncheck) id: cache_go_tools - uses: actions/cache@v5 + uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: | ~/go/bin @@ -279,14 +283,14 @@ jobs: - name: Setup - install Go dev tools (goimports, govulncheck) if: steps.cache_go_tools.outputs.cache-hit != 'true' - shell: bash run: | + set -Eeuo pipefail go install "golang.org/x/tools/cmd/goimports@${GOIMPORTS_VERSION}" go install "golang.org/x/vuln/cmd/govulncheck@${GOVULNCHECK_VERSION}" - name: Setup - add Go tools to PATH - shell: bash run: | + set -Eeuo pipefail GOBIN="$(go env GOBIN)" if [[ -z "$GOBIN" ]]; then GOPATH_FIRST="$(go env GOPATH)" @@ -299,11 +303,11 @@ jobs: - name: Validate - go.mod/go.sum are tidy run: | + set -Eeuo pipefail go mod tidy git diff --exit-code -- go.mod go.sum - name: Quality - format/import checks (gofmt, goimports) - shell: bash run: | set -Eeuo pipefail GO_DIRS="$(go list -f '{{.Dir}}' ./...)" @@ -318,17 +322,20 @@ jobs: fi - name: Quality - lint (golangci-lint) - uses: golangci/golangci-lint-action@v7 + uses: golangci/golangci-lint-action@0a35821d5c230e903fcfe077583637dea1b27b47 # v9.0.0 with: version: ${{ env.GOLANGCI_LINT_VERSION }} working-directory: golang-gin args: --config=.golangci.yaml ./... - name: Quality - static analysis (go vet) - run: go vet ./... + run: | + set -Eeuo pipefail + go vet ./... - name: Setup - system deps for race detector (CGO) run: | + set -Eeuo pipefail sudo apt-get update sudo apt-get install -y --no-install-recommends gcc libc6-dev @@ -336,6 +343,7 @@ jobs: env: CGO_ENABLED: "1" run: | + set -Eeuo pipefail go test ./... -race -shuffle=on -count=1 \ -covermode=atomic -coverprofile=coverage.out @@ -343,6 +351,7 @@ jobs: env: GO_COVERAGE_MIN: ${{ env.GO_COVERAGE_MIN }} run: | + set -Eeuo pipefail python3 - <<'PY' import os, re, subprocess, sys out = subprocess.check_output(["go", "tool", "cover", "-func=coverage.out"], text=True) @@ -357,7 +366,7 @@ jobs: PY - name: Artifact - upload coverage profile (coverage.out) - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: go-gin-coverage path: golang-gin/coverage.out @@ -367,12 +376,12 @@ jobs: - name: Security - vulnerability scan (govulncheck) (push + internal PRs) if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) run: | - set -o pipefail + set -Eeuo pipefail govulncheck -test ./... | tee govulncheck.txt - name: Artifact - upload govulncheck report (push + internal PRs) if: (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) && always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: go-gin-govulncheck path: golang-gin/govulncheck.txt @@ -381,13 +390,14 @@ jobs: java: name: Java / Spring Boot - Spotless, SpotBugs, tests, JaCoCo, security - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 40 needs: [ policy, changes ] if: needs.changes.outputs.java == 'true' defaults: run: working-directory: java-springboot + shell: bash env: DEPENDENCY_CHECK_MAVEN_VERSION: "" DEPENDENCY_CHECK_FAIL_CVSS: "" @@ -396,7 +406,7 @@ jobs: ODC_CACHE_EPOCH: "unset" steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false @@ -404,24 +414,25 @@ jobs: uses: ./.github/actions/load-tool-versions - name: Validate - tool versions loaded - shell: bash run: | set -Eeuo pipefail : "${DEPENDENCY_CHECK_MAVEN_VERSION:?DEPENDENCY_CHECK_MAVEN_VERSION is empty}" : "${DEPENDENCY_CHECK_FAIL_CVSS:?DEPENDENCY_CHECK_FAIL_CVSS is empty}" - name: Setup - JDK 25 (Temurin) - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: "25" cache: maven - name: Build - verify (Spotless, SpotBugs, tests, JaCoCo) - run: mvn -B -ntp verify -Djacoco.min.ratio="${JAVA_COVERAGE_MIN_RATIO}" + run: | + set -Eeuo pipefail + mvn -B -ntp verify -Djacoco.min.ratio="${JAVA_COVERAGE_MIN_RATIO}" - name: Artifact - upload JaCoCo report directory - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: java-springboot-jacoco path: java-springboot/target/site/jacoco/ @@ -430,12 +441,13 @@ jobs: - name: Compute cache epoch (weekly) if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) - shell: bash - run: echo "ODC_CACHE_EPOCH=$(date -u +%G-%V)" >> "$GITHUB_ENV" + run: | + set -Eeuo pipefail + echo "ODC_CACHE_EPOCH=$(date -u +%G-%V)" >> "$GITHUB_ENV" - name: Cache - OWASP Dependency-Check data (push + internal PRs) if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) - uses: actions/cache@v5 + uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: ${{ env.ODC_DATA_DIR }} key: dependency-check-${{ runner.os }}-${{ env.DEPENDENCY_CHECK_MAVEN_VERSION }}-${{ env.ODC_CACHE_EPOCH }} @@ -447,6 +459,7 @@ jobs: env: NVD_API_KEY: ${{ secrets.NVD_API_KEY }} run: | + set -Eeuo pipefail mvn -B -ntp org.owasp:dependency-check-maven:${DEPENDENCY_CHECK_MAVEN_VERSION}:check \ -DfailBuildOnCVSS=${DEPENDENCY_CHECK_FAIL_CVSS} \ -Dformats=HTML,JSON \ @@ -455,7 +468,7 @@ jobs: - name: Artifact - upload Dependency-Check reports (push + internal PRs) if: (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) && always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: java-springboot-dependency-check path: | @@ -466,20 +479,21 @@ jobs: python: name: Python / Django - quality, tests, coverage, security - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 30 needs: [ policy, changes ] if: needs.changes.outputs.python == 'true' defaults: run: working-directory: python-django + shell: bash env: PIP_AUDIT_VERSION: "" PIP_TOOLS_VERSION: "" PIP_MAX_VERSION: "" steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false @@ -487,7 +501,6 @@ jobs: uses: ./.github/actions/load-tool-versions - name: Validate - tool versions loaded - shell: bash run: | set -Eeuo pipefail : "${PIP_TOOLS_VERSION:?PIP_TOOLS_VERSION is empty}" @@ -495,57 +508,76 @@ jobs: : "${PIP_AUDIT_VERSION:?PIP_AUDIT_VERSION is empty}" - name: Setup - Python 3.12 - uses: actions/setup-python@v6 + id: py + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: "3.12" cache: pip cache-dependency-path: | python-django/requirements.lock - python-django/requirements.test.txt + python-django/requirements.test.lock - name: Cache - Python venv (.venv-ci) id: cache_venv - uses: actions/cache@v5 + uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: python-django/.venv-ci - key: python-venv-${{ runner.os }}-py312-${{ env.PIP_TOOLS_VERSION }}-${{ env.PIP_MAX_VERSION }}-${{ hashFiles('python-django/requirements.lock', 'python-django/requirements.test.txt') }} + key: python-venv-${{ runner.os }}-${{ runner.arch }}-${{ steps.py.outputs.python-version }}-${{ env.PIP_TOOLS_VERSION }}-${{ env.PIP_MAX_VERSION }}-${{ hashFiles('python-django/requirements.lock', 'python-django/requirements.test.lock') }} restore-keys: | - python-venv-${{ runner.os }}-py312-${{ env.PIP_TOOLS_VERSION }}-${{ env.PIP_MAX_VERSION }}- + python-venv-${{ runner.os }}-${{ runner.arch }}-${{ steps.py.outputs.python-version }}-${{ env.PIP_TOOLS_VERSION }}-${{ env.PIP_MAX_VERSION }}- + + - name: Setup - ensure isolated venv exists + run: | + set -Eeuo pipefail + if [[ ! -x .venv-ci/bin/python ]]; then + python -m venv .venv-ci + fi - - name: Setup - create isolated venv + install pip-tools (cache miss) - if: steps.cache_venv.outputs.cache-hit != 'true' + - name: Setup - ensure pip + pip-tools present run: | - python -m venv .venv-ci + set -Eeuo pipefail . .venv-ci/bin/activate python -m pip install -U "pip<${PIP_MAX_VERSION}" "pip-tools==${PIP_TOOLS_VERSION}" + command -v pip-compile - name: Validate - requirements.lock matches requirements.txt run: | + set -Eeuo pipefail . .venv-ci/bin/activate pip-compile --no-strip-extras --generate-hashes --output-file=requirements.lock requirements.txt git diff --exit-code -- requirements.lock - - name: Install - runtime + test dependencies (locked) (cache miss) - if: steps.cache_venv.outputs.cache-hit != 'true' + - name: Validate - requirements.test.lock matches requirements.test.txt + run: | + set -Eeuo pipefail + . .venv-ci/bin/activate + test -f requirements.test.txt + pip-compile --no-strip-extras --generate-hashes --output-file=requirements.test.lock requirements.test.txt + git diff --exit-code -- requirements.test.lock + + - name: Install - runtime + test dependencies (fully locked) run: | + set -Eeuo pipefail . .venv-ci/bin/activate - python -m pip install -U "pip<${PIP_MAX_VERSION}" python -m pip install --require-hashes -r requirements.lock - python -m pip install -r requirements.test.txt + python -m pip install --require-hashes -r requirements.test.lock - name: Validate - installed packages are consistent (pip check, compileall) run: | + set -Eeuo pipefail . .venv-ci/bin/activate python -m pip check python -m compileall -q app - name: Quality - format check (ruff format) run: | + set -Eeuo pipefail . .venv-ci/bin/activate ruff format --check . - name: Quality - lint (ruff) run: | + set -Eeuo pipefail . .venv-ci/bin/activate ruff check . @@ -553,12 +585,14 @@ jobs: if: github.event_name == 'pull_request' continue-on-error: true run: | + set -Eeuo pipefail . .venv-ci/bin/activate mypy app - name: Quality - typecheck (mypy) (push/tags only, blocking) if: github.event_name == 'push' run: | + set -Eeuo pipefail . .venv-ci/bin/activate mypy app @@ -567,11 +601,12 @@ jobs: DJANGO_SETTINGS_MODULE: django_app.settings PY_COVERAGE_MIN: ${{ env.PY_COVERAGE_MIN }} run: | + set -Eeuo pipefail . .venv-ci/bin/activate pytest --cov --cov-report=xml --cov-fail-under="${PY_COVERAGE_MIN}" - name: Artifact - upload coverage report (coverage.xml) - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: python-django-coverage path: python-django/coverage.xml @@ -581,6 +616,7 @@ jobs: - name: Security - vulnerability scan (pip-audit) (push + internal PRs) if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) run: | + set -Eeuo pipefail . .venv-ci/bin/activate python -m pip install "pip-audit==${PIP_AUDIT_VERSION}" pip-audit -r requirements.lock --format=json --output=pip-audit.json @@ -588,7 +624,7 @@ jobs: - name: Artifact - upload pip-audit report (push + internal PRs) if: (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) && always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: python-django-pip-audit path: python-django/pip-audit.json @@ -597,13 +633,16 @@ jobs: docker_release: name: Docker - build + Trivy scan + push (tags) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 60 needs: [ policy, trivy_repo, go, java, python ] if: startsWith(github.ref, 'refs/tags/v') permissions: contents: read security-events: write + defaults: + run: + shell: bash env: REGISTRY: docker.io DOCKERHUB_NAMESPACE: johnjaysonlopez @@ -623,7 +662,7 @@ jobs: context: ./python-django steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false @@ -631,17 +670,17 @@ jobs: uses: ./.github/actions/load-tool-versions - name: Validate - tool versions loaded - shell: bash run: | set -Eeuo pipefail : "${TRIVY_VERSION:?TRIVY_VERSION is empty (load-tool-versions failed?)}" - name: Compute - Trivy cache epoch (weekly) - shell: bash - run: echo "TRIVY_CACHE_EPOCH=$(date -u +%G-%V)" >> "$GITHUB_ENV" + run: | + set -Eeuo pipefail + echo "TRIVY_CACHE_EPOCH=$(date -u +%G-%V)" >> "$GITHUB_ENV" - name: Cache - Trivy DB/cache - uses: actions/cache@v5 + uses: actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0 with: path: ${{ github.workspace }}/.cache/trivy key: trivy-${{ runner.os }}-${{ env.TRIVY_VERSION }}-${{ env.TRIVY_CACHE_EPOCH }} @@ -650,15 +689,15 @@ jobs: - name: Compute - build metadata (version, build time, test flag) id: vars - shell: bash run: | + set -Eeuo pipefail echo "build_time=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" echo "version_arg=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" echo "run_tests_arg=true" >> "$GITHUB_OUTPUT" - name: Docker - compute tags and OCI labels (metadata-action) id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 with: images: ${{ env.REGISTRY }}/${{ env.DOCKERHUB_NAMESPACE }}/${{ matrix.image }} flavor: | @@ -671,18 +710,18 @@ jobs: type=sha,format=short - name: Auth - Docker Hub login (tags only) - uses: docker/login-action@v3 + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Setup - Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - name: Build - image (push temp tag, capture digest) id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@c382f710d39a5bb4e430307530a720f50c2d3318 # v6.0.0 with: context: ${{ matrix.context }} push: true @@ -703,7 +742,7 @@ jobs: - name: Security - Trivy scan (image by digest) (table) id: trivy_img_table_release continue-on-error: true - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # v0.34.0 with: version: ${{ env.TRIVY_VERSION }} cache-dir: ${{ github.workspace }}/.cache/trivy @@ -719,7 +758,7 @@ jobs: - name: Trivy - scan image -> SARIF if: always() - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # v0.34.0 with: version: ${{ env.TRIVY_VERSION }} cache-dir: ${{ github.workspace }}/.cache/trivy @@ -735,14 +774,14 @@ jobs: - name: Upload - SARIF to GitHub Code Scanning (image) if: always() - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4 with: sarif_file: trivy-image-${{ matrix.image }}.sarif category: trivy-image-release-${{ matrix.image }} - name: Artifact - upload Trivy image report (table) if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: trivy-image-release-${{ matrix.image }} path: trivy-image-${{ matrix.image }}.txt @@ -750,17 +789,16 @@ jobs: retention-days: 7 - name: Gate - record Trivy result - shell: bash run: | + set -Eeuo pipefail if [[ "${{ steps.trivy_img_table_release.outcome }}" == "failure" ]]; then echo "TRIVY_GATE_FAILED=true" >> "$GITHUB_ENV" else echo "TRIVY_GATE_FAILED=false" >> "$GITHUB_ENV" fi - - name: Cleanup - remove temporary ci tag from Docker Hub (best-effort) - if: env.TRIVY_GATE_ON_FINDINGS == 'true' && env.TRIVY_GATE_FAILED == 'true' - shell: bash + - name: Cleanup - remove temporary ci tag from Docker Hub + if: always() && steps.build.outcome == 'success' env: DH_USER: ${{ secrets.DOCKERHUB_USERNAME }} DH_PASS: ${{ secrets.DOCKERHUB_TOKEN }} @@ -809,11 +847,11 @@ jobs: - name: Gate - fail if Trivy image scan found HIGH/CRITICAL if: env.TRIVY_GATE_ON_FINDINGS == 'true' && env.TRIVY_GATE_FAILED == 'true' - shell: bash - run: exit 1 + run: | + set -Eeuo pipefail + exit 1 - name: Promote - apply release tags to scanned digest - shell: bash run: | set -Eeuo pipefail image="${{ env.REGISTRY }}/${{ env.DOCKERHUB_NAMESPACE }}/${{ matrix.image }}"