From 444eaf7437c7f4fa9c2f2d4751b08f61f18e202a Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Sun, 30 Mar 2025 01:13:44 +0100 Subject: [PATCH 01/48] chore: check rules without filename tag --- README.md | 6 +++--- src/crs_linter/cli.py | 6 ++++-- src/crs_linter/linter.py | 42 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6174123..9f20dac 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Second, the script loops over each of the parsed structures. Each iteration cons * **Check rule tags** - only tags listed in `util/APPROVED_TAGS` may be used as tags in rules * to use a new tag on a rule, it **must** first be registered in the util/APPROVED_TAGS file * **Check t:lowercase and (?i) flag** - No combination of t:lowercase and (?i) should appear in the same rule. -* **Check rule has a tag with value `OWASP_CRS`** - Every rule must have a tag with value `OWASP_CRS` +* **Check rule has a tag with value `OWASP_CRS`** - Every rule must have a tag with value `OWASP_CRS`; every non administrative rules must have a tag with value `OWASP_CRS/$filename$` * **Check rule has a `ver` action with correct version** - Every rule must have `ver` action with correct value * script accepts `-v` or `--version` argument if you want to pass it manually * if no `-v` was given, the script tries to extract the version from result of `git describe --tags` @@ -470,7 +470,7 @@ SecRule REQUEST_URI "@rx index.php" \ tag:attack-xss" ``` -Rule 1 does not have `tag:OWASP_CRS` +Rule 1 does not have `tag:OWASP_CRS` nor `t:OWASP_CRS/test11` ``` crs-linter -r examples/test11.conf -t ../APPROVED_TAGS @@ -489,7 +489,7 @@ examples/test11.conf No new tags added. No t:lowercase and (?i) flag used. There are one or more rules without OWASP_CRS tag. - file=examples/test11.conf, line=8, endLine=8, title=tag:OWASP_CRS is missing: rule does not have tag with value 'OWASP_CRS'; rule id: 1 + file=examples/test11.conf, line=8, endLine=8, title=tag:OWASP_CRS is missing: rule does not have tag with value 'OWASP_CRS' nor 'OWASP_CRS/test11'; rule id: 1 There are one or more rules without ver action. file=examples/test11.conf, line=8, endLine=8, title=ver is missing / incorrect: rule does not have 'ver' action; rule id: 1 End of checking parsed rules diff --git a/src/crs_linter/cli.py b/src/crs_linter/cli.py index 1a80170..2f282f2 100755 --- a/src/crs_linter/cli.py +++ b/src/crs_linter/cli.py @@ -7,6 +7,7 @@ import difflib import argparse import re +import os.path from dulwich.contrib.release_robot import get_current_version, get_recent_tags from semver import Version @@ -408,10 +409,11 @@ def main(): if len(c.error_no_crstag) == 0: logger.debug("No rule without OWASP_CRS tag.") else: + filenametag = c.gen_crs_file_tag() logger.error( - "There are one or more rules without OWASP_CRS tag", + "There are one or more rules without OWASP_CRS or %s tag" % (filenametag), file=f, - title="tag:OWASP_CRS is missing", + title="'tag:OWASP_CRS' or 'tag:OWASP_CRS/%s' is missing" % (filenametag) ) ### check for ver action diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index 1afa91a..1b06164 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -5,7 +5,7 @@ import re import subprocess import logging - +import os.path def parse_config(text): try: @@ -120,6 +120,8 @@ def __init__(self, data, filename=None, txvars={}): self.error_tx_N_without_capture_action = ( [] ) # list of rules which uses TX.N without previous 'capture' + # regex to produce tag from filename: + self.re_fname = re.compile(r"(REQUEST|RESPONSE)\-\d{3}\-") def is_error(self): """Returns True if any error is found""" @@ -761,13 +763,25 @@ def check_lowercase_ignorecase(self): } ) + def gen_crs_file_tag(self): + """ + generate tag from filename + """ + filename = self.re_fname.sub("", os.path.basename(self.filename.replace(".conf", ""))) + filename = filename.replace("APPLICATION-", "") + return "/".join(["OWASP_CRS", filename]) + def check_crs_tag(self): """ - check that every rule has a `tag:'OWASP_CRS'` action + check that every rule has a `tag:'OWASP_CRS'` and `tag:'$FILENAME$'` action """ + filenametag = self.gen_crs_file_tag() chained = False ruleid = 0 has_crs = False + has_crs_fname = False + tagcnt = 0 # counter to help check + crstagnr = 0 # hold the position of OWASP_CRS tag for d in self.data: if "actions" in d: chainlevel = 0 @@ -775,6 +789,7 @@ def check_crs_tag(self): if not chained: ruleid = 0 has_crs = False + has_crs_fname = False chainlevel = 0 else: chained = False @@ -785,9 +800,14 @@ def check_crs_tag(self): chained = True chainlevel += 1 if a["act_name"] == "tag": + tagcnt += 1 if chainlevel == 0: if a["act_arg"] == "OWASP_CRS": has_crs = True + crstagnr = tagcnt + if a['act_arg'] == filenametag: + if tagcnt == crstagnr + 1: + has_crs_fname = True if ruleid > 0 and not has_crs: self.error_no_crstag.append( { @@ -797,6 +817,24 @@ def check_crs_tag(self): "message": f"rule does not have tag with value 'OWASP_CRS'; rule id: {ruleid}", } ) + # see the exclusion list of files which does not require the filename tag + if filenametag not in ["OWASP_CRS/crs-setup.example", \ + "OWASP_CRS/INITIALIZATION", \ + "OWASP_CRS/BLOCKING-EVALUATION", \ + "OWASP_CRS/COMMON-EXCEPTIONS", \ + "OWASP_CRS/CORRELATION"]: + # check if wether the rule is admin rule or not + is_admin_rule = True if (ruleid % 1000 < 100) else False + # admin rules does not need filename tags + if not is_admin_rule and not has_crs_fname: + self.error_no_crstag.append( + { + "ruleid": ruleid, + "line": a["lineno"], + "endLine": a["lineno"], + "message": f"rule does not have tag with value '{filenametag}'; rule id: {ruleid}", + } + ) def check_ver_action(self, version): """ From a0ee46b930edfdac41f2e8ac5cf5fd7856b9c3e3 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Sun, 30 Mar 2025 01:22:17 +0100 Subject: [PATCH 02/48] Added pseudo filename to pytest test case --- tests/test_linter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_linter.py b/tests/test_linter.py index 9fe548a..318f682 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -236,10 +236,11 @@ def test_check_crs_tag(): deny,\ t:none,\ nolog,\ - tag:OWASP_CRS" + tag:OWASP_CRS",\ + tag:OWASP_CRS/CHECK-TAG" """ p = parse_config(t) - c = Check(p) + c = Check(p, "REQUEST-900-CHECK-TAG.conf") c.check_crs_tag() assert len(c.error_no_crstag) == 0 From 8443b3111d97640aaf321328cbc931f391f34ab8 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Sun, 30 Mar 2025 01:23:58 +0100 Subject: [PATCH 03/48] Typo fix --- tests/test_linter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_linter.py b/tests/test_linter.py index 318f682..0f503f8 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -236,7 +236,7 @@ def test_check_crs_tag(): deny,\ t:none,\ nolog,\ - tag:OWASP_CRS",\ + tag:OWASP_CRS,\ tag:OWASP_CRS/CHECK-TAG" """ p = parse_config(t) From 0be48c263965cc08596d7cd274ef580e63b1c6f5 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Sun, 30 Mar 2025 01:53:14 +0100 Subject: [PATCH 04/48] Fix regression tests cases --- src/crs_linter/linter.py | 2 +- tests/test_linter.py | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index 1b06164..384cb51 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -806,7 +806,7 @@ def check_crs_tag(self): has_crs = True crstagnr = tagcnt if a['act_arg'] == filenametag: - if tagcnt == crstagnr + 1: + if crstagnr == 0 or tagcnt == crstagnr + 1: has_crs_fname = True if ruleid > 0 and not has_crs: self.error_no_crstag.append( diff --git a/tests/test_linter.py b/tests/test_linter.py index 0f503f8..1a37bbf 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -240,7 +240,8 @@ def test_check_crs_tag(): tag:OWASP_CRS/CHECK-TAG" """ p = parse_config(t) - c = Check(p, "REQUEST-900-CHECK-TAG.conf") + c = Check(p, filename = "REQUEST-900-CHECK-TAG.conf") + print(c.filename) c.check_crs_tag() assert len(c.error_no_crstag) == 0 @@ -257,11 +258,44 @@ def test_check_crs_tag_fail(): tag:attack-xss" """ p = parse_config(t) - c = Check(p) + c = Check(p, filename = "REQUEST-900-CHECK-TAG.conf") + c.check_crs_tag() + + assert len(c.error_no_crstag) == 1 + +def test_check_crs_tag_fail2(): + t = """ +SecRule REQUEST_URI "@rx index.php" \ + "id:911200,\ + phase:1,\ + deny,\ + t:none,\ + nolog,\ + tag:attack-xss,\ + tag:OWASP_CRS" + """ + p = parse_config(t) + c = Check(p, filename = "REQUEST-900-CHECK-TAG.conf") c.check_crs_tag() assert len(c.error_no_crstag) == 1 +def test_check_crs_tag_fail3(): + t = """ +SecRule REQUEST_URI "@rx index.php" \ + "id:911200,\ + phase:1,\ + deny,\ + t:none,\ + nolog,\ + tag:attack-xss,\ + tag:OWASP_CRS/CHECK-TAG" + """ + p = parse_config(t) + c = Check(p, filename = "REQUEST-900-CHECK-TAG.conf") + c.check_crs_tag() + + assert len(c.error_no_crstag) == 1 def test_check_ver_action(crsversion): t = """ From 1431ab7c648bdfb564b82fe6ea6256d9065418a1 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Mon, 31 Mar 2025 14:25:29 +0200 Subject: [PATCH 05/48] fix: use file's basename instead of passed argument --- src/crs_linter/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crs_linter/cli.py b/src/crs_linter/cli.py index 1a80170..96e704f 100755 --- a/src/crs_linter/cli.py +++ b/src/crs_linter/cli.py @@ -7,6 +7,7 @@ import difflib import argparse import re +import os.path from dulwich.contrib.release_robot import get_current_version, get_recent_tags from semver import Version @@ -136,7 +137,7 @@ def check_indentation(filename, content): try: with open(filename, "r") as fp: from_lines = fp.readlines() - if filename.startswith("crs-setup.conf.example"): + if os.path.basename(filename).startswith("crs-setup.conf.example"): from_lines = remove_comments("".join(from_lines)).split("\n") from_lines = [l + "\n" for l in from_lines] except: From 44fef714c4e02e3b9c336cf63470cd696e71cff3 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Mon, 31 Mar 2025 18:10:26 +0200 Subject: [PATCH 06/48] chore: bump CRS version to 4.13.0 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d38bb48..8ea6e1d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: fail-fast: true matrix: # renovate: datasource=github-releases depName=coreruleset/coreruleset - crs-version: ['4.11.0'] + crs-version: ['4.13.0'] python-version: ['3.9', '3.10', '3.11', '3.12'] steps: From 5b8100075db583f2db55277a74ed0bed1720e97a Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 1 Apr 2025 19:07:54 +0200 Subject: [PATCH 07/48] Update src/crs_linter/cli.py Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- src/crs_linter/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crs_linter/cli.py b/src/crs_linter/cli.py index 2f282f2..6eaddf5 100755 --- a/src/crs_linter/cli.py +++ b/src/crs_linter/cli.py @@ -411,7 +411,7 @@ def main(): else: filenametag = c.gen_crs_file_tag() logger.error( - "There are one or more rules without OWASP_CRS or %s tag" % (filenametag), + f"There are one or more rules without OWASP_CRS or {filenametag} tag", file=f, title="'tag:OWASP_CRS' or 'tag:OWASP_CRS/%s' is missing" % (filenametag) ) From 3fade8e1b5f6c1e49ac4d751c3467afe7a144db2 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 1 Apr 2025 19:08:04 +0200 Subject: [PATCH 08/48] Update src/crs_linter/cli.py Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- src/crs_linter/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crs_linter/cli.py b/src/crs_linter/cli.py index 6eaddf5..34597f7 100755 --- a/src/crs_linter/cli.py +++ b/src/crs_linter/cli.py @@ -413,7 +413,7 @@ def main(): logger.error( f"There are one or more rules without OWASP_CRS or {filenametag} tag", file=f, - title="'tag:OWASP_CRS' or 'tag:OWASP_CRS/%s' is missing" % (filenametag) + title=f"'tag:OWASP_CRS' or 'tag:OWASP_CRS/{filenametag}' is missing" ) ### check for ver action From d822db62f35c28a38f3db273ec119a5b0e5b80ef Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 1 Apr 2025 19:12:21 +0200 Subject: [PATCH 09/48] Update src/crs_linter/linter.py Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- src/crs_linter/linter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index 384cb51..54f8f5d 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -767,7 +767,7 @@ def gen_crs_file_tag(self): """ generate tag from filename """ - filename = self.re_fname.sub("", os.path.basename(self.filename.replace(".conf", ""))) + filename = self.re_fname.sub("", os.path.basename(os.path.splitext(self.filename)[0])) filename = filename.replace("APPLICATION-", "") return "/".join(["OWASP_CRS", filename]) From e0a8035be5bd6265274d1758418d79ba37c3f2a4 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 1 Apr 2025 19:14:06 +0200 Subject: [PATCH 10/48] Update src/crs_linter/linter.py Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- src/crs_linter/linter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index 54f8f5d..7211a06 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -823,7 +823,7 @@ def check_crs_tag(self): "OWASP_CRS/BLOCKING-EVALUATION", \ "OWASP_CRS/COMMON-EXCEPTIONS", \ "OWASP_CRS/CORRELATION"]: - # check if wether the rule is admin rule or not + # check wether the rule is an administrative rule is_admin_rule = True if (ruleid % 1000 < 100) else False # admin rules does not need filename tags if not is_admin_rule and not has_crs_fname: From df2c23bacd49207381f46629d942f941ada62432 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 1 Apr 2025 19:14:17 +0200 Subject: [PATCH 11/48] Update src/crs_linter/linter.py Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- src/crs_linter/linter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index 7211a06..8f4f468 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -825,7 +825,7 @@ def check_crs_tag(self): "OWASP_CRS/CORRELATION"]: # check wether the rule is an administrative rule is_admin_rule = True if (ruleid % 1000 < 100) else False - # admin rules does not need filename tags + # admin rules do not need filename tags if not is_admin_rule and not has_crs_fname: self.error_no_crstag.append( { From e7c7be385435d5a5aa1dce98631e3fab2e12db38 Mon Sep 17 00:00:00 2001 From: Max Leske <250711+theseion@users.noreply.github.com> Date: Sun, 30 Mar 2025 09:59:55 +0200 Subject: [PATCH 12/48] chore: update version to 0.1.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bc35acf..dc61933 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crs-linter" -version = "0.1.0" +version = "0.1.1" description = "CRS linter" authors = [ {name = "Ervin Hegedus", email = "airween@gmail.com"} From 254f1ae80bba8d80b54eb1aa42c7d89f7110bef0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 05:57:29 +0000 Subject: [PATCH 13/48] chore(deps): update astral-sh/setup-uv digest to 0c5e2b8 in .github/workflows/test.yml (#29) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pypi-release.yml | 2 +- .github/workflows/pypi-test-release.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 87917ca..55f6032 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -25,7 +25,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@22695119d769bdb6f7032ad67b9bca0ef8c4a174 # v5 + uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 with: version: "0.6.1" diff --git a/.github/workflows/pypi-test-release.yml b/.github/workflows/pypi-test-release.yml index 6232585..320a690 100644 --- a/.github/workflows/pypi-test-release.yml +++ b/.github/workflows/pypi-test-release.yml @@ -18,7 +18,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@22695119d769bdb6f7032ad67b9bca0ef8c4a174 # v5 + uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 with: version: "0.6.1" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8ea6e1d..5a1b978 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install uv - uses: astral-sh/setup-uv@22695119d769bdb6f7032ad67b9bca0ef8c4a174 # v5 + uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 with: enable-cache: true From 139ccf4259d06024f1c311dabec43217fe0716cb Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Mon, 31 Mar 2025 14:25:29 +0200 Subject: [PATCH 14/48] fix: use file's basename instead of passed argument --- src/crs_linter/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crs_linter/cli.py b/src/crs_linter/cli.py index 34597f7..9827001 100755 --- a/src/crs_linter/cli.py +++ b/src/crs_linter/cli.py @@ -137,7 +137,7 @@ def check_indentation(filename, content): try: with open(filename, "r") as fp: from_lines = fp.readlines() - if filename.startswith("crs-setup.conf.example"): + if os.path.basename(filename).startswith("crs-setup.conf.example"): from_lines = remove_comments("".join(from_lines)).split("\n") from_lines = [l + "\n" for l in from_lines] except: From f2ca18388c078782a4058718c29b14477d6d8912 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 1 Apr 2025 19:26:55 +0200 Subject: [PATCH 15/48] pick up suggestions; fix 'crs-setup.conf' filename --- src/crs_linter/linter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index 8f4f468..e0871ba 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -818,7 +818,7 @@ def check_crs_tag(self): } ) # see the exclusion list of files which does not require the filename tag - if filenametag not in ["OWASP_CRS/crs-setup.example", \ + if filenametag not in ["OWASP_CRS/crs-setup.conf", \ "OWASP_CRS/INITIALIZATION", \ "OWASP_CRS/BLOCKING-EVALUATION", \ "OWASP_CRS/COMMON-EXCEPTIONS", \ @@ -826,6 +826,7 @@ def check_crs_tag(self): # check wether the rule is an administrative rule is_admin_rule = True if (ruleid % 1000 < 100) else False # admin rules do not need filename tags + if not is_admin_rule and not has_crs_fname: self.error_no_crstag.append( { From 6ebbc98b60e735da7b3f072d5ea165d83d015e78 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 03:50:47 +0000 Subject: [PATCH 16/48] chore(deps): update github/codeql-action digest to 45775bd in .github/workflows/codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3980c52..e07032f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,7 +37,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3 + uses: github/codeql-action/init@45775bd8235c68ba998cffa5171334d58593da47 # v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -48,4 +48,4 @@ jobs: # queries: security-extended,security-and-quality - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3 + uses: github/codeql-action/analyze@45775bd8235c68ba998cffa5171334d58593da47 # v3 From 75b1fba4d52e2314f186f707b2c7310d34674559 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 06:41:14 +0000 Subject: [PATCH 17/48] chore(deps): update astral-sh/setup-uv digest to d4b2f3b in .github/workflows/test.yml (#32) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pypi-release.yml | 2 +- .github/workflows/pypi-test-release.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 55f6032..1ad3118 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -25,7 +25,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 + uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 with: version: "0.6.1" diff --git a/.github/workflows/pypi-test-release.yml b/.github/workflows/pypi-test-release.yml index 320a690..9f9f399 100644 --- a/.github/workflows/pypi-test-release.yml +++ b/.github/workflows/pypi-test-release.yml @@ -18,7 +18,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 + uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 with: version: "0.6.1" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df683b5..40eadc2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install uv - uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 + uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 with: enable-cache: true From 67590677a2ddeaf6b162ec85a451ac2c184636c2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 03:50:47 +0000 Subject: [PATCH 18/48] chore(deps): update github/codeql-action digest to 45775bd in .github/workflows/codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3980c52..e07032f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,7 +37,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3 + uses: github/codeql-action/init@45775bd8235c68ba998cffa5171334d58593da47 # v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -48,4 +48,4 @@ jobs: # queries: security-extended,security-and-quality - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3 + uses: github/codeql-action/analyze@45775bd8235c68ba998cffa5171334d58593da47 # v3 From 35dc56adc1b7438e93a82f3c2194582d71a967a9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 06:41:14 +0000 Subject: [PATCH 19/48] chore(deps): update astral-sh/setup-uv digest to d4b2f3b in .github/workflows/test.yml (#32) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pypi-release.yml | 2 +- .github/workflows/pypi-test-release.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 55f6032..1ad3118 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -25,7 +25,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 + uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 with: version: "0.6.1" diff --git a/.github/workflows/pypi-test-release.yml b/.github/workflows/pypi-test-release.yml index 320a690..9f9f399 100644 --- a/.github/workflows/pypi-test-release.yml +++ b/.github/workflows/pypi-test-release.yml @@ -18,7 +18,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 + uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 with: version: "0.6.1" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5a1b978..c543ea3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install uv - uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5 + uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 with: enable-cache: true From e388a117ac426b51a19a518781f35a38374e9581 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Sun, 30 Mar 2025 01:13:44 +0100 Subject: [PATCH 20/48] Merge modifications from origin branch --- FILENAME_EXCLUSIONS | 6 ++++++ README.md | 20 ++++++++++++++++++-- src/crs_linter/cli.py | 25 +++++++++++++++++++------ src/crs_linter/linter.py | 39 ++++++++++++++++++++++++++++++--------- 4 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 FILENAME_EXCLUSIONS diff --git a/FILENAME_EXCLUSIONS b/FILENAME_EXCLUSIONS new file mode 100644 index 0000000..ff877e9 --- /dev/null +++ b/FILENAME_EXCLUSIONS @@ -0,0 +1,6 @@ +crs-setup.example +REQUEST-901-INITIALIZATION.conf +REQUEST-905-COMMON-EXCEPTIONS.conf +REQUEST-949-BLOCKING-EVALUATION.conf +RESPONSE-959-BLOCKING-EVALUATION.conf +RESPONSE-980-CORRELATION.conf diff --git a/README.md b/README.md index 9f20dac..1bf7438 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,23 @@ pip3 install crs-linter ## How does it work -The script expects an argument at least - this would be a single file or a file list, eg: `/path/to/coreruleset/*.conf`. +The script expects multiple arguments to work correctly. For the complete list of possible arguments, please run the script without any argument. You will get some similar output: + +```bash +usage: crs-linter [-h] [-o {native,github}] -d DIRECTORY [--debug] -r CRS_RULES -t TAGSLIST [-v VERSION] +crs-linter: error: the following arguments are required: -d/--directory, -r/--rules, -t/--tags-list +``` + +#### Arguments review + +* `-h` - gives help +* `-o` - output format, van be `native` (default) or `github`; Note, that `github` format follows [GH](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-a-notice-message) suggestion +* `-d` - directory path to CRS git repository. This is required if you don't add the version. +* `--debug` - show debug information +* `-r` - CRS rules file to check, can be used multiple times, eg `-r ../path/to/crs-setup.conf -r "../path/to/rules/*.conf"` +* `-t` - path to file which contains list of approved tags; only tags allowed at rules which are listed in this file +* `-v` - CRS version, optional +* `-f` - path to the file containing the list of files that do not need to be checked for filename tags, optional First, an attempt is made to parse each file specified on the command line. This is a "pre-check", and runs on all files before the other tests. * **Parsing check** - try to parse the structure, this is a syntax check @@ -44,7 +60,7 @@ Second, the script loops over each of the parsed structures. Each iteration cons * **Check rule tags** - only tags listed in `util/APPROVED_TAGS` may be used as tags in rules * to use a new tag on a rule, it **must** first be registered in the util/APPROVED_TAGS file * **Check t:lowercase and (?i) flag** - No combination of t:lowercase and (?i) should appear in the same rule. -* **Check rule has a tag with value `OWASP_CRS`** - Every rule must have a tag with value `OWASP_CRS`; every non administrative rules must have a tag with value `OWASP_CRS/$filename$` +* **Check rule has a tag with value `OWASP_CRS`** - Every rule must have a tag with value `OWASP_CRS`; every non administrative rules must have a tag with value `OWASP_CRS/$filename$`. You can pass a file with list of exclusions of files which don't need these tags. `crs_linter` directory contains an example, see `FILENAME_EXCLUSIONS`. * **Check rule has a `ver` action with correct version** - Every rule must have `ver` action with correct value * script accepts `-v` or `--version` argument if you want to pass it manually * if no `-v` was given, the script tries to extract the version from result of `git describe --tags` diff --git a/src/crs_linter/cli.py b/src/crs_linter/cli.py index 9827001..48f3e53 100755 --- a/src/crs_linter/cli.py +++ b/src/crs_linter/cli.py @@ -103,17 +103,17 @@ def generate_version_string(directory): return f"OWASP_CRS/{version}" -def get_tags_from_file(filename): +def get_lines_from_file(filename): try: with open(filename, "r") as fp: - tags = [l.strip() for l in fp.readlines()] + lines = [l.strip() for l in fp.readlines()] # remove empty items, if any - tags = [l for l in tags if len(l) > 0] + lines = [l for l in lines if len(l) > 0] except: logger.error(f"Can't open tags list: {filename}") sys.exit(1) - return tags + return lines def get_crs_version(directory, version=None): @@ -265,6 +265,13 @@ def parse_args(argv): parser.add_argument( "-v", "--version", dest="version", help="Check that the passed version string is used correctly.", required=False ) + parser.add_argument( + "-f", + "--filename-tags", + dest="filename_tags_exclusions", + help="Path to file with excluded filename tags", + required=False, + ) return parser.parse_args(argv) @@ -281,7 +288,13 @@ def main(): logger = Logger(output=args.output, debug=args.debug) crs_version = get_crs_version(args.directory, args.version) - tags = get_tags_from_file(args.tagslist) + tags = get_lines_from_file(args.tagslist) + if args.filename_tags_exclusions is None: + # if no filename_tags_exclusions is given, set it to empty list + # this means that all files are checked + filename_tags_exclusions = [] + else: + filename_tags_exclusions = get_lines_from_file(args.filename_tags_exclusions) parsed = read_files(files) txvars = {} @@ -405,7 +418,7 @@ def main(): ) ### check for tag:'OWASP_CRS' - c.check_crs_tag() + c.check_crs_tag(filename_tags_exclusions) if len(c.error_no_crstag) == 0: logger.debug("No rule without OWASP_CRS tag.") else: diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index e0871ba..d24b593 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -53,7 +53,7 @@ def __init__(self, data, filename=None, txvars={}): self.ctlsl = [c.lower() for c in self.ctls] # list the actions in expected order - # see wiki: https://github.com/SpiderLabs/owasp-modsecurity-crs/wiki/Order-of-ModSecurity-Actions-in-CRS-rules + # see wiki: https://github.com/coreruleset/coreruleset/wiki/Order-of-ModSecurity-Actions-in-CRS-rules # note, that these tokens are with lovercase here, but used only for to check the order self.ordered_actions = [ "id", # 0 @@ -122,6 +122,10 @@ def __init__(self, data, filename=None, txvars={}): ) # list of rules which uses TX.N without previous 'capture' # regex to produce tag from filename: self.re_fname = re.compile(r"(REQUEST|RESPONSE)\-\d{3}\-") +<<<<<<< HEAD +======= + self.filename_tag_exclusions = [] +>>>>>>> 612b861 (Add optional argument for filename tag exclusions) def is_error(self): """Returns True if any error is found""" @@ -763,6 +767,7 @@ def check_lowercase_ignorecase(self): } ) +<<<<<<< HEAD def gen_crs_file_tag(self): """ generate tag from filename @@ -776,6 +781,27 @@ def check_crs_tag(self): check that every rule has a `tag:'OWASP_CRS'` and `tag:'$FILENAME$'` action """ filenametag = self.gen_crs_file_tag() +======= + def gen_crs_file_tag(self, fname=None): + """ + generate tag from filename + """ + if fname is None: + filename = self.re_fname.sub("", os.path.basename(self.filename.replace(".conf", ""))) + else: + filename = self.re_fname.sub("", os.path.basename(fname.replace(".conf", ""))) + filename = filename.replace("APPLICATION-", "") + return "/".join(["OWASP_CRS", filename]) + + def check_crs_tag(self, excl_list): + """ + check that every rule has a `tag:'OWASP_CRS'` and `tag:'$FILENAME$'` action + """ + filenametag = self.gen_crs_file_tag() + if len(self.filename_tag_exclusions) == 0: + if len(excl_list) > 0: + self.filename_tag_exclusions = [self.gen_crs_file_tag(f) for f in excl_list] +>>>>>>> 612b861 (Add optional argument for filename tag exclusions) chained = False ruleid = 0 has_crs = False @@ -818,15 +844,10 @@ def check_crs_tag(self): } ) # see the exclusion list of files which does not require the filename tag - if filenametag not in ["OWASP_CRS/crs-setup.conf", \ - "OWASP_CRS/INITIALIZATION", \ - "OWASP_CRS/BLOCKING-EVALUATION", \ - "OWASP_CRS/COMMON-EXCEPTIONS", \ - "OWASP_CRS/CORRELATION"]: - # check wether the rule is an administrative rule + if filenametag not in self.filename_tag_exclusions: + # check if wether the rule is admin rule or not is_admin_rule = True if (ruleid % 1000 < 100) else False - # admin rules do not need filename tags - + # admin rules does not need filename tags if not is_admin_rule and not has_crs_fname: self.error_no_crstag.append( { From 64c28fb71cc0f481e16491b2c2f6804c5d9379df Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Sun, 30 Mar 2025 01:13:44 +0100 Subject: [PATCH 21/48] chore: check rules without filename tag --- src/crs_linter/linter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index d24b593..87fb88f 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -845,7 +845,7 @@ def check_crs_tag(self, excl_list): ) # see the exclusion list of files which does not require the filename tag if filenametag not in self.filename_tag_exclusions: - # check if wether the rule is admin rule or not + # check if wether the rule is admin rule or not is_admin_rule = True if (ruleid % 1000 < 100) else False # admin rules does not need filename tags if not is_admin_rule and not has_crs_fname: From ad472e67c0db1a480a4df41867ba38eed23613a8 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Mon, 21 Apr 2025 21:10:51 +0200 Subject: [PATCH 22/48] Typo fix --- FILENAME_EXCLUSIONS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FILENAME_EXCLUSIONS b/FILENAME_EXCLUSIONS index ff877e9..e8613c1 100644 --- a/FILENAME_EXCLUSIONS +++ b/FILENAME_EXCLUSIONS @@ -1,4 +1,4 @@ -crs-setup.example +crs-setup.conf.example REQUEST-901-INITIALIZATION.conf REQUEST-905-COMMON-EXCEPTIONS.conf REQUEST-949-BLOCKING-EVALUATION.conf From 0c09eeb43ce761e74256917d4c243fe0ebd58335 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 1 Apr 2025 19:12:21 +0200 Subject: [PATCH 23/48] Add modifications from origin --- src/crs_linter/linter.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index 87fb88f..c16dc29 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -122,10 +122,7 @@ def __init__(self, data, filename=None, txvars={}): ) # list of rules which uses TX.N without previous 'capture' # regex to produce tag from filename: self.re_fname = re.compile(r"(REQUEST|RESPONSE)\-\d{3}\-") -<<<<<<< HEAD -======= self.filename_tag_exclusions = [] ->>>>>>> 612b861 (Add optional argument for filename tag exclusions) def is_error(self): """Returns True if any error is found""" @@ -767,29 +764,14 @@ def check_lowercase_ignorecase(self): } ) -<<<<<<< HEAD - def gen_crs_file_tag(self): - """ - generate tag from filename - """ - filename = self.re_fname.sub("", os.path.basename(os.path.splitext(self.filename)[0])) - filename = filename.replace("APPLICATION-", "") - return "/".join(["OWASP_CRS", filename]) - - def check_crs_tag(self): - """ - check that every rule has a `tag:'OWASP_CRS'` and `tag:'$FILENAME$'` action - """ - filenametag = self.gen_crs_file_tag() -======= def gen_crs_file_tag(self, fname=None): """ generate tag from filename """ if fname is None: - filename = self.re_fname.sub("", os.path.basename(self.filename.replace(".conf", ""))) + filename = self.re_fname.sub("", os.path.basename(os.path.splitext(self.filename)[0])) else: - filename = self.re_fname.sub("", os.path.basename(fname.replace(".conf", ""))) + filename = self.re_fname.sub("", os.path.basename(os.path.splitext(fname)[0])) filename = filename.replace("APPLICATION-", "") return "/".join(["OWASP_CRS", filename]) @@ -801,7 +783,6 @@ def check_crs_tag(self, excl_list): if len(self.filename_tag_exclusions) == 0: if len(excl_list) > 0: self.filename_tag_exclusions = [self.gen_crs_file_tag(f) for f in excl_list] ->>>>>>> 612b861 (Add optional argument for filename tag exclusions) chained = False ruleid = 0 has_crs = False @@ -845,9 +826,9 @@ def check_crs_tag(self, excl_list): ) # see the exclusion list of files which does not require the filename tag if filenametag not in self.filename_tag_exclusions: - # check if wether the rule is admin rule or not + # check wether the rule is an administrative rule is_admin_rule = True if (ruleid % 1000 < 100) else False - # admin rules does not need filename tags + # admin rules do not need filename tags if not is_admin_rule and not has_crs_fname: self.error_no_crstag.append( { From 8c59d58cb8aeb8b49d165db3c1c218e5d8a22ea4 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 11:23:27 +0200 Subject: [PATCH 24/48] Reviewed README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1bf7438..37a6a2f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ pip3 install crs-linter The script expects multiple arguments to work correctly. For the complete list of possible arguments, please run the script without any argument. You will get some similar output: ```bash -usage: crs-linter [-h] [-o {native,github}] -d DIRECTORY [--debug] -r CRS_RULES -t TAGSLIST [-v VERSION] +usage: crs-linter [-h] [-o {native,github}] -d DIRECTORY [--debug] -r CRS_RULES -t TAGSLIST [-v VERSION] [-f FILENAME_TAGS_EXCLUSIONS] crs-linter: error: the following arguments are required: -d/--directory, -r/--rules, -t/--tags-list ``` @@ -77,16 +77,16 @@ If script finds any parser error, it stops immediately. In case of other error, If everything is fine, rule returns with 0. -Normally, you should run the script: +Normally, you should run the script (from `coreruleset` directory): ``` -crs-linter -r crs-setup.conf.example -r rules/*.conf +../crs-linter/src/crs_linter/cli.py --debug -r crs-setup.conf.example -r 'rules/*.conf' -t util/APPROVED_TAGS -f ../crs-linter/FILENAME_EXCLUSIONS -v "4.13.0-dev" ``` Optionally, you can add the option `--output=github` (default value is `native`): ``` -crs-linter --output=github -r crs-setup.conf.example -r rules/*.conf +../crs-linter/src/crs_linter/cli.py --debug --output=github -r crs-setup.conf.example -r 'rules/*.conf' -t util/APPROVED_TAGS -f ../crs-linter/FILENAME_EXCLUSIONS -v "4.13.0-dev" ``` In this case, each line will have a prefix, which could be `::debug` or `::error`. See [this](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message). From 329266ef11e44d986c4a1664b5562c5860236205 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 11:50:29 +0200 Subject: [PATCH 25/48] Add argument to fn in tests --- tests/test_linter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_linter.py b/tests/test_linter.py index 1a37bbf..c05e1f5 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -242,7 +242,7 @@ def test_check_crs_tag(): p = parse_config(t) c = Check(p, filename = "REQUEST-900-CHECK-TAG.conf") print(c.filename) - c.check_crs_tag() + c.check_crs_tag([]) assert len(c.error_no_crstag) == 0 @@ -259,7 +259,7 @@ def test_check_crs_tag_fail(): """ p = parse_config(t) c = Check(p, filename = "REQUEST-900-CHECK-TAG.conf") - c.check_crs_tag() + c.check_crs_tag([]) assert len(c.error_no_crstag) == 1 @@ -276,7 +276,7 @@ def test_check_crs_tag_fail2(): """ p = parse_config(t) c = Check(p, filename = "REQUEST-900-CHECK-TAG.conf") - c.check_crs_tag() + c.check_crs_tag([]) assert len(c.error_no_crstag) == 1 @@ -293,7 +293,7 @@ def test_check_crs_tag_fail3(): """ p = parse_config(t) c = Check(p, filename = "REQUEST-900-CHECK-TAG.conf") - c.check_crs_tag() + c.check_crs_tag([]) assert len(c.error_no_crstag) == 1 From 8153104254480d83aa69caeabb9ed83b19c57bff Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 12:04:24 +0200 Subject: [PATCH 26/48] Add new argument for regression test --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c543ea3..62be14d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,4 +46,4 @@ jobs: run: | curl -sSL https://github.com/coreruleset/coreruleset/archive/refs/tags/v${{ matrix.crs-version }}.tar.gz -o - | \ tar xzvf - --strip-components=1 --wildcards "*/rules/*" "*/crs-setup.conf.example" - uv run crs-linter --debug -o github -d . -r crs-setup.conf.example -r 'rules/*.conf' -t APPROVED_TAGS -v ${{ matrix.crs-version }} + uv run crs-linter --debug -o github -d . -r crs-setup.conf.example -r 'rules/*.conf' -t APPROVED_TAGS -f FILENAME_EXCLUSIONS -v ${{ matrix.crs-version }} From 11e060993309ac0ea8f9a852ecdcba42c831fb10 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:37:36 +0200 Subject: [PATCH 27/48] Fix script run description in README.md. Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37a6a2f..2583512 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ pip3 install crs-linter ## How does it work -The script expects multiple arguments to work correctly. For the complete list of possible arguments, please run the script without any argument. You will get some similar output: +The script expects multiple arguments to work correctly. For the complete list of possible arguments, please run the script without any argument. You will see output similar to the following: ```bash usage: crs-linter [-h] [-o {native,github}] -d DIRECTORY [--debug] -r CRS_RULES -t TAGSLIST [-v VERSION] [-f FILENAME_TAGS_EXCLUSIONS] From f6d942ccfaa54d855f994f8337b410f9946e08aa Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:38:23 +0200 Subject: [PATCH 28/48] Fix script's arguments overrview title Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2583512..f1b627e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ usage: crs-linter [-h] [-o {native,github}] -d DIRECTORY [--debug] -r CRS_RULES crs-linter: error: the following arguments are required: -d/--directory, -r/--rules, -t/--tags-list ``` -#### Arguments review +#### Arguments overview * `-h` - gives help * `-o` - output format, van be `native` (default) or `github`; Note, that `github` format follows [GH](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-a-notice-message) suggestion From ad52b0ce3d2c15e34b5310d05b9d45ce20313a74 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:38:54 +0200 Subject: [PATCH 29/48] Fix script's 'help' description Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1b627e..c591741 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ crs-linter: error: the following arguments are required: -d/--directory, -r/--ru #### Arguments overview -* `-h` - gives help +* `-h` - shows usage information and exits * `-o` - output format, van be `native` (default) or `github`; Note, that `github` format follows [GH](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-a-notice-message) suggestion * `-d` - directory path to CRS git repository. This is required if you don't add the version. * `--debug` - show debug information From df3d52e6dc6af5888145a83fd9e3e05ff93c8762 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:39:28 +0200 Subject: [PATCH 30/48] Fix script's 'output' description Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c591741..e9c02ef 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ crs-linter: error: the following arguments are required: -d/--directory, -r/--ru #### Arguments overview * `-h` - shows usage information and exits -* `-o` - output format, van be `native` (default) or `github`; Note, that `github` format follows [GH](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-a-notice-message) suggestion +* `-o` - output format, either `native` (default) or `github`. Note, that `github` format follows the suggestions from the [GitHub docs](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-a-notice-message) * `-d` - directory path to CRS git repository. This is required if you don't add the version. * `--debug` - show debug information * `-r` - CRS rules file to check, can be used multiple times, eg `-r ../path/to/crs-setup.conf -r "../path/to/rules/*.conf"` From c6659582c33dce044c1072da5a8f22db254f9484 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:40:01 +0200 Subject: [PATCH 31/48] Fix script's 'tags' description Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e9c02ef..eaa89c5 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ crs-linter: error: the following arguments are required: -d/--directory, -r/--ru * `-d` - directory path to CRS git repository. This is required if you don't add the version. * `--debug` - show debug information * `-r` - CRS rules file to check, can be used multiple times, eg `-r ../path/to/crs-setup.conf -r "../path/to/rules/*.conf"` -* `-t` - path to file which contains list of approved tags; only tags allowed at rules which are listed in this file +* `-t` - path to file which contains the list of approved tags; tags not listed in this file will be considered check failures when found on a rule * `-v` - CRS version, optional * `-f` - path to the file containing the list of files that do not need to be checked for filename tags, optional From 260ab07a196339962a4fb2831e02ac29098f6078 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:40:29 +0200 Subject: [PATCH 32/48] Fix script's 'version' description Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eaa89c5..72d55c2 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ crs-linter: error: the following arguments are required: -d/--directory, -r/--ru * `--debug` - show debug information * `-r` - CRS rules file to check, can be used multiple times, eg `-r ../path/to/crs-setup.conf -r "../path/to/rules/*.conf"` * `-t` - path to file which contains the list of approved tags; tags not listed in this file will be considered check failures when found on a rule -* `-v` - CRS version, optional +* `-v` - CRS version, optional (the linter will try to be smart and figure the version out by itself, which may fail) * `-f` - path to the file containing the list of files that do not need to be checked for filename tags, optional First, an attempt is made to parse each file specified on the command line. This is a "pre-check", and runs on all files before the other tests. From a200e582b9ccf840ce3b6ef89a931cc6a28ee2ac Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:41:20 +0200 Subject: [PATCH 33/48] Fix Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72d55c2..82f8af4 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Second, the script loops over each of the parsed structures. Each iteration cons * **Check rule tags** - only tags listed in `util/APPROVED_TAGS` may be used as tags in rules * to use a new tag on a rule, it **must** first be registered in the util/APPROVED_TAGS file * **Check t:lowercase and (?i) flag** - No combination of t:lowercase and (?i) should appear in the same rule. -* **Check rule has a tag with value `OWASP_CRS`** - Every rule must have a tag with value `OWASP_CRS`; every non administrative rules must have a tag with value `OWASP_CRS/$filename$`. You can pass a file with list of exclusions of files which don't need these tags. `crs_linter` directory contains an example, see `FILENAME_EXCLUSIONS`. +* **Check rule has a tag with value `OWASP_CRS`** - Every rule must have a tag with value `OWASP_CRS`; every non-administrative rule must have a tag with value `OWASP_CRS/$filename$`. You can pass a file with a list of files which should be excluded from this check using the `-f` flag. See `crs_linter/FILENAME_EXCLUSIONS` for an example of such a file. * **Check rule has a `ver` action with correct version** - Every rule must have `ver` action with correct value * script accepts `-v` or `--version` argument if you want to pass it manually * if no `-v` was given, the script tries to extract the version from result of `git describe --tags` From fe1a2851fc3bf478a608334442fa5199aabc892d Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:42:28 +0200 Subject: [PATCH 34/48] Fix script's run example Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82f8af4..2e321f4 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ If script finds any parser error, it stops immediately. In case of other error, If everything is fine, rule returns with 0. -Normally, you should run the script (from `coreruleset` directory): +Normally, you will run the script (from `coreruleset` directory) like this: ``` ../crs-linter/src/crs_linter/cli.py --debug -r crs-setup.conf.example -r 'rules/*.conf' -t util/APPROVED_TAGS -f ../crs-linter/FILENAME_EXCLUSIONS -v "4.13.0-dev" From 3fc7549f69e70a03b3c0d4231a73c40a0a8073eb Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:43:24 +0200 Subject: [PATCH 35/48] Change script run example Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e321f4..7e8739a 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,13 @@ If everything is fine, rule returns with 0. Normally, you will run the script (from `coreruleset` directory) like this: ``` -../crs-linter/src/crs_linter/cli.py --debug -r crs-setup.conf.example -r 'rules/*.conf' -t util/APPROVED_TAGS -f ../crs-linter/FILENAME_EXCLUSIONS -v "4.13.0-dev" +../crs-linter/src/crs_linter/cli.py \ + --debug \ + -r crs-setup.conf.example \ + -r 'rules/*.conf' \ + -t util/APPROVED_TAGS \ + -f ../crs-linter/FILENAME_EXCLUSIONS \ + -v "4.13.0-dev" ``` Optionally, you can add the option `--output=github` (default value is `native`): From 3c9586db140a781b5c8151164d46ad899e5f2028 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:45:40 +0200 Subject: [PATCH 36/48] Add interpreter to syntax highlight Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e8739a..22c505e 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ If everything is fine, rule returns with 0. Normally, you will run the script (from `coreruleset` directory) like this: -``` +```bash ../crs-linter/src/crs_linter/cli.py \ --debug \ -r crs-setup.conf.example \ From 30e050c8667c1c1ffbc049956e38065949917f97 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:46:08 +0200 Subject: [PATCH 37/48] Change script run example Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 22c505e..1b181cf 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,14 @@ Normally, you will run the script (from `coreruleset` directory) like this: Optionally, you can add the option `--output=github` (default value is `native`): ``` -../crs-linter/src/crs_linter/cli.py --debug --output=github -r crs-setup.conf.example -r 'rules/*.conf' -t util/APPROVED_TAGS -f ../crs-linter/FILENAME_EXCLUSIONS -v "4.13.0-dev" +../crs-linter/src/crs_linter/cli.py \ + --debug \ + --output=github \ + -r crs-setup.conf.example \ + -r 'rules/*.conf' \ + -t util/APPROVED_TAGS \ + -f ../crs-linter/FILENAME_EXCLUSIONS \ + -v "4.13.0-dev" ``` In this case, each line will have a prefix, which could be `::debug` or `::error`. See [this](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message). From 4cd49dbf238fbad358be58233da445af0d48a14c Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:46:26 +0200 Subject: [PATCH 38/48] Add interpreter to syntax highlight Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b181cf..ffb51b8 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ Normally, you will run the script (from `coreruleset` directory) like this: Optionally, you can add the option `--output=github` (default value is `native`): -``` +```bash ../crs-linter/src/crs_linter/cli.py \ --debug \ --output=github \ From fd2bd1057e261d7c7663f6d2fb43210c15f3d1bd Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:47:10 +0200 Subject: [PATCH 39/48] Change setup.conf file check method Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- src/crs_linter/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crs_linter/cli.py b/src/crs_linter/cli.py index 48f3e53..d916707 100755 --- a/src/crs_linter/cli.py +++ b/src/crs_linter/cli.py @@ -137,7 +137,7 @@ def check_indentation(filename, content): try: with open(filename, "r") as fp: from_lines = fp.readlines() - if os.path.basename(filename).startswith("crs-setup.conf.example"): + if os.path.basename(filename) == "crs-setup.conf.example": from_lines = remove_comments("".join(from_lines)).split("\n") from_lines = [l + "\n" for l in from_lines] except: From ba1442ef7e5f8daec4bda7c4d4496685f454af85 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:47:53 +0200 Subject: [PATCH 40/48] Change list build method Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- src/crs_linter/cli.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/crs_linter/cli.py b/src/crs_linter/cli.py index d916707..cb45493 100755 --- a/src/crs_linter/cli.py +++ b/src/crs_linter/cli.py @@ -289,11 +289,9 @@ def main(): crs_version = get_crs_version(args.directory, args.version) tags = get_lines_from_file(args.tagslist) - if args.filename_tags_exclusions is None: - # if no filename_tags_exclusions is given, set it to empty list - # this means that all files are checked - filename_tags_exclusions = [] - else: + # Check all files by default + filename_tags_exclusions = [] + if args.filename_tags_exclusions is not None: filename_tags_exclusions = get_lines_from_file(args.filename_tags_exclusions) parsed = read_files(files) txvars = {} From c3ff504d22da40885591c4b19612107c42028747 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:48:18 +0200 Subject: [PATCH 41/48] Change list build method Co-authored-by: Max Leske <250711+theseion@users.noreply.github.com> --- src/crs_linter/linter.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index c16dc29..bf960de 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -768,10 +768,8 @@ def gen_crs_file_tag(self, fname=None): """ generate tag from filename """ - if fname is None: - filename = self.re_fname.sub("", os.path.basename(os.path.splitext(self.filename)[0])) - else: - filename = self.re_fname.sub("", os.path.basename(os.path.splitext(fname)[0])) + filename_for_tag = fname if fname is not None else self.filename + filename = self.re_fname.sub("", os.path.basename(os.path.splitext(filename_for_tag)[0])) filename = filename.replace("APPLICATION-", "") return "/".join(["OWASP_CRS", filename]) From 57f92250ce917f2eb353cb41bf960ec1a7a5d27d Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Tue, 22 Apr 2025 20:56:52 +0200 Subject: [PATCH 42/48] Fix invalid indentation --- src/crs_linter/linter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crs_linter/linter.py b/src/crs_linter/linter.py index bf960de..d17985b 100755 --- a/src/crs_linter/linter.py +++ b/src/crs_linter/linter.py @@ -768,8 +768,8 @@ def gen_crs_file_tag(self, fname=None): """ generate tag from filename """ - filename_for_tag = fname if fname is not None else self.filename - filename = self.re_fname.sub("", os.path.basename(os.path.splitext(filename_for_tag)[0])) + filename_for_tag = fname if fname is not None else self.filename + filename = self.re_fname.sub("", os.path.basename(os.path.splitext(filename_for_tag)[0])) filename = filename.replace("APPLICATION-", "") return "/".join(["OWASP_CRS", filename]) From f1ab56d0b277b5b190d6fbcd3e0a4feba448d790 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 06:43:56 +0000 Subject: [PATCH 43/48] Update all non-major dependencies in .github/workflows/test.yml (#33) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/pypi-release.yml | 2 +- .github/workflows/pypi-test-release.yml | 2 +- .github/workflows/test.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e07032f..cc7f058 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,7 +37,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@45775bd8235c68ba998cffa5171334d58593da47 # v3 + uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -48,4 +48,4 @@ jobs: # queries: security-extended,security-and-quality - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@45775bd8235c68ba998cffa5171334d58593da47 # v3 + uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3 diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 1ad3118..1651d5e 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: "Set up Python" - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version-file: "pyproject.toml" diff --git a/.github/workflows/pypi-test-release.yml b/.github/workflows/pypi-test-release.yml index 9f9f399..e5298b8 100644 --- a/.github/workflows/pypi-test-release.yml +++ b/.github/workflows/pypi-test-release.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: "Set up Python" - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version-file: "pyproject.toml" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 62be14d..926b38a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: enable-cache: true - name: Set up Python 3 - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: ${{ matrix.python-version }} From 7286abcb8b74d4d110de0fb8e25eed0c682c1622 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 06:44:20 +0000 Subject: [PATCH 44/48] Update astral-sh/setup-uv action to v6 in .github/workflows/test.yml --- .github/workflows/pypi-release.yml | 2 +- .github/workflows/pypi-test-release.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 1651d5e..53bdade 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -25,7 +25,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 + uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6 with: version: "0.6.1" diff --git a/.github/workflows/pypi-test-release.yml b/.github/workflows/pypi-test-release.yml index e5298b8..e656460 100644 --- a/.github/workflows/pypi-test-release.yml +++ b/.github/workflows/pypi-test-release.yml @@ -18,7 +18,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 + uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6 with: version: "0.6.1" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 926b38a..18d36fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 + uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6 with: enable-cache: true From 88683c5b7611961ed6d650a2d35cbc5a982062eb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 07:20:25 +0000 Subject: [PATCH 45/48] Update all non-major dependencies in .github/workflows/test.yml (#35) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/pypi-release.yml | 2 +- .github/workflows/pypi-test-release.yml | 2 +- .github/workflows/test.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index cc7f058..8c9b6d6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,7 +37,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -48,4 +48,4 @@ jobs: # queries: security-extended,security-and-quality - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3 diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index 53bdade..53dc266 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -25,7 +25,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6 + uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6 with: version: "0.6.1" diff --git a/.github/workflows/pypi-test-release.yml b/.github/workflows/pypi-test-release.yml index e656460..c6ef467 100644 --- a/.github/workflows/pypi-test-release.yml +++ b/.github/workflows/pypi-test-release.yml @@ -18,7 +18,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install uv - uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6 + uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6 with: version: "0.6.1" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 18d36fc..f8915ba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install uv - uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6 + uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6 with: enable-cache: true From 1fa188db8ece9ca7b7064fef4fa36af94e3cbe75 Mon Sep 17 00:00:00 2001 From: Max Leske <250711+theseion@users.noreply.github.com> Date: Tue, 6 May 2025 07:33:54 +0200 Subject: [PATCH 46/48] feat: improve GitHub output in case of errors Groups in the GitHub output are always closed. When an error is found and printed within a group, it is, therefore, automatically hidden in the output. This change adds an additional error output after a group with errors, to makes it easy to find the affected groups. This commit also cleans up the project setup: - use uv - drop poetry - clean up pyproject.toml to adhere to specs and remove unused things - use hatch-vcs to parse the release version from the latest Git tag --- .gitignore | 1 + pyproject.toml | 49 ++++---- src/crs_linter/cli.py | 39 +++++-- src/crs_linter/logger.py | 23 ++-- uv.lock | 235 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 304 insertions(+), 43 deletions(-) create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 9ac2dd8..fbc37cb 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +_version.py # PyInstaller # Usually these files are written by a python script from a template diff --git a/pyproject.toml b/pyproject.toml index dc61933..1baee3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "crs-linter" -version = "0.1.1" +dynamic = [ "version" ] description = "CRS linter" authors = [ {name = "Ervin Hegedus", email = "airween@gmail.com"} @@ -8,10 +8,6 @@ authors = [ requires-python = ">=3.9" license = "Apache-2.0" readme = "README.md" -Issues = "https://github.com/coreruleset/crs-linter/issues" -Homepage = "https://github.com/coreruleset/crs-linter" -Repository = "https://github.com/coreruleset/crs-linter.git" - keywords = ["OWASP", "CRS", "linter"] classifiers = [ @@ -20,11 +16,6 @@ classifiers = [ "Operating System :: OS Independent", ] -packages = [ - { include = "crs_linter", from = "src" } -] - -# Requirements dependencies = [ "msc_pyparser >=1.2.1", "dulwich (>=0.22.7,<0.23.0)", @@ -33,26 +24,34 @@ dependencies = [ ] [project.scripts] -crs-linter = 'crs_linter.cli:main' + crs-linter = 'crs_linter.cli:main' -[dependency-groups] -dev = [ - "pytest >=8.1.1,<9" -] +[project.urls] + issues = "https://github.com/coreruleset/crs-linter/issues" + homepage = "https://github.com/coreruleset/crs-linter" + repository = "https://github.com/coreruleset/crs-linter.git" -[tool.semantic_release] -version_variable = "pyproject.toml:version" +[dependency-groups] + dev = [ + "pytest >=8.1.1,<9" + ] [build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" + requires = ["hatchling", "hatch-vcs"] + build-backend = "hatchling.build" + +[tool.hatch.version] + source = "vcs" + +[tool.hatch.version.raw-options] +version_scheme = "no-guess-dev" [[tool.uv.index]] -name = "pypi" -url = "https://pypi.org/simple/" -publish-url = "https://pypi.org/legacy/" + name = "pypi" + url = "https://pypi.org/simple/" + publish-url = "https://pypi.org/legacy/" [[tool.uv.index]] -name = "testpypi" -url = "https://test.pypi.org/simple/" -publish-url = "https://test.pypi.org/legacy/" + name = "testpypi" + url = "https://test.pypi.org/simple/" + publish-url = "https://test.pypi.org/legacy/" diff --git a/src/crs_linter/cli.py b/src/crs_linter/cli.py index cb45493..be5c0ed 100755 --- a/src/crs_linter/cli.py +++ b/src/crs_linter/cli.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 + import glob import logging import pathlib @@ -18,7 +19,8 @@ try: from logger import Logger except: - from crs_linter.logger import Logger + from crs_linter.logger import Logger, Output + def remove_comments(data): """ @@ -168,7 +170,13 @@ def check_indentation(filename, content): r = re.match(r"^@@ -(\d+),(\d+) \+\d+,\d+ @@$", d) if r: line1, line2 = [int(i) for i in r.groups()] - logger.error("an indentation error was found", file=filename, title="Indentation error", line=line1, end_line=line1 + line2) + logger.error( + "an indentation error was found", + file=filename, + title="Indentation error", + line=line1, + end_line=line1 + line2, + ) return error @@ -215,12 +223,14 @@ def read_files(filenames): return parsed + def _version_in_argv(argv): - """" If version was passed as argument, make it not required """ + """ " If version was passed as argument, make it not required""" if "-v" in argv or "--version" in argv: return False return True + def parse_args(argv): parser = argparse.ArgumentParser( prog="crs-linter", description="CRS Rules Check tool" @@ -229,9 +239,10 @@ def parse_args(argv): "-o", "--output", dest="output", - default="native", + type=Output, + default=Output.NATIVE, help="Output format", - choices=["native", "github"], + choices=[o.value for o in Output], required=False, ) parser.add_argument( @@ -241,7 +252,9 @@ def parse_args(argv): default=pathlib.Path("."), type=pathlib.Path, help="Directory path to CRS git repository. This is required if you don't add the version.", - required=_version_in_argv(argv), # this means it is required if you don't pass the version + required=_version_in_argv( + argv + ), # this means it is required if you don't pass the version ) parser.add_argument( "--debug", dest="debug", help="Show debug information.", action="store_true" @@ -263,7 +276,11 @@ def parse_args(argv): required=True, ) parser.add_argument( - "-v", "--version", dest="version", help="Check that the passed version string is used correctly.", required=False + "-v", + "--version", + dest="version", + help="Check that the passed version string is used correctly.", + required=False, ) parser.add_argument( "-f", @@ -422,9 +439,9 @@ def main(): else: filenametag = c.gen_crs_file_tag() logger.error( - f"There are one or more rules without OWASP_CRS or {filenametag} tag", + f"There are one or more rules without OWASP_CRS or {filenametag} tag", file=f, - title=f"'tag:OWASP_CRS' or 'tag:OWASP_CRS/{filenametag}' is missing" + title=f"'tag:OWASP_CRS' or 'tag:OWASP_CRS/{filenametag}' is missing", ) ### check for ver action @@ -454,6 +471,10 @@ def main(): retval = 1 logger.end_group() + if c.is_error() and logger.output == Output.GITHUB: + # Groups hide log entries, so if we find an error we need to tell + # users where it is. + logger.error("Error found in previous group") logger.debug("End of checking parsed rules") logger.debug("Cumulated report about unused TX variables") diff --git a/src/crs_linter/logger.py b/src/crs_linter/logger.py index 5da3af4..7c893dd 100644 --- a/src/crs_linter/logger.py +++ b/src/crs_linter/logger.py @@ -1,15 +1,20 @@ +from enum import StrEnum, auto import logging -import logging.handlers import github_action_utils as gha_utils +class Output(StrEnum): + NATIVE = auto() + GITHUB = auto() + + class Logger: - def __init__(self, output="native", debug=False): + def __init__(self, output=Output.NATIVE, debug=False): self.output = output self.debugging = debug level = logging.INFO - if self.output == "native": + if self.output == Output.NATIVE: level = logging.DEBUG if self.debugging else logging.INFO logging.basicConfig(level=level) self.logger = logging.getLogger() @@ -18,16 +23,16 @@ def __init__(self, output="native", debug=False): self.logger = gha_utils def start_group(self, *args, **kwargs): - if self.output == "github": + if self.output == Output.GITHUB: self.logger.start_group(*args, **kwargs) def end_group(self): - if self.output == "github": + if self.output == Output.GITHUB: self.logger.end_group() def debug(self, *args, **kwargs): if self.debugging: - if self.output == "native": + if self.output == Output.NATIVE: self.logger.debug(*args) else: self.logger.debug(*args, **kwargs) @@ -35,17 +40,17 @@ def debug(self, *args, **kwargs): pass def error(self, *args, **kwargs): - if self.output == "native": + if self.output == Output.NATIVE: self.logger.error(*args) else: self.logger.error(*args, **kwargs) def warning(self, *args, **kwargs): - if self.output == "native": + if self.output == Output.NATIVE: self.logger.warning(*args) else: self.logger.warning(*args, **kwargs) def info(self, *args, **kwargs): - if self.output == "native": + if self.output == Output.NATIVE: self.logger.info(*args, **kwargs) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..69eb4af --- /dev/null +++ b/uv.lock @@ -0,0 +1,235 @@ +version = 1 +revision = 2 +requires-python = ">=3.9" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "crs-linter" +source = { editable = "." } +dependencies = [ + { name = "dulwich" }, + { name = "github-action-utils" }, + { name = "msc-pyparser" }, + { name = "semver" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "dulwich", specifier = ">=0.22.7,<0.23.0" }, + { name = "github-action-utils", specifier = ">=1.1.0,<2.0.0" }, + { name = "msc-pyparser", specifier = ">=1.2.1" }, + { name = "semver", specifier = ">=3.0.2,<4.0.0" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=8.1.1,<9" }] + +[[package]] +name = "dulwich" +version = "0.22.8" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/8b/0f2de00c0c0d5881dc39be147ec2918725fb3628deeeb1f27d1c6cf6d9f4/dulwich-0.22.8.tar.gz", hash = "sha256:701547310415de300269331abe29cb5717aa1ea377af826bf513d0adfb1c209b", size = 466542, upload-time = "2025-03-02T23:08:10.375Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/4d/0bfc8a96456d033428875003b5104da2c32407363b5b829da5e27553b403/dulwich-0.22.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546176d18b8cc0a492b0f23f07411e38686024cffa7e9d097ae20512a2e57127", size = 925150, upload-time = "2025-03-02T23:06:45.982Z" }, + { url = "https://files.pythonhosted.org/packages/99/71/0dd97cf5a7a09aee93f8266421898d705eba737ca904720450584f471bd3/dulwich-0.22.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d2434dd72b2ae09b653c9cfe6764a03c25cfbd99fbbb7c426f0478f6fb1100f", size = 994973, upload-time = "2025-03-02T23:06:48.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/40/831bed622eeacfa21f47d1fd75fc0c33a70a2cf1c091ae955be63e94144c/dulwich-0.22.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8318bc0921d42e3e69f03716f983a301b5ee4c8dc23c7f2c5bbb28581257a9", size = 1002875, upload-time = "2025-03-02T23:06:50.835Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9e/5255b3927f355c95f6779debf11d551b7bb427a80a11564a1e1b78f0acf6/dulwich-0.22.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7a0f96a2a87f3b4f7feae79d2ac6b94107d6b7d827ac08f2f331b88c8f597a1", size = 1046048, upload-time = "2025-03-02T23:06:53.173Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f9/d3041cea8cbaaffbd4bf95343c5c16d64608200fc5fa26418bee00ebff23/dulwich-0.22.8-cp310-cp310-win32.whl", hash = "sha256:432a37b25733202897b8d67cdd641688444d980167c356ef4e4dd15a17a39a24", size = 592790, upload-time = "2025-03-02T23:06:55.319Z" }, + { url = "https://files.pythonhosted.org/packages/94/95/e90a292fb00ffae4f3fbb53b199574eedfaf57b72b67a8ddb835536fc66b/dulwich-0.22.8-cp310-cp310-win_amd64.whl", hash = "sha256:f3a15e58dac8b8a76073ddca34e014f66f3672a5540a99d49ef6a9c09ab21285", size = 609197, upload-time = "2025-03-02T23:06:57.439Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6e/de1a1c35960d0e399f71725cfcd4dfdb3c391b22c0e5059d991f7ade3488/dulwich-0.22.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0852edc51cff4f4f62976bdaa1d82f6ef248356c681c764c0feb699bc17d5782", size = 925222, upload-time = "2025-03-02T23:06:59.595Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/b65953b4e9c39268c67038bb8d88516885b720beb25b0f6a0ae95ea3f6b2/dulwich-0.22.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:826aae8b64ac1a12321d6b272fc13934d8f62804fda2bc6ae46f93f4380798eb", size = 994572, upload-time = "2025-03-02T23:07:00.971Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/07e3974964bfe05888457f7764cfe53b6b95082313c2be06fbbb72116372/dulwich-0.22.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7ae726f923057d36cdbb9f4fb7da0d0903751435934648b13f1b851f0e38ea1", size = 1002530, upload-time = "2025-03-02T23:07:02.927Z" }, + { url = "https://files.pythonhosted.org/packages/2d/b3/69aebfda4dd4b05ae11af803e4df2d8d350356a30b3b6b6fc662fa1ff729/dulwich-0.22.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6987d753227f55cf75ba29a8dab69d1d83308ce483d7a8c6d223086f7a42e125", size = 1046084, upload-time = "2025-03-02T23:07:04.901Z" }, + { url = "https://files.pythonhosted.org/packages/d4/88/ea0f473d726e117f9fcd7c7a95d97f9ba0e0ee9d9005d745a38809d33352/dulwich-0.22.8-cp311-cp311-win32.whl", hash = "sha256:7757b4a2aad64c6f1920082fc1fccf4da25c3923a0ae7b242c08d06861dae6e1", size = 593130, upload-time = "2025-03-02T23:07:07.336Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a8/ed23a435d6922ba7d9601404f473e49acdcb5768a35d89a5bc5fa51d882b/dulwich-0.22.8-cp311-cp311-win_amd64.whl", hash = "sha256:12b243b7e912011c7225dc67480c313ac8d2990744789b876016fb593f6f3e19", size = 609118, upload-time = "2025-03-02T23:07:11.171Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f2/53c5a22a4a9c0033e10f35c293bc533d64fe3e0c4ff4421128a97d6feda9/dulwich-0.22.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d81697f74f50f008bb221ab5045595f8a3b87c0de2c86aa55be42ba97421f3cd", size = 915677, upload-time = "2025-03-02T23:07:13.292Z" }, + { url = "https://files.pythonhosted.org/packages/02/57/7163ed06a2d9bf1f34d89dcc7c5881119beeed287022c997b0a706edcfbe/dulwich-0.22.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bff1da8e2e6a607c3cb45f5c2e652739589fe891245e1d5b770330cdecbde41", size = 991955, upload-time = "2025-03-02T23:07:14.633Z" }, + { url = "https://files.pythonhosted.org/packages/fa/73/50ddf1f3ad592c2526cb34287f45b07ee6320b850efddda2917cc81ac651/dulwich-0.22.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9969099e15b939d3936f8bee8459eaef7ef5a86cd6173393a17fe28ca3d38aff", size = 1000045, upload-time = "2025-03-02T23:07:16.807Z" }, + { url = "https://files.pythonhosted.org/packages/70/6b/1153b2793bfc34253589badb5fc22ed476cf741dab7854919e6e51cb0441/dulwich-0.22.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:017152c51b9a613f0698db28c67cf3e0a89392d28050dbf4f4ac3f657ea4c0dc", size = 1044291, upload-time = "2025-03-02T23:07:18.912Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e3/6b013b98254d7f508f21456832e757b17a9116752979e8b923f89f8c8989/dulwich-0.22.8-cp312-cp312-win32.whl", hash = "sha256:ee70e8bb8798b503f81b53f7a103cb869c8e89141db9005909f79ab1506e26e9", size = 591258, upload-time = "2025-03-02T23:07:21.038Z" }, + { url = "https://files.pythonhosted.org/packages/81/20/b149f68557d42607b5dcc6f57c1650f2136049be617f3e68092c25861275/dulwich-0.22.8-cp312-cp312-win_amd64.whl", hash = "sha256:dc89c6f14dcdcbfee200b0557c59ae243835e42720be143526d834d0e53ed3af", size = 608693, upload-time = "2025-03-02T23:07:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b7/78116bfe8860edca277d00ac243749c8b94714dc3b4608f0c23fa7f4b78e/dulwich-0.22.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbade3342376be1cd2409539fe1b901d2d57a531106bbae204da921ef4456a74", size = 915617, upload-time = "2025-03-02T23:07:25.18Z" }, + { url = "https://files.pythonhosted.org/packages/a1/af/28c317a83d6ae9ca93a8decfaa50f09b25a73134f5087a98f51fa5a2d784/dulwich-0.22.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71420ffb6deebc59b2ce875e63d814509f9c1dc89c76db962d547aebf15670c7", size = 991271, upload-time = "2025-03-02T23:07:26.554Z" }, + { url = "https://files.pythonhosted.org/packages/84/a0/64a0376f79c7fb87ec6e6d9a0e2157f3196d1f5f75618c402645ac5ccf19/dulwich-0.22.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a626adbfac44646a125618266a24133763bdc992bf8bd0702910d67e6b994443", size = 999791, upload-time = "2025-03-02T23:07:28.068Z" }, + { url = "https://files.pythonhosted.org/packages/63/c3/260f060ededcdf5f13a7e63a36329c95225bf8e8c3f50aeca6820850b56a/dulwich-0.22.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f1476c9c4e4ede95714d06c4831883a26680e37b040b8b6230f506e5ba39f51", size = 1043970, upload-time = "2025-03-02T23:07:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/11/47/2bc02dd1c25eb13cb3cd20cd5a55dd9d7b9fa6af95ed574dd913dd67a0fb/dulwich-0.22.8-cp313-cp313-win32.whl", hash = "sha256:b2b31913932bb5bd41658dd398b33b1a2d4d34825123ad54e40912cfdfe60003", size = 590548, upload-time = "2025-03-02T23:07:31.518Z" }, + { url = "https://files.pythonhosted.org/packages/f3/17/66368fa9d4cffd52663d20354a74aa42d3a6d998f1a462e30aff38c99d25/dulwich-0.22.8-cp313-cp313-win_amd64.whl", hash = "sha256:7a44e5a61a7989aca1e301d39cfb62ad2f8853368682f524d6e878b4115d823d", size = 608200, upload-time = "2025-03-02T23:07:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c5/c67e7742c5fa7d70a01eb8689b3c2014e5151169fc5d19186ec81899001b/dulwich-0.22.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9cd0c67fb44a38358b9fcabee948bf11044ef6ce7a129e50962f54c176d084e", size = 926618, upload-time = "2025-03-02T23:07:34.615Z" }, + { url = "https://files.pythonhosted.org/packages/3a/92/7bd8fc43b02d6f3f997a5a201af6effed0d026359877092f84d50ac5f327/dulwich-0.22.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b79b94726c3f4a9e5a830c649376fd0963236e73142a4290bac6bc9fc9cb120", size = 995038, upload-time = "2025-03-02T23:07:35.979Z" }, + { url = "https://files.pythonhosted.org/packages/96/f3/8f96461752375bc0b81cab941d58824a1359b84d43a49311b5213a9699d0/dulwich-0.22.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16bbe483d663944972e22d64e1f191201123c3b5580fbdaac6a4f66bfaa4fc11", size = 1003876, upload-time = "2025-03-02T23:07:37.497Z" }, + { url = "https://files.pythonhosted.org/packages/d5/34/5d3b5b1ace0c2ab964f0a724f57523e07cf02eafa45df39328cd4bcf2e99/dulwich-0.22.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e02d403af23d93dc1f96eb2408e25efd50046e38590a88c86fa4002adc9849b0", size = 1048552, upload-time = "2025-03-02T23:07:39.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d9/16fcd2c973aa2c1ec3e880c43c95f5afced1abb3f655f5a3fd1911abf02b/dulwich-0.22.8-cp39-cp39-win32.whl", hash = "sha256:8bdd9543a77fb01be704377f5e634b71f955fec64caa4a493dc3bfb98e3a986e", size = 594500, upload-time = "2025-03-02T23:07:41.683Z" }, + { url = "https://files.pythonhosted.org/packages/ef/9b/e7f3d9a5b7ceed1c1051237abd48b5fa1c1a3ab716a4f9c56a1a2f5e839a/dulwich-0.22.8-cp39-cp39-win_amd64.whl", hash = "sha256:3b6757c6b3ba98212b854a766a4157b9cb79a06f4e1b06b46dec4bd834945b8e", size = 610275, upload-time = "2025-03-02T23:07:43.105Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a3/7f88ba8ed56eaed6206a7d9b35244964a32eb08635be33f2af60819e6431/dulwich-0.22.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7bb18fa09daa1586c1040b3e2777d38d4212a5cdbe47d384ba66a1ac336fcc4c", size = 947436, upload-time = "2025-03-02T23:07:44.398Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d0/664a38f03cf4264a4ab9112067eb4998d14ffbf3af4cff9fb2d1447f11bc/dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2fda8e87907ed304d4a5962aea0338366144df0df60f950b8f7f125871707f", size = 998380, upload-time = "2025-03-02T23:07:45.935Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e4/3595a23375b797a8602a2ca8f6b8207b4ebdf2e3a1ccba306f7b90d74c3f/dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1748cd573a0aee4d530bc223a23ccb8bb5b319645931a37bd1cfb68933b720c1", size = 1006758, upload-time = "2025-03-02T23:07:47.503Z" }, + { url = "https://files.pythonhosted.org/packages/20/d1/32d89d37da8e2ae947558db0401940594efdda9fa5bb1c55c2b46c43f244/dulwich-0.22.8-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a631b2309feb9a9631eabd896612ba36532e3ffedccace57f183bb868d7afc06", size = 1050947, upload-time = "2025-03-02T23:07:49.208Z" }, + { url = "https://files.pythonhosted.org/packages/f5/dc/b9448b82de3e244400dc35813f31db9f4952605c7d4e3041fd94878613c9/dulwich-0.22.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:00e7d9a3d324f9e0a1b27880eec0e8e276ff76519621b66c1a429ca9eb3f5a8d", size = 612479, upload-time = "2025-03-02T23:07:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/e2/20/d855d603ea49ce437d2a015fad9dbb22409e23520340aef3d3dca8b299bb/dulwich-0.22.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f8aa3de93201f9e3e40198725389aa9554a4ee3318a865f96a8e9bc9080f0b25", size = 947073, upload-time = "2025-03-02T23:07:52.082Z" }, + { url = "https://files.pythonhosted.org/packages/30/06/390a3a9ce2f4d5b20af0e64f0e9bcefb4a87ad30ef53ee122887f5444076/dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e8da9dd8135884975f5be0563ede02179240250e11f11942801ae31ac293f37", size = 997873, upload-time = "2025-03-02T23:07:54.399Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cd/3c5731784bac200e41b5e66b1440f9f30f92781d3eeefb9f90147c3d392e/dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fc5ce2435fb3abdf76f1acabe48f2e4b3f7428232cadaef9daaf50ea7fa30ee", size = 1006609, upload-time = "2025-03-02T23:07:56.091Z" }, + { url = "https://files.pythonhosted.org/packages/19/cf/01180599b0028e2175da4c0878fbe050d1f197825529be19718f65c5a475/dulwich-0.22.8-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:982b21cc3100d959232cadb3da0a478bd549814dd937104ea50f43694ec27153", size = 1051004, upload-time = "2025-03-02T23:07:58.211Z" }, + { url = "https://files.pythonhosted.org/packages/92/7b/df95faaf8746cce65704f1631a6626e5bb4604a499a0f63fc9103669deba/dulwich-0.22.8-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6bde2b13a05cc0ec2ecd4597a99896663544c40af1466121f4d046119b874ce3", size = 612529, upload-time = "2025-03-02T23:07:59.731Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a1/f9736e4a94f2d13220169c3293167e5d154508a6038613fcda8cc2515c55/dulwich-0.22.8-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6d446cb7d272a151934ad4b48ba691f32486d5267cf2de04ee3b5e05fc865326", size = 947961, upload-time = "2025-03-02T23:08:01.842Z" }, + { url = "https://files.pythonhosted.org/packages/3b/20/7d7a38b8409514365bd0bc046ced20f011daf363dba55434643a9cfbb484/dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f6338e6cf95cd76a0191b3637dc3caed1f988ae84d8e75f876d5cd75a8dd81a", size = 998944, upload-time = "2025-03-02T23:08:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/f4/4f/a95c197882dd93c5e3997f64d5e53cd70ceec4dcc8ff9eb8fc1eb0cab34f/dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e004fc532ea262f2d5f375068101ca4792becb9d4aa663b050f5ac31fda0bb5c", size = 1007748, upload-time = "2025-03-02T23:08:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/79/45/d29a9fca7960d8ef9eb7e2cc8a8049add3a2e831e48a56f07a5ae886ace6/dulwich-0.22.8-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bfdbc6fa477dee00d04e22d43a51571cd820cfaaaa886f0f155b8e29b3e3d45", size = 1053398, upload-time = "2025-03-02T23:08:06.29Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3a/2fdc2e85d9eea6324617a566138f60ffc2b3fdf89cd058aae0c4edb72a22/dulwich-0.22.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ae900c8e573f79d714c1d22b02cdadd50b64286dd7203028f0200f82089e4950", size = 613736, upload-time = "2025-03-02T23:08:07.662Z" }, + { url = "https://files.pythonhosted.org/packages/37/56/395c6d82d4d9eb7a7ab62939c99db5b746995b0f3ad3b31f43c15e3e07a0/dulwich-0.22.8-py3-none-any.whl", hash = "sha256:ffc7a02e62b72884de58baaa3b898b7f6427893e79b1289ffa075092efe59181", size = 273071, upload-time = "2025-03-02T23:08:09.013Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload-time = "2024-07-12T22:26:00.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, +] + +[[package]] +name = "github-action-utils" +version = "1.1.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/41/81/b70e3be8686c2011ec03eddf5a695496c9935818b18cde6a5f9553127a4e/github-action-utils-1.1.0.tar.gz", hash = "sha256:8aa40d90b89d814004160bb7e90b42cc07b55f41f66e4a4a32766d26c9ca3d61", size = 8860, upload-time = "2022-10-17T14:28:57.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/c8/d2a1c4ac43c9194316b8802b08502712ef242e6f325435409688e760b03a/github_action_utils-1.1.0-py2.py3-none-any.whl", hash = "sha256:bc84bac22e8a25ebe86370b08ff2c174960e468e899ffd313cb09d19629acefb", size = 8893, upload-time = "2022-10-17T14:28:56.215Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "msc-pyparser" +version = "1.2.2" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "ply" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/39/4a50f2ff69609d2d79a28df2679fccb70e48c291e73b6e05d5aeddc36d49/msc_pyparser-1.2.2.tar.gz", hash = "sha256:412de2418a35e49ba748cd7007058e03e6c0db3a51cedb6bc23799d62fa367bc", size = 47123, upload-time = "2025-02-21T13:35:01.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/6a/ff72190ebc1cb0850fc7ecc2413da46dea3b8581357e0e2aa0a98e171925/msc_pyparser-1.2.2-py2.py3-none-any.whl", hash = "sha256:978196044690e4a68d7686416aabf774d6b7be5f58f8f0510c36932bde0722ea", size = 48510, upload-time = "2025-02-21T13:34:58.915Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, +] + +[[package]] +name = "ply" +version = "3.11" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130, upload-time = "2018-02-15T19:01:31.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple/" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "semver" +version = "3.0.4" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple/" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, +] From 000e59dc04ac64d0de7487d2a7ef11d8d675e241 Mon Sep 17 00:00:00 2001 From: Max Leske <250711+theseion@users.noreply.github.com> Date: Tue, 6 May 2025 07:49:30 +0200 Subject: [PATCH 47/48] chore: drop support for Python 3.9, 3.10 StrEnum was introduced with 3.11 --- .github/workflows/test.yml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 18d36fc..c22f3aa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: matrix: # renovate: datasource=github-releases depName=coreruleset/coreruleset crs-version: ['4.13.0'] - python-version: ['3.9', '3.10', '3.11', '3.12'] + python-version: ['3.11', '3.12', '3.13'] steps: - name: "Checkout repo" diff --git a/pyproject.toml b/pyproject.toml index 1baee3f..29aadbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "CRS linter" authors = [ {name = "Ervin Hegedus", email = "airween@gmail.com"} ] -requires-python = ">=3.9" +requires-python = ">=3.11" license = "Apache-2.0" readme = "README.md" keywords = ["OWASP", "CRS", "linter"] From c9b3a7d9c1b77faeb7e6ec95b43baadffc0892fb Mon Sep 17 00:00:00 2001 From: Max Leske <250711+theseion@users.noreply.github.com> Date: Tue, 6 May 2025 07:50:38 +0200 Subject: [PATCH 48/48] deps: update CRS version to v4.14.0 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c22f3aa..33fb7db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: fail-fast: true matrix: # renovate: datasource=github-releases depName=coreruleset/coreruleset - crs-version: ['4.13.0'] + crs-version: ['4.14.0'] python-version: ['3.11', '3.12', '3.13'] steps: