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.
- safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/bug_report.md +35 -0
- safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/config.yml +5 -0
- safeuploads-1.1.0/.forgejo/ISSUE_TEMPLATE/feature_request.md +27 -0
- safeuploads-1.1.0/.forgejo/PULL_REQUEST_TEMPLATE.md +33 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/scripts/check_conventional_commits.py +5 -3
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/workflows/conventional-commits.yml +3 -7
- safeuploads-1.1.0/.forgejo/workflows/docs.yml +59 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/workflows/fuzz.yml +6 -9
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/workflows/lint-and-test.yml +16 -17
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/workflows/publish.yml +6 -9
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.forgejo/workflows/renovate.yml +3 -8
- safeuploads-1.1.0/CHANGELOG.md +147 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/PKG-INFO +17 -5
- {safeuploads-1.0.1 → safeuploads-1.1.0}/README.md +16 -4
- {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/index.md +13 -1
- {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/security/architecture.md +11 -11
- {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/security/threat-model.md +21 -2
- {safeuploads-1.0.1 → safeuploads-1.1.0}/examples/README.md +13 -5
- {safeuploads-1.0.1 → safeuploads-1.1.0}/examples/fastapi_example.py +125 -3
- {safeuploads-1.0.1 → safeuploads-1.1.0}/pyproject.toml +51 -1
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/__init__.py +6 -3
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/audit.py +4 -3
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/config.py +467 -310
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/enums.py +28 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/exceptions.py +29 -13
- safeuploads-1.1.0/safeuploads/file_validator.py +1212 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/inspectors/__init__.py +4 -2
- safeuploads-1.1.0/safeuploads/inspectors/base.py +34 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/inspectors/content_inspector.py +40 -63
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/inspectors/gzip_inspector.py +16 -20
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/inspectors/zip_inspector.py +44 -69
- safeuploads-1.1.0/safeuploads/py.typed +0 -0
- safeuploads-1.1.0/safeuploads/utils.py +311 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/base.py +6 -1
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/compression_validator.py +67 -28
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/extension_validator.py +3 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/unicode_validator.py +5 -1
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/windows_validator.py +3 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/xml_validator.py +10 -3
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/inspectors/test_zip_inspector.py +36 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_config.py +45 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_config_validation.py +63 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_enums.py +20 -0
- safeuploads-1.1.0/tests/test_fastapi_integration.py +164 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_file_validator.py +241 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_utils.py +37 -4
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/test_compression_validator.py +89 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/test_unicode_validator.py +5 -1
- safeuploads-1.1.0/uv.lock +1110 -0
- safeuploads-1.0.1/.forgejo/workflows/docs.yml +0 -51
- safeuploads-1.0.1/.github/workflows/fuzz.yml +0 -51
- safeuploads-1.0.1/.github/workflows/lint&test.yml +0 -96
- safeuploads-1.0.1/.github/workflows/publish.yml +0 -67
- safeuploads-1.0.1/safeuploads/file_validator.py +0 -1028
- safeuploads-1.0.1/safeuploads/utils.py +0 -176
- safeuploads-1.0.1/uv.lock +0 -422
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/agents/se-gitops-ci-specialist.agent.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/agents/se-security-reviewer.agent.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/agents/test-implementor.agent.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/agents/test-reviewer.agent.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/copilot-instructions.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/agents.instructions.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/instructions.instructions.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/prompt.instructions.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/python.instructions.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.github/instructions/security-and-owasp.instructions.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/.gitignore +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/CODE_OF_CONDUCT.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/CONTRIBUTING.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/LICENSE.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/SECURITY.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/api.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/rate-limiting.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/docs/security/integration-checklist.md +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/mkdocs.yml +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/renovate.json +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/protocols.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/safeuploads/validators/__init__.py +2 -2
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/__init__.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/conftest.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/fuzz/__init__.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_config.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_filenames.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_images.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/fuzz/test_fuzz_zip.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/inspectors/__init__.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/inspectors/test_content_inspector.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/inspectors/test_gzip_inspector.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_audit.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_exceptions.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_performance.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_validate_activity_file.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/test_validate_gzip_file.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/__init__.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/test_extension_validator.py +0 -0
- {safeuploads-1.0.1 → safeuploads-1.1.0}/tests/validators/test_windows_validator.py +0 -0
- {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,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
|
|
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:
|
|
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:
|
|
18
|
+
runs-on: local-runner
|
|
19
19
|
|
|
20
20
|
steps:
|
|
21
21
|
- name: Checkout repository
|
|
22
|
-
uses: https://data.forgejo.org/actions/checkout@
|
|
22
|
+
uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
23
23
|
|
|
24
24
|
- name: Install system dependencies
|
|
25
|
-
run:
|
|
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
|
-
|
|
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
|
|
27
|
-
uses: https://data.forgejo.org/actions/checkout@
|
|
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
|
-
|
|
29
|
+
fetch-depth: 1
|
|
33
30
|
|
|
34
31
|
- name: Install uv
|
|
35
|
-
|
|
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:
|
|
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@
|
|
60
|
+
uses: https://data.forgejo.org/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
59
61
|
|
|
60
62
|
- name: Install system dependencies
|
|
61
|
-
run:
|
|
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
|
-
|
|
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:
|
|
19
|
+
runs-on: local-runner
|
|
20
20
|
|
|
21
21
|
steps:
|
|
22
22
|
- name: Checkout repository
|
|
23
|
-
uses: https://data.forgejo.org/actions/checkout@
|
|
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:
|
|
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
|
-
|
|
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:
|
|
16
|
+
runs-on: local-runner
|
|
22
17
|
|
|
23
18
|
steps:
|
|
24
19
|
- name: Checkout code
|
|
25
|
-
uses: https://data.forgejo.org/actions/checkout@
|
|
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:
|
|
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
|
|
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
|
[](https://pypi.org/project/safeuploads/)
|
|
20
20
|
[](https://pypi.org/project/safeuploads/)
|
|
21
21
|
[](https://pypi.org/project/safeuploads/)
|
|
22
|
-
[](https://endurain
|
|
22
|
+
[](https://safeuploads.endurain.com/)
|
|
23
23
|
[](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
|
|
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
|
[](https://pypi.org/project/safeuploads/)
|
|
6
6
|
[](https://pypi.org/project/safeuploads/)
|
|
7
7
|
[](https://pypi.org/project/safeuploads/)
|
|
8
|
-
[](https://endurain
|
|
8
|
+
[](https://safeuploads.endurain.com/)
|
|
9
9
|
[](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
|
|
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
|
|