safeuploads 1.0.1__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/bug_report.md +35 -0
  2. safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/config.yml +5 -0
  3. safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/feature_request.md +27 -0
  4. safeuploads-1.1.0/.forgejo/PULL_REQUEST_TEMPLATE.md +33 -0
  5. {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/scripts/check_conventional_commits.py +5 -3
  6. {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/workflows/conventional-commits.yml +3 -7
  7. safeuploads-1.1.0/.forgejo/workflows/docs.yml +59 -0
  8. {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/workflows/fuzz.yml +6 -9
  9. {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/workflows/lint-and-test.yml +16 -17
  10. {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/workflows/publish.yml +6 -9
  11. {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/workflows/renovate.yml +3 -8
  12. safeuploads-1.1.0/CHANGELOG.md +147 -0
  13. {safeuploads-1.0.1 → safeuploads-1.1.0}/PKG-INFO +17 -5
  14. {safeuploads-1.0.1 → safeuploads-1.1.0}/README.md +16 -4
  15. {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/index.md +13 -1
  16. {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/security/architecture.md +11 -11
  17. {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/security/threat-model.md +21 -2
  18. {safeuploads-1.0.1 → safeuploads-1.1.0}/examples/README.md +13 -5
  19. {safeuploads-1.0.1 → safeuploads-1.1.0}/examples/fastapi_example.py +125 -3
  20. {safeuploads-1.0.1 → safeuploads-1.1.0}/pyproject.toml +51 -1
  21. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/__init__.py +6 -3
  22. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/audit.py +4 -3
  23. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/config.py +467 -310
  24. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/enums.py +28 -0
  25. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/exceptions.py +29 -13
  26. safeuploads-1.1.0/safeuploads/file_validator.py +1212 -0
  27. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/inspectors/__init__.py +4 -2
  28. safeuploads-1.1.0/safeuploads/inspectors/base.py +34 -0
  29. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/inspectors/content_inspector.py +40 -63
  30. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/inspectors/gzip_inspector.py +16 -20
  31. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/inspectors/zip_inspector.py +44 -69
  32. safeuploads-1.1.0/safeuploads/py.typed +0 -0
  33. safeuploads-1.1.0/safeuploads/utils.py +311 -0
  34. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/base.py +6 -1
  35. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/compression_validator.py +67 -28
  36. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/extension_validator.py +3 -0
  37. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/unicode_validator.py +5 -1
  38. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/windows_validator.py +3 -0
  39. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/xml_validator.py +10 -3
  40. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/inspectors/test_zip_inspector.py +36 -0
  41. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_config.py +45 -0
  42. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_config_validation.py +63 -0
  43. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_enums.py +20 -0
  44. safeuploads-1.1.0/tests/test_fastapi_integration.py +164 -0
  45. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_file_validator.py +241 -0
  46. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_utils.py +37 -4
  47. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/test_compression_validator.py +89 -0
  48. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/test_unicode_validator.py +5 -1
  49. safeuploads-1.1.0/uv.lock +1110 -0
  50. safeuploads-1.0.1/.forgejo/workflows/docs.yml +0 -51
  51. safeuploads-1.0.1/.github/workflows/fuzz.yml +0 -51
  52. safeuploads-1.0.1/.github/workflows/lint&test.yml +0 -96
  53. safeuploads-1.0.1/.github/workflows/publish.yml +0 -67
  54. safeuploads-1.0.1/safeuploads/file_validator.py +0 -1028
  55. safeuploads-1.0.1/safeuploads/utils.py +0 -176
  56. safeuploads-1.0.1/uv.lock +0 -422
  57. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/agents/se-gitops-ci-specialist.agent.md +0 -0
  58. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/agents/se-security-reviewer.agent.md +0 -0
  59. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/agents/test-implementor.agent.md +0 -0
  60. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/agents/test-reviewer.agent.md +0 -0
  61. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/copilot-instructions.md +0 -0
  62. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/agents.instructions.md +0 -0
  63. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +0 -0
  64. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/instructions.instructions.md +0 -0
  65. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/prompt.instructions.md +0 -0
  66. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/python.instructions.md +0 -0
  67. {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/security-and-owasp.instructions.md +0 -0
  68. {safeuploads-1.0.1 → safeuploads-1.1.0}/.gitignore +0 -0
  69. {safeuploads-1.0.1 → safeuploads-1.1.0}/CODE_OF_CONDUCT.md +0 -0
  70. {safeuploads-1.0.1 → safeuploads-1.1.0}/CONTRIBUTING.md +0 -0
  71. {safeuploads-1.0.1 → safeuploads-1.1.0}/LICENSE.md +0 -0
  72. {safeuploads-1.0.1 → safeuploads-1.1.0}/SECURITY.md +0 -0
  73. {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/api.md +0 -0
  74. {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/rate-limiting.md +0 -0
  75. {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/security/integration-checklist.md +0 -0
  76. {safeuploads-1.0.1 → safeuploads-1.1.0}/mkdocs.yml +0 -0
  77. {safeuploads-1.0.1 → safeuploads-1.1.0}/renovate.json +0 -0
  78. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/protocols.py +0 -0
  79. {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/__init__.py +2 -2
  80. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/__init__.py +0 -0
  81. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/conftest.py +0 -0
  82. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/fuzz/__init__.py +0 -0
  83. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_config.py +0 -0
  84. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_filenames.py +0 -0
  85. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_images.py +0 -0
  86. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_zip.py +0 -0
  87. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/inspectors/__init__.py +0 -0
  88. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/inspectors/test_content_inspector.py +0 -0
  89. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/inspectors/test_gzip_inspector.py +0 -0
  90. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_audit.py +0 -0
  91. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_exceptions.py +0 -0
  92. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_performance.py +0 -0
  93. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_validate_activity_file.py +0 -0
  94. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_validate_gzip_file.py +0 -0
  95. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/__init__.py +0 -0
  96. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/test_extension_validator.py +0 -0
  97. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/test_windows_validator.py +0 -0
  98. {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/test_xml_validator.py +0 -0
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: 🐞 Bug report
3
+ about: Report a bug
4
+ title: "[BUG]: "
5
+ labels: [bug]
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Checklist
10
+
11
+ - [ ] I have searched **existing issues** to make sure this bug hasn't already been reported.
12
+ - [ ] I have checked the **documentation** at [https://safeuploads.endurain.com](https://safeuploads.endurain.com) and didn't find a solution.
13
+
14
+ ## Description
15
+
16
+ Describe the bug you encountered.
17
+
18
+ ## Steps to Reproduce
19
+
20
+ 1. Version
21
+ 2. Go to '...'
22
+ 3. Click on '...'
23
+ 4. See error
24
+
25
+ ## Expected Behavior
26
+
27
+ What did you expect to happen?
28
+
29
+ ## Screenshots, logs or files
30
+
31
+ If applicable, add screenshots, logs or files to help explain your problem. If you do not wish to upload sensitive information to the issue you can send that info to [joao@endurain.com](joao@endurain.com).
32
+
33
+ ## Additional Context
34
+
35
+ Add any other context about the problem here.
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: 📚 Documentation
4
+ url: https://safeuploads.endurain.com
5
+ about: Please check our documentation before creating an issue.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: 💡 Feature request
3
+ about: Suggest an idea or enhancement for this project
4
+ title: "[FEATURE]: "
5
+ labels: [enhancement]
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Summary
10
+
11
+ Briefly describe the feature you'd like to see.
12
+
13
+ ## Motivation
14
+
15
+ Why is this feature important? What problem does it solve?
16
+
17
+ ## Proposed Solution
18
+
19
+ How would this feature work? Describe any ideas you have.
20
+
21
+ ## Alternatives
22
+
23
+ Have you considered any alternatives?
24
+
25
+ ## Additional Context
26
+
27
+ Add screenshots, mockups, or examples if relevant.
@@ -0,0 +1,33 @@
1
+ ## What does this PR do?
2
+
3
+ A clear description of the change.
4
+
5
+ ## Related issue
6
+
7
+ Required for anything beyond a small bug fix or typo.
8
+ Don't forget to reference the issue so it auto-closes works, for example use `Closes #XYZ`
9
+
10
+ ## Type of change
11
+
12
+ Check all that apply
13
+
14
+ - [ ] Bug fix
15
+ - [ ] New feature (exception during freeze; link prior discussion/acknowledgment)
16
+ - [ ] Documentation
17
+ - [ ] Refactor / code quality
18
+ - [ ] Translation
19
+ - [ ] Other (describe below)
20
+
21
+ ## Checklist
22
+
23
+ - [ ] This PR is focused on a single concern
24
+ - [ ] An issue was opened and acknowledged before writing this code (if not a small fix)
25
+ - [ ] If this PR introduces a new feature during freeze, it was discussed and explicitly acknowledged first
26
+ - [ ] The PR is under ~300 lines changed, excluding lock files, migrations, and generated code
27
+ - [ ] Tests have been added or updated where applicable
28
+ - [ ] Documentation has been updated where applicable
29
+ - [ ] Commit messages follow Conventional Commits format
30
+
31
+ ## Notes for the reviewer
32
+
33
+ Anything that would help review this. Tricky areas, alternative approaches considered, screenshots, etc.
@@ -57,7 +57,7 @@ _HEADER_RE = re.compile(
57
57
  (?P<type>[A-Za-z]+)
58
58
  (?:\((?P<scope>[^)]+)\))?
59
59
  (?P<breaking>!)?
60
- :\
60
+ :\x20
61
61
  (?P<description>.+?)
62
62
  \s*$
63
63
  """,
@@ -102,6 +102,7 @@ def _safe_log_value(value: str) -> str:
102
102
 
103
103
 
104
104
  def _iter_messages(args: argparse.Namespace) -> Iterable[str]:
105
+ """Yield messages from stdin or CLI arguments."""
105
106
  if args.stdin:
106
107
  for line in sys.stdin:
107
108
  stripped = line.rstrip("\n")
@@ -112,6 +113,7 @@ def _iter_messages(args: argparse.Namespace) -> Iterable[str]:
112
113
 
113
114
 
114
115
  def main(argv: list[str] | None = None) -> int:
116
+ """Validate all messages and return a process exit code."""
115
117
  parser = argparse.ArgumentParser(
116
118
  description="Validate Conventional Commits headers.",
117
119
  )
@@ -150,8 +152,8 @@ def main(argv: list[str] | None = None) -> int:
150
152
 
151
153
  if failures:
152
154
  print(
153
- f"\n{failures} of {checked} message(s) failed Conventional Commits "
154
- "validation.",
155
+ f"\n{failures} of {checked} message(s) failed "
156
+ "Conventional Commits validation.",
155
157
  file=sys.stderr,
156
158
  )
157
159
  return 1
@@ -4,21 +4,17 @@ on:
4
4
  pull_request:
5
5
  types: [opened, edited, synchronize, reopened]
6
6
 
7
- permissions:
8
- contents: read
9
- pull-requests: read
10
-
11
7
  concurrency:
12
8
  group: ${{ forgejo.workflow }}-${{ forgejo.event.pull_request.number }}
13
9
  cancel-in-progress: true
14
10
 
15
11
  jobs:
16
12
  validate:
17
- runs-on: codeberg-small
13
+ runs-on: local-runner
18
14
 
19
15
  steps:
20
16
  - name: Checkout code
21
- uses: https://data.forgejo.org/actions/checkout@v6
17
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
22
18
  with:
23
19
  fetch-depth: 0
24
20
 
@@ -40,5 +36,5 @@ jobs:
40
36
  run: |
41
37
  set -euo pipefail
42
38
  git fetch --no-tags origin "$BASE_SHA" "$HEAD_SHA"
43
- git log --format=%s "$BASE_SHA".."$HEAD_SHA" \
39
+ git log --no-merges --format=%s "$BASE_SHA".."$HEAD_SHA" \
44
40
  | python3 .forgejo/scripts/check_conventional_commits.py --stdin
@@ -0,0 +1,59 @@
1
+ name: Deploy documentation
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ dry-run:
7
+ description: Build docs but do not deploy
8
+ required: false
9
+ default: false
10
+ type: boolean
11
+ push:
12
+ branches:
13
+ - main
14
+ - master
15
+ paths:
16
+ - 'docs/**'
17
+ - 'mkdocs.yml'
18
+ - 'safeuploads/**/*.py'
19
+ - '.forgejo/workflows/docs.yml'
20
+
21
+ concurrency:
22
+ group: ${{ forgejo.workflow }}-${{ forgejo.ref }}
23
+ cancel-in-progress: true
24
+
25
+ jobs:
26
+
27
+ deploy:
28
+
29
+ runs-on: local-runner
30
+
31
+ steps:
32
+ - name: Checkout code
33
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
34
+ with:
35
+ fetch-depth: 0
36
+
37
+ - name: Install uv
38
+ uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
39
+ with:
40
+ working-directory: .
41
+
42
+ - name: Sync documentation dependencies
43
+ working-directory: .
44
+ run: uv sync --frozen --only-group docs
45
+ - name: Build documentation
46
+ run: uv run --no-sync --project safeuploads mkdocs build --site-dir site
47
+
48
+ - name: Set dry-run mode
49
+ if: ${{ inputs.dry-run == true }}
50
+ run: echo "DOCS_DRY_RUN=true" >> "$FORGEJO_ENV"
51
+
52
+ - name: Deploy documentation to Codeberg Pages
53
+ if: env.DOCS_DRY_RUN != 'true'
54
+ uses: https://data.forgejo.org/actions/git-pages@5978530ffb9bbe196ad9fdc0c48fdaf1483de4cf # v2.1.0
55
+ with:
56
+ site: https://safeuploads.endurain.com/
57
+ server: codeberg.page
58
+ token: ${{ forge.token }}
59
+ source: site/
@@ -15,22 +15,19 @@ concurrency:
15
15
  jobs:
16
16
  fuzz:
17
17
  name: Fuzz
18
- runs-on: codeberg-medium-lazy
18
+ runs-on: local-runner
19
19
 
20
20
  steps:
21
21
  - name: Checkout repository
22
- uses: https://data.forgejo.org/actions/checkout@v4
22
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23
23
 
24
24
  - name: Install system dependencies
25
- run: sudo apt-get update && sudo apt-get install -y libmagic1
26
-
27
- - name: Set up Python 3.13
28
- uses: https://data.forgejo.org/actions/setup-python@v5
29
- with:
30
- python-version: "3.13"
25
+ run: apt-get update && apt-get install -y libmagic1
31
26
 
32
27
  - name: Install uv
33
- run: pip install uv
28
+ uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
29
+ with:
30
+ working-directory: .
34
31
 
35
32
  - name: Install dependencies
36
33
  run: uv sync --frozen --dev
@@ -23,16 +23,15 @@ jobs:
23
23
  runs-on: codeberg-tiny
24
24
 
25
25
  steps:
26
- - name: Checkout repository
27
- uses: https://data.forgejo.org/actions/checkout@v4
28
-
29
- - name: Set up Python 3.13
30
- uses: https://data.forgejo.org/actions/setup-python@v5
26
+ - name: Checkout code
27
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
31
28
  with:
32
- python-version: "3.13"
29
+ fetch-depth: 1
33
30
 
34
31
  - name: Install uv
35
- run: pip install uv
32
+ uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
33
+ with:
34
+ working-directory: .
36
35
 
37
36
  - name: Install dependencies
38
37
  run: uv sync --frozen --dev
@@ -43,6 +42,9 @@ jobs:
43
42
  - name: Check formatting with ruff
44
43
  run: uv run ruff format --check safeuploads tests examples
45
44
 
45
+ - name: Type check with mypy
46
+ run: uv run mypy
47
+
46
48
  # ----------------------------------------------------------------
47
49
  # Test
48
50
  # Runs the full pytest suite with coverage. Only runs after lint
@@ -50,26 +52,23 @@ jobs:
50
52
  # ----------------------------------------------------------------
51
53
  test:
52
54
  name: Test
53
- runs-on: codeberg-medium
55
+ runs-on: local-runner
54
56
  needs: lint
55
57
 
56
58
  steps:
57
59
  - name: Checkout repository
58
- uses: https://data.forgejo.org/actions/checkout@v4
60
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
59
61
 
60
62
  - name: Install system dependencies
61
- run: sudo apt-get update && sudo apt-get install -y libmagic1
62
-
63
- - name: Set up Python 3.13
64
- uses: https://data.forgejo.org/actions/setup-python@v5
65
- with:
66
- python-version: "3.13"
63
+ run: apt-get update && apt-get install -y libmagic1
67
64
 
68
65
  - name: Install uv
69
- run: pip install uv
66
+ uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
67
+ with:
68
+ working-directory: .
70
69
 
71
70
  - name: Install dependencies
72
- run: uv sync --frozen --dev
71
+ run: uv sync --frozen --dev --extra fastapi
73
72
 
74
73
  - name: Audit Python dependencies
75
74
  run: uvx pip-audit --require-hashes --disable-pip -r <(uv export --frozen --no-header --no-emit-project --no-dev)
@@ -16,11 +16,11 @@ concurrency:
16
16
  jobs:
17
17
  publish:
18
18
  name: Build & Publish
19
- runs-on: codeberg-medium
19
+ runs-on: local-runner
20
20
 
21
21
  steps:
22
22
  - name: Checkout repository
23
- uses: https://data.forgejo.org/actions/checkout@v4
23
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
24
24
 
25
25
  - name: Verify version matches input
26
26
  run: |
@@ -38,15 +38,12 @@ jobs:
38
38
  echo "Publishing version $PKG_VERSION"
39
39
 
40
40
  - name: Install system dependencies
41
- run: sudo apt-get update && sudo apt-get install -y libmagic1
42
-
43
- - name: Set up Python 3.13
44
- uses: https://data.forgejo.org/actions/setup-python@v5
45
- with:
46
- python-version: "3.13"
41
+ run: apt-get update && apt-get install -y libmagic1
47
42
 
48
43
  - name: Install uv
49
- run: pip install uv
44
+ uses: https://data.forgejo.org/astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
45
+ with:
46
+ working-directory: .
50
47
 
51
48
  - name: Install dependencies
52
49
  run: uv sync --frozen --dev
@@ -5,11 +5,6 @@ on:
5
5
  schedule:
6
6
  - cron: '0 4 * * 0'
7
7
 
8
- permissions:
9
- contents: write
10
- pull-requests: write
11
- issues: write
12
-
13
8
  concurrency:
14
9
  group: ${{ forgejo.workflow }}-${{ forgejo.ref }}
15
10
  cancel-in-progress: false
@@ -18,16 +13,16 @@ jobs:
18
13
 
19
14
  renovate:
20
15
 
21
- runs-on: codeberg-small
16
+ runs-on: local-runner
22
17
 
23
18
  steps:
24
19
  - name: Checkout code
25
- uses: https://data.forgejo.org/actions/checkout@v4
20
+ uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
26
21
  with:
27
22
  fetch-depth: 1
28
23
 
29
24
  - name: Run Renovate
30
- uses: docker://ghcr.io/renovatebot/renovate:latest
25
+ uses: docker://ghcr.io/renovatebot/renovate@sha256:38aac6e29e09288bc3fb3b95ba9445962508a1081013144d5b6ec5a6735707b9 # 43.168.5
31
26
  env:
32
27
  LOG_LEVEL: info
33
28
  RENOVATE_CONFIG_FILE: renovate.json
@@ -0,0 +1,147 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on
6
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
7
+ project adheres to
8
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
+
10
+ ## [1.1.0] - 2026-07-20
11
+
12
+ ### Added
13
+
14
+ - `verify_zip_decompression` option that decompresses every ZIP entry
15
+ to reject archives with forged central-directory metadata.
16
+ - Custom `executor` parameter on `FileValidator` to offload blocking
17
+ inspection to a bounded thread pool instead of the default
18
+ `asyncio` pool.
19
+ - Instance-level configuration validation and isolation so a
20
+ per-instance `limits` object never mutates the shared class
21
+ defaults.
22
+ - New `SecurityLimits` size limits plus additional recursable-archive
23
+ types and script patterns for deeper threat detection.
24
+ - `BaseInspector` base class sharing configuration and audit state
25
+ across inspectors.
26
+ - PEP 561 `py.typed` marker so downstream projects consume the shipped
27
+ type hints.
28
+ - Integration tests exercising a real Starlette `UploadFile` and an
29
+ end-to-end FastAPI multipart request.
30
+
31
+ ### Changed
32
+
33
+ - `ErrorCode` is now a `StrEnum`. This is backward compatible: members
34
+ still compare and hash equal to their string values, so existing
35
+ comparisons and JSON serialization are unaffected.
36
+ - Validation flow refactored around a single generic runner for
37
+ consistent audit logging and error mapping.
38
+ - Thread safety hardened: access to the shared `python-magic` instance
39
+ is locked, and blocking inspection is offloaded off the event loop.
40
+ - File-size validation now rejects uploads whose declared size already
41
+ exceeds the limit and verifies the real byte count by streaming.
42
+ - **Potentially breaking:** the public allow-list constants
43
+ (`ALLOWED_IMAGE_MIMES`, `ALLOWED_ZIP_MIMES`,
44
+ `ALLOWED_IMAGE_EXTENSIONS`, `ALLOWED_ZIP_EXTENSIONS`) are now
45
+ immutable `frozenset`s. Read-only use is unaffected; in-place
46
+ mutation (e.g. `.add(...)`) is no longer supported — assign a new
47
+ set instead.
48
+
49
+ ### Fixed
50
+
51
+ - Corrected the filename-sanitization debug log so it reports the true
52
+ original filename rather than the already-sanitized value.
53
+ - Documentation URLs updated to the new site.
54
+
55
+ ## [1.0.1] - 2026-06-03
56
+
57
+ ### Fixed
58
+
59
+ - `_stream_to_temp_file` now seeks the original `UploadFile` back to
60
+ position 0 (#3).
61
+
62
+ ### Changed
63
+
64
+ - Dependency version bumps.
65
+
66
+ ## [1.0.0] - 2026-03-26
67
+
68
+ First stable, production-ready release. Every layer of the validation
69
+ pipeline was expanded, hardened, and documented since v0.1.2.
70
+
71
+ ### Added
72
+
73
+ - **Activity & gzip validation:** `validate_activity_file` (GPX/TCX/FIT)
74
+ and `validate_gzip_file` with decompression-bomb detection, plus
75
+ `XmlSecurityValidator` using `defusedxml` to block XXE, DTD, and
76
+ entity-expansion attacks in XML-based activity files.
77
+ - **Audit logging:** `SecurityAuditLogger`, `AuditEvent`, and
78
+ `AuditEventType` with per-request correlation IDs, toggled by
79
+ `enable_audit_logging`.
80
+ - **Content security inspector:** `ContentSecurityInspector` for
81
+ malware-signature scanning, script/webshell marker detection, and
82
+ polyglot detection, controlled by `enable_content_analysis` and
83
+ `content_scan_max_size`.
84
+ - **Nested ZIP complexity checks:** recursive inspection with
85
+ cumulative entry, depth, hash, and timing checks, archive-quine and
86
+ ZIP-of-ZIPs detection, and a new `max_total_entries_recursive` limit.
87
+
88
+ ### Changed
89
+
90
+ - **Security hardening:** streaming file-size enforcement via
91
+ `chunk_size`, tightened ZIP inspection (null-byte filename detection,
92
+ monotonic timers, 512-byte content scans, sanitized error messages),
93
+ and a hardened MIME cache (256 → 64 entries, extension-only
94
+ derivation).
95
+ - `defusedxml` added as a runtime dependency.
96
+ - FastAPI example now binds to `127.0.0.1` instead of `0.0.0.0`.
97
+ - **Performance:** pre-computed `frozenset` collections, cached lookup
98
+ sets at initialization, and LRU-cached MIME guessing.
99
+
100
+ ### Other
101
+
102
+ - Hypothesis fuzz tests and integration suites; GitHub Actions for
103
+ lint/test, fuzzing, docs, and PyPI publish; Dependabot and
104
+ `CODEOWNERS`; new security docs (threat model, architecture,
105
+ integration checklist).
106
+
107
+ ## [0.1.2] - 2025-10-31
108
+
109
+ ### Fixed
110
+
111
+ - Added `BinaryFileCategory` to the exported enums in
112
+ `safeuploads/__init__.py` so it is available for external use.
113
+
114
+ ## [0.1.1] - 2025-10-31
115
+
116
+ ### Added
117
+
118
+ - `max_number_files_same_type` in `SecurityLimits`; `ZipContentInspector`
119
+ now uses this configurable limit instead of a hardcoded value when
120
+ checking for excessive files of the same type in a ZIP archive.
121
+ - `BinaryFileCategory` enum categorizing binary file extensions
122
+ (starting with fitness files such as `.fit`); `ZipContentInspector`
123
+ skips script-content checks for these binary files.
124
+
125
+ ## [0.1.0] - 2025-10-30
126
+
127
+ Initial release.
128
+
129
+ ### Added
130
+
131
+ - **Filename security:** Unicode normalization, directory-traversal
132
+ protection, Windows reserved-name blocking.
133
+ - **Extension validation:** configurable allow/block lists with
134
+ dangerous-extension detection.
135
+ - **ZIP bomb protection:** compression-ratio limits, nested-archive
136
+ inspection, size constraints.
137
+ - **MIME type verification:** magic-number validation for common file
138
+ types.
139
+ - Framework-agnostic async validation, a rich exception hierarchy with
140
+ machine-readable error codes, secure defaults, and full type hints.
141
+
142
+ [1.1.0]: https://codeberg.org/endurain-project/safeuploads/compare/v1.0.1...v1.1.0
143
+ [1.0.1]: https://codeberg.org/endurain-project/safeuploads/releases/tag/v1.0.1
144
+ [1.0.0]: https://codeberg.org/endurain-project/safeuploads/releases/tag/v1.0.0
145
+ [0.1.2]: https://codeberg.org/endurain-project/safeuploads/releases/tag/v0.1.2
146
+ [0.1.1]: https://codeberg.org/endurain-project/safeuploads/releases/tag/v0.1.1
147
+ [0.1.0]: https://codeberg.org/endurain-project/safeuploads/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: safeuploads
3
- Version: 1.0.1
3
+ Version: 1.1.0
4
4
  Summary: A comprehensive file security system for validating uploads and preventing attacks
5
5
  Author-email: João Vitória Silva <joao@endurain.com>
6
6
  License: MIT
@@ -19,7 +19,7 @@ Description-Content-Type: text/markdown
19
19
  [![PyPI version](https://img.shields.io/pypi/v/safeuploads)](https://pypi.org/project/safeuploads/)
20
20
  [![PyPI downloads](https://img.shields.io/pypi/dm/safeuploads)](https://pypi.org/project/safeuploads/)
21
21
  [![Python](https://img.shields.io/badge/python-3.13%2B-blue)](https://pypi.org/project/safeuploads/)
22
- [![Docs](https://img.shields.io/badge/docs-endurain--project.codeberg.page-blue)](https://endurain-project.codeberg.page/safeuploads/)
22
+ [![Docs](https://img.shields.io/badge/docs-endurain--project.codeberg.page-blue)](https://safeuploads.endurain.com/)
23
23
  [![Stars](https://img.shields.io/badge/dynamic/json?url=https://codeberg.org/api/v1/repos/endurain-project/safeuploads&query=$.stars_count&label=stars&logo=codeberg)](https://codeberg.org/endurain-project/safeuploads)
24
24
 
25
25
  Secure file upload validation for Python 3.13+ applications. Catches dangerous filenames, malicious extensions, Windows reserved names, and compression-based attacks before you accept an upload.
@@ -84,7 +84,19 @@ config = FileSecurityConfig()
84
84
  config.limits.max_image_size = 10 * 1024 * 1024 # 10 MiB
85
85
  config.limits.max_compression_ratio = 50
86
86
 
87
+ # Opt in to strict ZIP checking: decompress every entry to
88
+ # reject archives with forged central-directory metadata
89
+ config.limits.verify_zip_decompression = True
90
+
87
91
  validator = FileValidator(config=config)
92
+
93
+ # Optionally offload blocking inspection to a bounded pool
94
+ from concurrent.futures import ThreadPoolExecutor
95
+
96
+ pooled_validator = FileValidator(
97
+ config=config,
98
+ executor=ThreadPoolExecutor(max_workers=4),
99
+ )
88
100
  ```
89
101
 
90
102
  ## Exception Handling
@@ -113,7 +125,7 @@ except FileValidationError as err:
113
125
 
114
126
  - **Filename Security**: Unicode normalization, directory traversal prevention, Windows reserved names blocking
115
127
  - **Extension Validation**: Allow/block lists with configurable rules, dangerous extension detection
116
- - **Compression Security**: ZIP bomb detection, nested archive inspection, recursive structure and quine detection, size and ratio limits
128
+ - **Compression Security**: ZIP bomb detection, nested archive inspection, recursive structure and quine detection, size and ratio limits, optional strict decompression verification
117
129
  - **Content Inspection**: Deep ZIP content analysis with configurable depth and entry limits
118
130
  - **MIME Type Verification**: Magic number validation for images, ZIP, activity files, and gzip
119
131
  - **Streaming Validation**: Memory-efficient processing via `SpooledTemporaryFile` for large files
@@ -134,11 +146,11 @@ except FileValidationError as err:
134
146
 
135
147
  ## Documentation
136
148
 
137
- Full documentation is available at the [safeuploads docs site](https://endurain-project.codeberg.page/safeuploads/).
149
+ Full documentation is available at the [safeuploads docs site](https://safeuploads.endurain.com/).
138
150
 
139
151
  ## License
140
152
 
141
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
153
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details.
142
154
 
143
155
  ## Contributing
144
156
 
@@ -5,7 +5,7 @@
5
5
  [![PyPI version](https://img.shields.io/pypi/v/safeuploads)](https://pypi.org/project/safeuploads/)
6
6
  [![PyPI downloads](https://img.shields.io/pypi/dm/safeuploads)](https://pypi.org/project/safeuploads/)
7
7
  [![Python](https://img.shields.io/badge/python-3.13%2B-blue)](https://pypi.org/project/safeuploads/)
8
- [![Docs](https://img.shields.io/badge/docs-endurain--project.codeberg.page-blue)](https://endurain-project.codeberg.page/safeuploads/)
8
+ [![Docs](https://img.shields.io/badge/docs-endurain--project.codeberg.page-blue)](https://safeuploads.endurain.com/)
9
9
  [![Stars](https://img.shields.io/badge/dynamic/json?url=https://codeberg.org/api/v1/repos/endurain-project/safeuploads&query=$.stars_count&label=stars&logo=codeberg)](https://codeberg.org/endurain-project/safeuploads)
10
10
 
11
11
  Secure file upload validation for Python 3.13+ applications. Catches dangerous filenames, malicious extensions, Windows reserved names, and compression-based attacks before you accept an upload.
@@ -70,7 +70,19 @@ config = FileSecurityConfig()
70
70
  config.limits.max_image_size = 10 * 1024 * 1024 # 10 MiB
71
71
  config.limits.max_compression_ratio = 50
72
72
 
73
+ # Opt in to strict ZIP checking: decompress every entry to
74
+ # reject archives with forged central-directory metadata
75
+ config.limits.verify_zip_decompression = True
76
+
73
77
  validator = FileValidator(config=config)
78
+
79
+ # Optionally offload blocking inspection to a bounded pool
80
+ from concurrent.futures import ThreadPoolExecutor
81
+
82
+ pooled_validator = FileValidator(
83
+ config=config,
84
+ executor=ThreadPoolExecutor(max_workers=4),
85
+ )
74
86
  ```
75
87
 
76
88
  ## Exception Handling
@@ -99,7 +111,7 @@ except FileValidationError as err:
99
111
 
100
112
  - **Filename Security**: Unicode normalization, directory traversal prevention, Windows reserved names blocking
101
113
  - **Extension Validation**: Allow/block lists with configurable rules, dangerous extension detection
102
- - **Compression Security**: ZIP bomb detection, nested archive inspection, recursive structure and quine detection, size and ratio limits
114
+ - **Compression Security**: ZIP bomb detection, nested archive inspection, recursive structure and quine detection, size and ratio limits, optional strict decompression verification
103
115
  - **Content Inspection**: Deep ZIP content analysis with configurable depth and entry limits
104
116
  - **MIME Type Verification**: Magic number validation for images, ZIP, activity files, and gzip
105
117
  - **Streaming Validation**: Memory-efficient processing via `SpooledTemporaryFile` for large files
@@ -120,11 +132,11 @@ except FileValidationError as err:
120
132
 
121
133
  ## Documentation
122
134
 
123
- Full documentation is available at the [safeuploads docs site](https://endurain-project.codeberg.page/safeuploads/).
135
+ Full documentation is available at the [safeuploads docs site](https://safeuploads.endurain.com/).
124
136
 
125
137
  ## License
126
138
 
127
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
139
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details.
128
140
 
129
141
  ## Contributing
130
142