safeuploads 1.1.1__tar.gz → 1.1.3__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.1 → safeuploads-1.1.3}/.github/workflows/audit-safeuploads.yml +1 -1
  2. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/workflows/conventional-commits.yml +7 -2
  3. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/workflows/docs.yml +2 -2
  4. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/workflows/fuzz.yml +1 -1
  5. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/workflows/lint-and-test.yml +31 -3
  6. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/workflows/publish-safeuploads.yml +7 -7
  7. safeuploads-1.1.3/.github/workflows/test-matrix-safeuploads.yml +104 -0
  8. safeuploads-1.1.3/CHANGELOG.md +123 -0
  9. {safeuploads-1.1.1 → safeuploads-1.1.3}/PKG-INFO +2 -2
  10. {safeuploads-1.1.1 → safeuploads-1.1.3}/pyproject.toml +11 -2
  11. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/protocols.py +6 -5
  12. safeuploads-1.1.3/uv.lock +1335 -0
  13. safeuploads-1.1.1/CHANGELOG.md +0 -161
  14. safeuploads-1.1.1/uv.lock +0 -1120
  15. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/CODEOWNERS +0 -0
  16. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/FUNDING.yml +0 -0
  17. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  18. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  19. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  20. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  21. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/agents/se-gitops-ci-specialist.agent.md +0 -0
  22. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/agents/se-security-reviewer.agent.md +0 -0
  23. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/agents/test-implementor.agent.md +0 -0
  24. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/agents/test-reviewer.agent.md +0 -0
  25. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/copilot-instructions.md +0 -0
  26. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/dependabot.yml +0 -0
  27. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/instructions/agents.instructions.md +0 -0
  28. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +0 -0
  29. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/instructions/instructions.instructions.md +0 -0
  30. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/instructions/prompt.instructions.md +0 -0
  31. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/instructions/python.instructions.md +0 -0
  32. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/instructions/security-and-owasp.instructions.md +0 -0
  33. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/scripts/check_conventional_commits.py +0 -0
  34. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/scripts/smoke_import.py +0 -0
  35. {safeuploads-1.1.1 → safeuploads-1.1.3}/.github/workflows/mirror.yml +0 -0
  36. {safeuploads-1.1.1 → safeuploads-1.1.3}/.gitignore +0 -0
  37. {safeuploads-1.1.1 → safeuploads-1.1.3}/CODE_OF_CONDUCT.md +0 -0
  38. {safeuploads-1.1.1 → safeuploads-1.1.3}/CONTRIBUTING.md +0 -0
  39. {safeuploads-1.1.1 → safeuploads-1.1.3}/LICENSE.md +0 -0
  40. {safeuploads-1.1.1 → safeuploads-1.1.3}/README.md +0 -0
  41. {safeuploads-1.1.1 → safeuploads-1.1.3}/SECURITY.md +0 -0
  42. {safeuploads-1.1.1 → safeuploads-1.1.3}/docs/api.md +0 -0
  43. {safeuploads-1.1.1 → safeuploads-1.1.3}/docs/index.md +0 -0
  44. {safeuploads-1.1.1 → safeuploads-1.1.3}/docs/rate-limiting.md +0 -0
  45. {safeuploads-1.1.1 → safeuploads-1.1.3}/docs/security/architecture.md +0 -0
  46. {safeuploads-1.1.1 → safeuploads-1.1.3}/docs/security/integration-checklist.md +0 -0
  47. {safeuploads-1.1.1 → safeuploads-1.1.3}/docs/security/threat-model.md +0 -0
  48. {safeuploads-1.1.1 → safeuploads-1.1.3}/examples/README.md +0 -0
  49. {safeuploads-1.1.1 → safeuploads-1.1.3}/examples/fastapi_example.py +0 -0
  50. {safeuploads-1.1.1 → safeuploads-1.1.3}/mkdocs.yml +0 -0
  51. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/__init__.py +0 -0
  52. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/audit.py +0 -0
  53. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/config.py +0 -0
  54. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/enums.py +0 -0
  55. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/exceptions.py +0 -0
  56. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/file_validator.py +0 -0
  57. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/inspectors/__init__.py +0 -0
  58. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/inspectors/base.py +0 -0
  59. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/inspectors/content_inspector.py +0 -0
  60. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/inspectors/gzip_inspector.py +0 -0
  61. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/inspectors/zip_inspector.py +0 -0
  62. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/py.typed +0 -0
  63. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/utils.py +0 -0
  64. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/validators/__init__.py +0 -0
  65. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/validators/base.py +0 -0
  66. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/validators/compression_validator.py +0 -0
  67. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/validators/extension_validator.py +0 -0
  68. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/validators/unicode_validator.py +0 -0
  69. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/validators/windows_validator.py +0 -0
  70. {safeuploads-1.1.1 → safeuploads-1.1.3}/safeuploads/validators/xml_validator.py +0 -0
  71. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/__init__.py +0 -0
  72. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/conftest.py +0 -0
  73. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/fuzz/__init__.py +0 -0
  74. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/fuzz/test_fuzz_config.py +0 -0
  75. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/fuzz/test_fuzz_filenames.py +0 -0
  76. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/fuzz/test_fuzz_images.py +0 -0
  77. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/fuzz/test_fuzz_zip.py +0 -0
  78. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/inspectors/__init__.py +0 -0
  79. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/inspectors/test_content_inspector.py +0 -0
  80. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/inspectors/test_gzip_inspector.py +0 -0
  81. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/inspectors/test_zip_inspector.py +0 -0
  82. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_audit.py +0 -0
  83. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_config.py +0 -0
  84. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_config_validation.py +0 -0
  85. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_enums.py +0 -0
  86. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_exceptions.py +0 -0
  87. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_fastapi_integration.py +0 -0
  88. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_file_validator.py +0 -0
  89. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_performance.py +0 -0
  90. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_utils.py +0 -0
  91. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_validate_activity_file.py +0 -0
  92. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/test_validate_gzip_file.py +0 -0
  93. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/validators/__init__.py +0 -0
  94. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/validators/test_compression_validator.py +0 -0
  95. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/validators/test_extension_validator.py +0 -0
  96. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/validators/test_unicode_validator.py +0 -0
  97. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/validators/test_windows_validator.py +0 -0
  98. {safeuploads-1.1.1 → safeuploads-1.1.3}/tests/validators/test_xml_validator.py +0 -0
@@ -39,7 +39,7 @@ jobs:
39
39
  fetch-depth: 1
40
40
 
41
41
  - name: Install uv
42
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
42
+ uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
43
43
  with:
44
44
  # Resolve uv from `[tool.uv] required-version` in pyproject.toml, so CI
45
45
  # and local development are held to the same floor and ceiling.
@@ -22,8 +22,13 @@ jobs:
22
22
  fetch-depth: 0
23
23
 
24
24
  - name: Validate PR title
25
- # The PR title is passed through an environment variable to prevent
26
- # shell injection via crafted titles (see security-and-owasp guidelines).
25
+ # The title only changes on opened/edited/reopened. Skipping it on
26
+ # `synchronize` keeps a push to a branch shared with another open PR
27
+ # (for example a WIP umbrella PR) from failing on that PR's title
28
+ # before the commit-range check below can run.
29
+ if: github.event.action != 'synchronize'
30
+ # The PR title is untrusted data: passed through an environment variable
31
+ # (never interpolated into the shell) to the trusted base-ref checker.
27
32
  env:
28
33
  PR_TITLE: ${{ github.event.pull_request.title }}
29
34
  run: |
@@ -40,7 +40,7 @@ jobs:
40
40
  fetch-depth: 1
41
41
 
42
42
  - name: Install uv
43
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
43
+ uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
44
44
  with:
45
45
  # Resolve uv from `[tool.uv] required-version` in pyproject.toml, so CI
46
46
  # and local development are held to the same floor and ceiling.
@@ -78,4 +78,4 @@ jobs:
78
78
  steps:
79
79
  - name: Deploy to GitHub Pages
80
80
  id: deployment
81
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
81
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -28,7 +28,7 @@ jobs:
28
28
  run: sudo apt-get update && sudo apt-get install -y libmagic1
29
29
 
30
30
  - name: Install uv
31
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
31
+ uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
32
32
  with:
33
33
  version-file: pyproject.toml
34
34
 
@@ -32,7 +32,7 @@ jobs:
32
32
  fetch-depth: 1
33
33
 
34
34
  - name: Install uv
35
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
35
+ uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
36
36
  with:
37
37
  # Resolve uv from `[tool.uv] required-version` in pyproject.toml, so CI
38
38
  # and local development are held to the same floor and ceiling.
@@ -50,6 +50,34 @@ jobs:
50
50
  - name: Type check with mypy
51
51
  run: uv run mypy
52
52
 
53
+ # ----------------------------------------------------------------
54
+ # Type check with ty (advisory)
55
+ # ty is pre-1.0 and runs beside mypy for evaluation: findings are
56
+ # reported but never block a merge. mypy is the gate.
57
+ # ----------------------------------------------------------------
58
+ typecheck-ty:
59
+ name: Type check (ty, advisory)
60
+ runs-on: ubuntu-latest
61
+ continue-on-error: true
62
+
63
+ steps:
64
+ - name: Checkout code
65
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
66
+ with:
67
+ fetch-depth: 1
68
+
69
+ - name: Install uv
70
+ uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
71
+ with:
72
+ version-file: pyproject.toml
73
+
74
+ - name: Install dependencies
75
+ # Same environment as the mypy step: ty resolves third-party types from it.
76
+ run: uv sync --frozen --dev --extra fastapi
77
+
78
+ - name: Type check (ty)
79
+ run: uv run --no-sync ty check --python .venv
80
+
53
81
  # ----------------------------------------------------------------
54
82
  # Test
55
83
  # Runs the full pytest suite with coverage. Only runs after lint
@@ -68,7 +96,7 @@ jobs:
68
96
  run: sudo apt-get update && sudo apt-get install -y libmagic1
69
97
 
70
98
  - name: Install uv
71
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
99
+ uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
72
100
  with:
73
101
  version-file: pyproject.toml
74
102
 
@@ -82,7 +110,7 @@ jobs:
82
110
  run: uv run pytest --cov-fail-under=90
83
111
 
84
112
  - name: Upload coverage report
85
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
113
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
86
114
  if: always()
87
115
  with:
88
116
  name: safeuploads-coverage
@@ -36,7 +36,7 @@ jobs:
36
36
  persist-credentials: false
37
37
 
38
38
  - name: Install uv
39
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
39
+ uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
40
40
  with:
41
41
  # Resolve uv from `[tool.uv] required-version` in pyproject.toml, so CI
42
42
  # and local development are held to the same floor and ceiling.
@@ -122,7 +122,7 @@ jobs:
122
122
  # attests everything it downloads, so a stray checksum or SBOM file there
123
123
  # would end up in the attestation.
124
124
  - name: Upload build artifacts
125
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
125
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
126
126
  with:
127
127
  name: safeuploads-dist
128
128
  path: dist/
@@ -130,7 +130,7 @@ jobs:
130
130
  retention-days: 90
131
131
 
132
132
  - name: Upload release metadata
133
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
133
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
134
134
  with:
135
135
  name: safeuploads-release-metadata
136
136
  path: release-metadata/
@@ -149,22 +149,22 @@ jobs:
149
149
  id-token: write
150
150
  steps:
151
151
  - name: Install uv
152
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
152
+ uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
153
153
  with:
154
154
  # This job never checks the repository out, so there is no
155
155
  # pyproject.toml to read `required-version` from; keep this in step
156
156
  # with the floor declared there.
157
- version: "0.12.5"
157
+ version: "0.13.5"
158
158
  enable-cache: false
159
159
 
160
160
  - name: Download build artifacts
161
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
161
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
162
162
  with:
163
163
  name: safeuploads-dist
164
164
  path: dist/
165
165
 
166
166
  - name: Generate PEP 740 attestations
167
- uses: astral-sh/attest-action@f589a42a7efb6fe400b4f400de60b4bc90390027 # v0.0.6
167
+ uses: astral-sh/attest-action@39b6ce43875c196a579e31635b11aa2a2036384a # v0.0.7
168
168
 
169
169
  - name: Publish to PyPI
170
170
  run: uv publish --trusted-publishing always dist/*.whl dist/*.tar.gz
@@ -0,0 +1,104 @@
1
+ name: Test matrix safeuploads
2
+
3
+ # Portability matrix on top of the fast default `Lint & Test` workflow:
4
+ # * every supported Python version (3.13, 3.14, 3.15) with the full
5
+ # dependency set (dev group + fastapi extra), proving the suite
6
+ # passes on every Python the package declares support for; and
7
+ # * a bare `import safeuploads` with only the base dependencies
8
+ # installed (no optional extras, no dev group), proving
9
+ # python-magic and defusedxml are truly sufficient on their own
10
+ # and the `fastapi` extra stays optional.
11
+
12
+ on:
13
+ push:
14
+ branches:
15
+ - main
16
+ paths: &paths
17
+ - safeuploads/**
18
+ - tests/**
19
+ - pyproject.toml
20
+ - uv.lock
21
+ - .github/workflows/test-matrix-safeuploads.yml
22
+ - .github/scripts/smoke_import.py
23
+ pull_request:
24
+ branches:
25
+ - main
26
+ paths: *paths
27
+
28
+ permissions:
29
+ contents: read
30
+
31
+ concurrency:
32
+ group: ${{ github.workflow }}-${{ github.ref }}
33
+ cancel-in-progress: true
34
+
35
+ jobs:
36
+ # 1. Every supported Python version, with the full dependency set.
37
+ test-python-matrix:
38
+ name: safeuploads (py${{ matrix.python }})
39
+ runs-on: ubuntu-latest
40
+ strategy:
41
+ fail-fast: false
42
+ matrix:
43
+ python: ["3.13", "3.14", "3.15"]
44
+ steps:
45
+ - name: Checkout code
46
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
47
+ with:
48
+ fetch-depth: 1
49
+
50
+ - name: Install system dependencies
51
+ run: sudo apt-get update && sudo apt-get install -y libmagic1
52
+
53
+ - name: Install uv
54
+ uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
55
+ with:
56
+ # Resolve uv from `[tool.uv] required-version` in pyproject.toml, so CI
57
+ # and local development are held to the same floor and ceiling.
58
+ version-file: pyproject.toml
59
+
60
+ - name: Sync dependencies
61
+ run: uv sync --frozen --dev --extra fastapi --python ${{ matrix.python }}
62
+
63
+ - name: Run tests
64
+ run: uv run --no-sync --python ${{ matrix.python }} pytest
65
+
66
+ # 2. Bare import with only the base dependencies (no extras, no dev group):
67
+ # proves `import safeuploads` works with zero extras installed.
68
+ import-base-deps:
69
+ name: safeuploads (bare import, no extras)
70
+ runs-on: ubuntu-latest
71
+ steps:
72
+ - name: Checkout code
73
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
74
+ with:
75
+ fetch-depth: 1
76
+
77
+ - name: Install system dependencies
78
+ run: sudo apt-get update && sudo apt-get install -y libmagic1
79
+
80
+ - name: Install uv
81
+ uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
82
+ with:
83
+ # Resolve uv from `[tool.uv] required-version` in pyproject.toml, so CI
84
+ # and local development are held to the same floor and ceiling.
85
+ version-file: pyproject.toml
86
+
87
+ - name: Install base dependencies only (no optional extras, no dev group)
88
+ # --no-dev installs the base runtime dependencies + the project only;
89
+ # the fastapi extra and the dev dependency group stay out.
90
+ # --no-editable installs safeuploads as a real distribution (not a
91
+ # path into the checkout), so the smoke test actually validates an
92
+ # install.
93
+ run: uv sync --frozen --no-dev --no-editable
94
+
95
+ # Run from outside the repository root so the local `safeuploads/`
96
+ # package directory cannot shadow the installed distribution:
97
+ # otherwise this would validate the working tree rather than what a
98
+ # user actually gets.
99
+ - name: Import safeuploads without optional extras
100
+ run: |
101
+ set -euo pipefail
102
+ cp -r .venv "$RUNNER_TEMP/base-venv"
103
+ cd "$RUNNER_TEMP"
104
+ VIRTUAL_ENV="$RUNNER_TEMP/base-venv" uv run --no-project python "$GITHUB_WORKSPACE/.github/scripts/smoke_import.py"
@@ -0,0 +1,123 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [1.1.3] - 2026-10-22
8
+
9
+ ### Changed
10
+
11
+ - Development: the Astral `ty` type checker runs beside mypy as an advisory CI job (`Type check (ty, advisory)`); mypy remains the gate.
12
+ - `SeekableFile.read` and `seek` take positional-only parameters, so `BytesIO` and `SpooledTemporaryFile` satisfy the protocol under `ty`. No runtime change.
13
+ - `UploadFileProtocol.seek` may return `int | None`, so FastAPI/Starlette's `UploadFile` (whose `seek` returns `None`) satisfies the protocol under strict type checking. No runtime change.
14
+
15
+ ## [1.1.2] - 2026-10-22
16
+
17
+ ### Changed
18
+
19
+ - Updated all dependency groups to their latest versions.
20
+ - Updated uv to 0.13.5.
21
+ - Updated GitHub Actions to their latest releases, pinned by commit hash.
22
+
23
+ ## [1.1.1] - 2026-08-19
24
+
25
+ ### Changed
26
+
27
+ - Migrated CI/CD from Forgejo/Codeberg to GitHub Actions: lint & test, scheduled fuzz, docs deploy, PyPI publish, and Conventional Commits checks.
28
+ - Added Dependabot configuration, a `CODEOWNERS` file, and a scheduled dependency-vulnerability audit workflow (`pip-audit`).
29
+ - Updated all repository, documentation, and badge links from Codeberg to GitHub.
30
+
31
+ ## [1.1.0] - 2026-07-20
32
+
33
+
34
+ ### Added
35
+
36
+ - `verify_zip_decompression` option that decompresses every ZIP entry to reject archives with forged central-directory metadata.
37
+ - Custom `executor` parameter on `FileValidator` to offload blocking inspection to a bounded thread pool instead of the default `asyncio` pool.
38
+ - Instance-level configuration validation and isolation so a per-instance `limits` object never mutates the shared class defaults.
39
+ - New `SecurityLimits` size limits plus additional recursable-archive types and script patterns for deeper threat detection.
40
+ - `BaseInspector` base class sharing configuration and audit state across inspectors.
41
+ - PEP 561 `py.typed` marker so downstream projects consume the shipped type hints.
42
+ - Integration tests exercising a real Starlette `UploadFile` and an end-to-end FastAPI multipart request.
43
+
44
+ ### Changed
45
+
46
+ - `ErrorCode` is now a `StrEnum`. This is backward compatible: members still compare and hash equal to their string values, so existing comparisons and JSON serialization are unaffected.
47
+ - Validation flow refactored around a single generic runner for consistent audit logging and error mapping.
48
+ - Thread safety hardened: access to the shared `python-magic` instance is locked, and blocking inspection is offloaded off the event loop.
49
+ - File-size validation now rejects uploads whose declared size already exceeds the limit and verifies the real byte count by streaming.
50
+ - **Potentially breaking:** the public allow-list constants (`ALLOWED_IMAGE_MIMES`, `ALLOWED_ZIP_MIMES`, `ALLOWED_IMAGE_EXTENSIONS`, `ALLOWED_ZIP_EXTENSIONS`) are now immutable `frozenset`s. Read-only use is unaffected; in-place mutation (e.g. `.add(...)`) is no longer supported, assign a new set instead.
51
+
52
+ ### Fixed
53
+
54
+ - Corrected the filename-sanitization debug log so it reports the true original filename rather than the already-sanitized value.
55
+ - Documentation URLs updated to the new site.
56
+
57
+ ## [1.0.1] - 2026-06-03
58
+
59
+ ### Fixed
60
+
61
+ - `_stream_to_temp_file` now seeks the original `UploadFile` back to position 0 (#3).
62
+
63
+ ### Changed
64
+
65
+ - Dependency version bumps.
66
+
67
+ ## [1.0.0] - 2026-03-26
68
+
69
+ First stable, production-ready release. Every layer of the validation
70
+ pipeline was expanded, hardened, and documented since v0.1.2.
71
+
72
+ ### Added
73
+
74
+ - **Activity & gzip validation:** `validate_activity_file` (GPX/TCX/FIT) and `validate_gzip_file` with decompression-bomb detection, plus `XmlSecurityValidator` using `defusedxml` to block XXE, DTD, and entity-expansion attacks in XML-based activity files.
75
+ - **Audit logging:** `SecurityAuditLogger`, `AuditEvent`, and `AuditEventType` with per-request correlation IDs, toggled by `enable_audit_logging`.
76
+ - **Content security inspector:** `ContentSecurityInspector` for malware-signature scanning, script/webshell marker detection, and polyglot detection, controlled by `enable_content_analysis` and `content_scan_max_size`.
77
+ - **Nested ZIP complexity checks:** recursive inspection with cumulative entry, depth, hash, and timing checks, archive-quine and ZIP-of-ZIPs detection, and a new `max_total_entries_recursive` limit.
78
+
79
+ ### Changed
80
+
81
+ - **Security hardening:** streaming file-size enforcement via `chunk_size`, tightened ZIP inspection (null-byte filename detection, monotonic timers, 512-byte content scans, sanitized error messages), and a hardened MIME cache (256 → 64 entries, extension-only derivation).
82
+ - `defusedxml` added as a runtime dependency.
83
+ - FastAPI example now binds to `127.0.0.1` instead of `0.0.0.0`.
84
+ - **Performance:** pre-computed `frozenset` collections, cached lookup sets at initialization, and LRU-cached MIME guessing.
85
+
86
+ ### Other
87
+
88
+ - Hypothesis fuzz tests and integration suites; GitHub Actions for lint/test, fuzzing, docs, and PyPI publish; Dependabot and `CODEOWNERS`; new security docs (threat model, architecture, integration checklist).
89
+
90
+ ## [0.1.2] - 2025-10-31
91
+
92
+ ### Fixed
93
+
94
+ - Added `BinaryFileCategory` to the exported enums in `safeuploads/__init__.py` so it is available for external use.
95
+
96
+ ## [0.1.1] - 2025-10-31
97
+
98
+ ### Added
99
+
100
+ - `max_number_files_same_type` in `SecurityLimits`; `ZipContentInspector` now uses this configurable limit instead of a hardcoded value when checking for excessive files of the same type in a ZIP archive.
101
+ - `BinaryFileCategory` enum categorizing binary file extensions (starting with fitness files such as `.fit`); `ZipContentInspector` skips script-content checks for these binary files.
102
+
103
+ ## [0.1.0] - 2025-10-30
104
+
105
+ Initial release.
106
+
107
+ ### Added
108
+
109
+ - **Filename security:** Unicode normalization, directory-traversal protection, Windows reserved-name blocking.
110
+ - **Extension validation:** configurable allow/block lists with dangerous-extension detection.
111
+ - **ZIP bomb protection:** compression-ratio limits, nested-archive inspection, size constraints.
112
+ - **MIME type verification:** magic-number validation for common file types.
113
+ - Framework-agnostic async validation, a rich exception hierarchy with machine-readable error codes, secure defaults, and full type hints.
114
+
115
+ [1.1.3]: https://github.com/endurain-project/safeuploads/compare/v1.1.2...v1.1.3
116
+ [1.1.2]: https://github.com/endurain-project/safeuploads/compare/v1.1.1...v1.1.2
117
+ [1.1.1]: https://github.com/endurain-project/safeuploads/compare/v1.1.0...v1.1.1
118
+ [1.1.0]: https://github.com/endurain-project/safeuploads/compare/v1.0.1...v1.1.0
119
+ [1.0.1]: https://github.com/endurain-project/safeuploads/releases/tag/v1.0.1
120
+ [1.0.0]: https://github.com/endurain-project/safeuploads/releases/tag/v1.0.0
121
+ [0.1.2]: https://github.com/endurain-project/safeuploads/releases/tag/v0.1.2
122
+ [0.1.1]: https://github.com/endurain-project/safeuploads/releases/tag/v0.1.1
123
+ [0.1.0]: https://github.com/endurain-project/safeuploads/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: safeuploads
3
- Version: 1.1.1
3
+ Version: 1.1.3
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "safeuploads"
3
- version = "1.1.1"
3
+ version = "1.1.3"
4
4
  description = "A comprehensive file security system for validating uploads and preventing attacks"
5
5
  authors = [
6
6
  {name = "João Vitória Silva",email = "joao@endurain.com"}
@@ -24,6 +24,7 @@ dev = [
24
24
  "ruff>=0.9.0",
25
25
  "hypothesis>=6.100,<7.0",
26
26
  "mypy>=1.20.2",
27
+ "ty>=0.0.83",
27
28
  "types-defusedxml",
28
29
  # HTTP stack for the FastAPI multipart integration test
29
30
  "httpx>=0.27,<1.0",
@@ -40,7 +41,7 @@ requires = ["hatchling"]
40
41
  build-backend = "hatchling.build"
41
42
 
42
43
  [tool.uv]
43
- required-version = "0.12.5"
44
+ required-version = "0.13.5"
44
45
  exclude-newer = "30 days"
45
46
 
46
47
  [tool.ruff]
@@ -165,3 +166,11 @@ exclude = ["tests/"]
165
166
  [[tool.mypy.overrides]]
166
167
  module = ["magic", "fastapi"]
167
168
  ignore_missing_imports = true
169
+
170
+ # ``ty`` runs beside mypy as an advisory, non-blocking CI job (it is pre-1.0);
171
+ # mypy stays the gate. Same scope as ``[tool.mypy]``.
172
+ [tool.ty.environment]
173
+ python-version = "3.13"
174
+
175
+ [tool.ty.src]
176
+ include = ["safeuploads"]
@@ -23,7 +23,7 @@ class SeekableFile(Protocol):
23
23
  tell: Return current file pointer position.
24
24
  """
25
25
 
26
- def read(self, size: int = -1) -> bytes:
26
+ def read(self, size: int = -1, /) -> bytes:
27
27
  """
28
28
  Read bytes from the file.
29
29
 
@@ -35,7 +35,7 @@ class SeekableFile(Protocol):
35
35
  """
36
36
  ... # pragma: no cover
37
37
 
38
- def seek(self, offset: int, whence: int = 0) -> int:
38
+ def seek(self, offset: int, whence: int = 0, /) -> int:
39
39
  """
40
40
  Move file pointer to specified position.
41
41
 
@@ -76,7 +76,7 @@ class UploadFileProtocol(Protocol):
76
76
  filename: str | None
77
77
  size: int | None
78
78
 
79
- async def read(self, size: int = -1) -> bytes:
79
+ async def read(self, size: int = -1, /) -> bytes:
80
80
  """
81
81
  Read bytes from the uploaded file.
82
82
 
@@ -88,7 +88,7 @@ class UploadFileProtocol(Protocol):
88
88
  """
89
89
  ... # pragma: no cover
90
90
 
91
- async def seek(self, offset: int) -> int:
91
+ async def seek(self, offset: int) -> int | None:
92
92
  """
93
93
  Move file pointer to specified position.
94
94
 
@@ -96,6 +96,7 @@ class UploadFileProtocol(Protocol):
96
96
  offset: Position to move to in bytes.
97
97
 
98
98
  Returns:
99
- New position in the file.
99
+ New position in the file, or None for implementations
100
+ (such as Starlette's UploadFile) that return nothing.
100
101
  """
101
102
  ... # pragma: no cover