safeuploads 1.0.1__tar.gz → 1.1.1__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 (105) hide show
  1. safeuploads-1.1.1/.github/CODEOWNERS +4 -0
  2. safeuploads-1.1.1/.github/FUNDING.yml +13 -0
  3. safeuploads-1.1.1/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  4. safeuploads-1.1.1/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. safeuploads-1.1.1/.github/ISSUE_TEMPLATE/feature_request.md +27 -0
  6. safeuploads-1.1.1/.github/PULL_REQUEST_TEMPLATE.md +33 -0
  7. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/copilot-instructions.md +1 -1
  8. safeuploads-1.1.1/.github/dependabot.yml +56 -0
  9. {safeuploads-1.0.1/.forgejo → safeuploads-1.1.1/.github}/scripts/check_conventional_commits.py +8 -6
  10. safeuploads-1.1.1/.github/scripts/smoke_import.py +35 -0
  11. safeuploads-1.1.1/.github/workflows/audit-safeuploads.yml +58 -0
  12. {safeuploads-1.0.1/.forgejo → safeuploads-1.1.1/.github}/workflows/conventional-commits.yml +12 -13
  13. safeuploads-1.1.1/.github/workflows/docs.yml +81 -0
  14. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/workflows/fuzz.yml +6 -13
  15. safeuploads-1.0.1/.github/workflows/lint&test.yml → safeuploads-1.1.1/.github/workflows/lint-and-test.yml +19 -25
  16. safeuploads-1.1.1/.github/workflows/mirror.yml +30 -0
  17. safeuploads-1.1.1/.github/workflows/publish-safeuploads.yml +170 -0
  18. safeuploads-1.1.1/CHANGELOG.md +161 -0
  19. {safeuploads-1.0.1 → safeuploads-1.1.1}/CONTRIBUTING.md +2 -2
  20. {safeuploads-1.0.1 → safeuploads-1.1.1}/PKG-INFO +21 -9
  21. {safeuploads-1.0.1 → safeuploads-1.1.1}/README.md +20 -8
  22. {safeuploads-1.0.1 → safeuploads-1.1.1}/docs/index.md +21 -9
  23. {safeuploads-1.0.1 → safeuploads-1.1.1}/docs/rate-limiting.md +1 -1
  24. {safeuploads-1.0.1 → safeuploads-1.1.1}/docs/security/architecture.md +11 -11
  25. {safeuploads-1.0.1 → safeuploads-1.1.1}/docs/security/threat-model.md +21 -2
  26. {safeuploads-1.0.1 → safeuploads-1.1.1}/examples/README.md +13 -5
  27. {safeuploads-1.0.1 → safeuploads-1.1.1}/examples/fastapi_example.py +125 -3
  28. {safeuploads-1.0.1 → safeuploads-1.1.1}/mkdocs.yml +1 -1
  29. {safeuploads-1.0.1 → safeuploads-1.1.1}/pyproject.toml +51 -1
  30. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/__init__.py +6 -3
  31. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/audit.py +4 -3
  32. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/config.py +467 -310
  33. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/enums.py +28 -0
  34. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/exceptions.py +29 -13
  35. safeuploads-1.1.1/safeuploads/file_validator.py +1212 -0
  36. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/inspectors/__init__.py +4 -2
  37. safeuploads-1.1.1/safeuploads/inspectors/base.py +34 -0
  38. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/inspectors/content_inspector.py +40 -63
  39. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/inspectors/gzip_inspector.py +16 -20
  40. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/inspectors/zip_inspector.py +44 -69
  41. safeuploads-1.1.1/safeuploads/py.typed +0 -0
  42. safeuploads-1.1.1/safeuploads/utils.py +311 -0
  43. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/validators/base.py +6 -1
  44. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/validators/compression_validator.py +67 -28
  45. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/validators/extension_validator.py +3 -0
  46. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/validators/unicode_validator.py +5 -1
  47. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/validators/windows_validator.py +3 -0
  48. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/validators/xml_validator.py +10 -3
  49. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/inspectors/test_zip_inspector.py +36 -0
  50. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/test_config.py +45 -0
  51. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/test_config_validation.py +63 -0
  52. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/test_enums.py +20 -0
  53. safeuploads-1.1.1/tests/test_fastapi_integration.py +164 -0
  54. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/test_file_validator.py +241 -0
  55. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/test_utils.py +37 -4
  56. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/validators/test_compression_validator.py +89 -0
  57. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/validators/test_unicode_validator.py +5 -1
  58. safeuploads-1.1.1/uv.lock +1120 -0
  59. safeuploads-1.0.1/.forgejo/workflows/docs.yml +0 -51
  60. safeuploads-1.0.1/.forgejo/workflows/fuzz.yml +0 -44
  61. safeuploads-1.0.1/.forgejo/workflows/lint-and-test.yml +0 -86
  62. safeuploads-1.0.1/.forgejo/workflows/publish.yml +0 -63
  63. safeuploads-1.0.1/.forgejo/workflows/renovate.yml +0 -37
  64. safeuploads-1.0.1/.github/workflows/publish.yml +0 -67
  65. safeuploads-1.0.1/renovate.json +0 -35
  66. safeuploads-1.0.1/safeuploads/file_validator.py +0 -1028
  67. safeuploads-1.0.1/safeuploads/utils.py +0 -176
  68. safeuploads-1.0.1/uv.lock +0 -422
  69. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/agents/se-gitops-ci-specialist.agent.md +0 -0
  70. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/agents/se-security-reviewer.agent.md +0 -0
  71. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/agents/test-implementor.agent.md +0 -0
  72. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/agents/test-reviewer.agent.md +0 -0
  73. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/instructions/agents.instructions.md +0 -0
  74. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +0 -0
  75. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/instructions/instructions.instructions.md +0 -0
  76. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/instructions/prompt.instructions.md +0 -0
  77. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/instructions/python.instructions.md +0 -0
  78. {safeuploads-1.0.1 → safeuploads-1.1.1}/.github/instructions/security-and-owasp.instructions.md +0 -0
  79. {safeuploads-1.0.1 → safeuploads-1.1.1}/.gitignore +0 -0
  80. {safeuploads-1.0.1 → safeuploads-1.1.1}/CODE_OF_CONDUCT.md +0 -0
  81. {safeuploads-1.0.1 → safeuploads-1.1.1}/LICENSE.md +0 -0
  82. {safeuploads-1.0.1 → safeuploads-1.1.1}/SECURITY.md +0 -0
  83. {safeuploads-1.0.1 → safeuploads-1.1.1}/docs/api.md +0 -0
  84. {safeuploads-1.0.1 → safeuploads-1.1.1}/docs/security/integration-checklist.md +0 -0
  85. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/protocols.py +0 -0
  86. {safeuploads-1.0.1 → safeuploads-1.1.1}/safeuploads/validators/__init__.py +2 -2
  87. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/__init__.py +0 -0
  88. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/conftest.py +0 -0
  89. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/fuzz/__init__.py +0 -0
  90. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/fuzz/test_fuzz_config.py +0 -0
  91. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/fuzz/test_fuzz_filenames.py +0 -0
  92. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/fuzz/test_fuzz_images.py +0 -0
  93. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/fuzz/test_fuzz_zip.py +0 -0
  94. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/inspectors/__init__.py +0 -0
  95. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/inspectors/test_content_inspector.py +0 -0
  96. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/inspectors/test_gzip_inspector.py +0 -0
  97. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/test_audit.py +0 -0
  98. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/test_exceptions.py +0 -0
  99. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/test_performance.py +0 -0
  100. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/test_validate_activity_file.py +0 -0
  101. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/test_validate_gzip_file.py +0 -0
  102. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/validators/__init__.py +0 -0
  103. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/validators/test_extension_validator.py +0 -0
  104. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/validators/test_windows_validator.py +0 -0
  105. {safeuploads-1.0.1 → safeuploads-1.1.1}/tests/validators/test_xml_validator.py +0 -0
@@ -0,0 +1,4 @@
1
+ # Every change needs a review from the maintainer. safeuploads is maintained by one
2
+ # person (see CONTRIBUTING.md), so this is a single entry rather than a routing
3
+ # table — it exists so review is requested automatically rather than remembered.
4
+ * @joaovitoriasilva
@@ -0,0 +1,13 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: joaovitoriasilva # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4
+ patreon: endurain # Replace with a single Patreon username
5
+ #open_collective: # Replace with a single Open Collective username
6
+ #ko_fi: # Replace with a single Ko-fi username
7
+ #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8
+ #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
+ liberapay: endurain # Replace with a single Liberapay username
10
+ #issuehunt: # Replace with a single IssueHunt username
11
+ #otechie: # Replace with a single Otechie username
12
+ #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13
+ custom: ['https://buymeacoffee.com/endurain'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
@@ -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.
@@ -77,7 +77,7 @@ Committing should use clear messages following [Conventional Commits](https://ww
77
77
 
78
78
  **Format:** `<type>(<scope>)!: <description>` — `(<scope>)` and the breaking-change `!` are optional.
79
79
 
80
- The following rules are enforced automatically on every PR (against the PR title and every commit subject) by `.forgejo/workflows/conventional-commits.yml`. Validation follows the [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) spec, with a single project-policy addition (the allowed-type whitelist). Generated commit messages must comply:
80
+ The following rules are enforced automatically on every PR (against the PR title and every commit subject) by `.github/workflows/conventional-commits.yml`. Validation follows the [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) spec, with a single project-policy addition (the allowed-type whitelist). Generated commit messages must comply:
81
81
 
82
82
  - **Header format:** `<type>(<scope>)!: <description>` — the `(<scope>)` and breaking-change `!` are optional, and a space is required after the colon.
83
83
  - **Allowed types (project policy):** `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`. Types are matched case-insensitively per the spec.
@@ -0,0 +1,56 @@
1
+ # Dependabot keeps dependencies *fresh*; the separate `audit-safeuploads` workflow
2
+ # is what catches an advisory against a version already pinned. Both are needed.
3
+ #
4
+ # Updates are grouped so a week's worth of patch bumps arrives as one reviewable
5
+ # pull request rather than a dozen. `uv.lock` is regenerated by Dependabot's uv
6
+ # support, so the lockfile and `pyproject.toml` stay in step.
7
+ version: 2
8
+
9
+ updates:
10
+ - package-ecosystem: uv
11
+ directory: /
12
+ schedule:
13
+ interval: weekly
14
+ day: monday
15
+ time: "05:00"
16
+ timezone: Etc/UTC
17
+ open-pull-requests-limit: 5
18
+ labels:
19
+ - dependencies
20
+ commit-message:
21
+ # Matches the Conventional Commits check in `conventional-commits.yml`.
22
+ prefix: chore
23
+ include: scope
24
+ groups:
25
+ # The runtime dependency set is small and deliberate, so these are worth
26
+ # reading individually rather than batching.
27
+ production-dependencies:
28
+ dependency-type: production
29
+ update-types:
30
+ - minor
31
+ - patch
32
+ development-dependencies:
33
+ dependency-type: development
34
+ update-types:
35
+ - minor
36
+ - patch
37
+
38
+ # Every action is pinned to a commit SHA, so without this they never move.
39
+ - package-ecosystem: github-actions
40
+ directory: /
41
+ schedule:
42
+ interval: weekly
43
+ day: monday
44
+ time: "05:30"
45
+ timezone: Etc/UTC
46
+ open-pull-requests-limit: 5
47
+ labels:
48
+ - dependencies
49
+ - github-actions
50
+ commit-message:
51
+ prefix: chore
52
+ include: scope
53
+ groups:
54
+ github-actions:
55
+ patterns:
56
+ - "*"
@@ -12,9 +12,9 @@ Usage::
12
12
  check_conventional_commits.py "feat(api): add endpoint"
13
13
  git log --format=%s base..head | check_conventional_commits.py --stdin
14
14
 
15
- Designed to run with the Python interpreter already available on the Forgejo
16
- runner image. Uses only the standard library so there is no third-party
17
- supply-chain surface.
15
+ Designed to run with the Python interpreter already available on the GitHub
16
+ Actions runner image. Uses only the standard library so there is no
17
+ third-party supply-chain surface.
18
18
  """
19
19
 
20
20
  from __future__ import annotations
@@ -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
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ """Smoke-test that the built safeuploads wheel is self-contained.
3
+
4
+ Imports the installed package (not the source tree) and constructs
5
+ a FileValidator to prove the distribution's declared dependencies
6
+ are sufficient to actually use the library.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+ from importlib.metadata import version
13
+
14
+ from safeuploads import FileSecurityConfig, FileValidator
15
+
16
+
17
+ def main() -> int:
18
+ """Exercise the installed package and return a process exit code.
19
+
20
+ Returns:
21
+ 0 on success, 1 if a check fails.
22
+ """
23
+ print(f"safeuploads version: {version('safeuploads')}")
24
+
25
+ validator = FileValidator(config=FileSecurityConfig())
26
+ if not isinstance(validator, FileValidator):
27
+ print("FileValidator construction failed.", file=sys.stderr)
28
+ return 1
29
+
30
+ print("Smoke test passed: package imported and validator built.")
31
+ return 0
32
+
33
+
34
+ if __name__ == "__main__":
35
+ sys.exit(main())
@@ -0,0 +1,58 @@
1
+ name: Audit dependencies
2
+
3
+ # Dependabot keeps dependencies *fresh*; it does not tell you when a version you
4
+ # are already pinned to turns out to be vulnerable. This job scans the resolved
5
+ # lockfile against the OSV / PyPI advisory databases, on every dependency change
6
+ # and on a schedule so a newly-published advisory against an unchanged pin is
7
+ # still surfaced.
8
+
9
+ on:
10
+ push:
11
+ branches:
12
+ - main
13
+ paths: &paths
14
+ - pyproject.toml
15
+ - uv.lock
16
+ - .github/workflows/audit-safeuploads.yml
17
+ pull_request:
18
+ paths: *paths
19
+ schedule:
20
+ # Daily, offset from the dependency-update run so the two do not contend.
21
+ - cron: '30 5 * * *'
22
+ workflow_dispatch:
23
+
24
+ permissions:
25
+ contents: read
26
+
27
+ concurrency:
28
+ group: ${{ github.workflow }}-${{ github.ref }}
29
+ cancel-in-progress: true
30
+
31
+ jobs:
32
+ audit:
33
+ name: safeuploads (pip-audit)
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - name: Checkout code
37
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
38
+ with:
39
+ fetch-depth: 1
40
+
41
+ - name: Install uv
42
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
43
+ with:
44
+ # Resolve uv from `[tool.uv] required-version` in pyproject.toml, so CI
45
+ # and local development are held to the same floor and ceiling.
46
+ version-file: pyproject.toml
47
+
48
+ # Audit the full resolved set - runtime dependencies plus every optional
49
+ # extra - because a host enabling ``safeuploads[fastapi]`` inherits those too.
50
+ # ``--no-emit-project`` drops the editable self-reference, which carries no
51
+ # hash and which pip-audit therefore rejects.
52
+ - name: Export the resolved dependency set
53
+ run: >-
54
+ uv export --frozen --all-extras --no-dev --no-emit-project
55
+ --format requirements-txt --output-file "$RUNNER_TEMP/requirements.txt"
56
+
57
+ - name: Scan for known vulnerabilities
58
+ run: uvx pip-audit --strict --requirement "$RUNNER_TEMP/requirements.txt" --disable-pip
@@ -4,21 +4,20 @@ 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
- group: ${{ forgejo.workflow }}-${{ forgejo.event.pull_request.number }}
8
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
13
9
  cancel-in-progress: true
14
10
 
11
+ permissions:
12
+ contents: read
13
+
15
14
  jobs:
16
15
  validate:
17
- runs-on: codeberg-small
16
+ runs-on: ubuntu-latest
18
17
 
19
18
  steps:
20
19
  - name: Checkout code
21
- uses: https://data.forgejo.org/actions/checkout@v6
20
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
21
  with:
23
22
  fetch-depth: 0
24
23
 
@@ -26,19 +25,19 @@ jobs:
26
25
  # The PR title is passed through an environment variable to prevent
27
26
  # shell injection via crafted titles (see security-and-owasp guidelines).
28
27
  env:
29
- PR_TITLE: ${{ forgejo.event.pull_request.title }}
28
+ PR_TITLE: ${{ github.event.pull_request.title }}
30
29
  run: |
31
- python3 .forgejo/scripts/check_conventional_commits.py "$PR_TITLE"
30
+ python3 .github/scripts/check_conventional_commits.py "$PR_TITLE"
32
31
 
33
32
  - name: Validate commits in PR
34
33
  # Validates every commit subject between the PR base and head. Use the
35
34
  # base/head SHAs from the event payload so we don't depend on the local
36
35
  # checkout ref layout.
37
36
  env:
38
- BASE_SHA: ${{ forgejo.event.pull_request.base.sha }}
39
- HEAD_SHA: ${{ forgejo.event.pull_request.head.sha }}
37
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
38
+ HEAD_SHA: ${{ github.event.pull_request.head.sha }}
40
39
  run: |
41
40
  set -euo pipefail
42
41
  git fetch --no-tags origin "$BASE_SHA" "$HEAD_SHA"
43
- git log --format=%s "$BASE_SHA".."$HEAD_SHA" \
44
- | python3 .forgejo/scripts/check_conventional_commits.py --stdin
42
+ git log --no-merges --format=%s "$BASE_SHA".."$HEAD_SHA" \
43
+ | python3 .github/scripts/check_conventional_commits.py --stdin
@@ -0,0 +1,81 @@
1
+ name: Deploy documentation
2
+
3
+ # ``dry-run`` only exists on ``workflow_dispatch``. On a push ``inputs.dry-run``
4
+ # is null, so every ``!= true`` guard below passes and the site deploys.
5
+ on:
6
+ workflow_dispatch:
7
+ inputs:
8
+ dry-run:
9
+ description: Build docs but do not deploy
10
+ required: false
11
+ default: false
12
+ type: boolean
13
+ push:
14
+ branches:
15
+ - main
16
+ paths:
17
+ - 'docs/**'
18
+ - 'mkdocs.yml'
19
+ - 'safeuploads/**/*.py'
20
+ - '.github/workflows/docs.yml'
21
+
22
+ permissions:
23
+ contents: read
24
+
25
+ concurrency:
26
+ group: github-pages
27
+ cancel-in-progress: false
28
+
29
+ jobs:
30
+ build:
31
+ name: Build documentation
32
+ runs-on: ubuntu-latest
33
+ permissions:
34
+ contents: read
35
+ pages: write
36
+ steps:
37
+ - name: Checkout code
38
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
39
+ with:
40
+ fetch-depth: 1
41
+
42
+ - name: Install uv
43
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
44
+ with:
45
+ # Resolve uv from `[tool.uv] required-version` in pyproject.toml, so CI
46
+ # and local development are held to the same floor and ceiling.
47
+ version-file: pyproject.toml
48
+
49
+ # mkdocstrings imports the package to render the API reference, so sync the
50
+ # default dependencies plus the docs group and install the project itself.
51
+ - name: Sync documentation dependencies
52
+ run: uv sync --frozen --group docs
53
+
54
+ - name: Build documentation
55
+ run: uv run --no-sync mkdocs build --site-dir site
56
+
57
+ - name: Configure GitHub Pages
58
+ if: ${{ inputs.dry-run != true }}
59
+ uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
60
+
61
+ - name: Upload GitHub Pages artifact
62
+ if: ${{ inputs.dry-run != true }}
63
+ uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
64
+ with:
65
+ path: site
66
+
67
+ deploy:
68
+ name: Deploy documentation
69
+ if: ${{ inputs.dry-run != true }}
70
+ needs: build
71
+ runs-on: ubuntu-latest
72
+ permissions:
73
+ pages: write
74
+ id-token: write
75
+ environment:
76
+ name: github-pages
77
+ url: ${{ steps.deployment.outputs.page_url }}
78
+ steps:
79
+ - name: Deploy to GitHub Pages
80
+ id: deployment
81
+ uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
@@ -6,16 +6,15 @@ on:
6
6
  - cron: "0 3 * * 0"
7
7
  workflow_dispatch:
8
8
 
9
+ permissions:
10
+ contents: read
11
+
9
12
  # Cancel in-progress runs for the same branch to avoid wasted
10
13
  # runner minutes.
11
14
  concurrency:
12
15
  group: ${{ github.workflow }}-${{ github.ref }}
13
16
  cancel-in-progress: true
14
17
 
15
- # Principle of least privilege: only read access by default.
16
- permissions:
17
- contents: read
18
-
19
18
  jobs:
20
19
  fuzz:
21
20
  name: Fuzz
@@ -23,21 +22,15 @@ jobs:
23
22
 
24
23
  steps:
25
24
  - name: Checkout repository
26
- uses: actions/checkout@v6
25
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27
26
 
28
27
  - name: Install system dependencies
29
28
  run: sudo apt-get update && sudo apt-get install -y libmagic1
30
29
 
31
- - name: Set up Python 3.13
32
- uses: actions/setup-python@v6
33
- with:
34
- python-version: "3.13"
35
-
36
30
  - name: Install uv
37
- uses: astral-sh/setup-uv@v7
31
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
38
32
  with:
39
- enable-cache: true
40
- cache-dependency-glob: "uv.lock"
33
+ version-file: pyproject.toml
41
34
 
42
35
  - name: Install dependencies
43
36
  run: uv sync --frozen --dev
@@ -6,16 +6,15 @@ on:
6
6
  pull_request:
7
7
  branches: [main]
8
8
 
9
+ permissions:
10
+ contents: read
11
+
9
12
  # Cancel in-progress runs for the same branch/PR to avoid wasted
10
13
  # runner minutes and race conditions on shared resources.
11
14
  concurrency:
12
15
  group: ${{ github.workflow }}-${{ github.ref }}
13
16
  cancel-in-progress: true
14
17
 
15
- # Principle of least privilege: only read access by default.
16
- permissions:
17
- contents: read
18
-
19
18
  jobs:
20
19
  # ----------------------------------------------------------------
21
20
  # Lint
@@ -27,19 +26,17 @@ jobs:
27
26
  runs-on: ubuntu-latest
28
27
 
29
28
  steps:
30
- - name: Checkout repository
31
- uses: actions/checkout@v6
32
-
33
- - name: Set up Python 3.13
34
- uses: actions/setup-python@v6
29
+ - name: Checkout code
30
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
35
31
  with:
36
- python-version: "3.13"
32
+ fetch-depth: 1
37
33
 
38
34
  - name: Install uv
39
- uses: astral-sh/setup-uv@v7
35
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
40
36
  with:
41
- enable-cache: true
42
- cache-dependency-glob: "uv.lock"
37
+ # Resolve uv from `[tool.uv] required-version` in pyproject.toml, so CI
38
+ # and local development are held to the same floor and ceiling.
39
+ version-file: pyproject.toml
43
40
 
44
41
  - name: Install dependencies
45
42
  run: uv sync --frozen --dev
@@ -50,6 +47,9 @@ jobs:
50
47
  - name: Check formatting with ruff
51
48
  run: uv run ruff format --check safeuploads tests examples
52
49
 
50
+ - name: Type check with mypy
51
+ run: uv run mypy
52
+
53
53
  # ----------------------------------------------------------------
54
54
  # Test
55
55
  # Runs the full pytest suite with coverage. Only runs after lint
@@ -62,24 +62,18 @@ jobs:
62
62
 
63
63
  steps:
64
64
  - name: Checkout repository
65
- uses: actions/checkout@v6
65
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
66
66
 
67
67
  - name: Install system dependencies
68
68
  run: sudo apt-get update && sudo apt-get install -y libmagic1
69
69
 
70
- - name: Set up Python 3.13
71
- uses: actions/setup-python@v6
72
- with:
73
- python-version: "3.13"
74
-
75
70
  - name: Install uv
76
- uses: astral-sh/setup-uv@v7
71
+ uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
77
72
  with:
78
- enable-cache: true
79
- cache-dependency-glob: "uv.lock"
73
+ version-file: pyproject.toml
80
74
 
81
75
  - name: Install dependencies
82
- run: uv sync --frozen --dev
76
+ run: uv sync --frozen --dev --extra fastapi
83
77
 
84
78
  - name: Audit Python dependencies
85
79
  run: uvx pip-audit --require-hashes --disable-pip -r <(uv export --frozen --no-header --no-emit-project --no-dev)
@@ -88,9 +82,9 @@ jobs:
88
82
  run: uv run pytest --cov-fail-under=90
89
83
 
90
84
  - name: Upload coverage report
91
- uses: actions/upload-artifact@v7
85
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
92
86
  if: always()
93
87
  with:
94
88
  name: safeuploads-coverage
95
89
  path: .coverage
96
- retention-days: 7
90
+ retention-days: 7
@@ -0,0 +1,30 @@
1
+ name: Mirror to GitLab
2
+
3
+ on:
4
+ push:
5
+
6
+ concurrency:
7
+ group: gitlab-mirror
8
+ cancel-in-progress: false
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ mirror:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Push full mirror to GitLab
18
+ env:
19
+ GITHUB_TOKEN: ${{ github.token }}
20
+ GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
21
+ run: |
22
+ set -euo pipefail
23
+ mirror_dir="$RUNNER_TEMP/safeuploads.git"
24
+ git clone --mirror \
25
+ "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \
26
+ "$mirror_dir"
27
+ cd "$mirror_dir"
28
+ git remote set-url --push origin \
29
+ "https://oauth2:${GITLAB_TOKEN}@gitlab.com/endurain-project/safeuploads.git"
30
+ git push --mirror