safeuploads 1.1.0__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.
- safeuploads-1.1.1/.github/CODEOWNERS +4 -0
- safeuploads-1.1.1/.github/FUNDING.yml +13 -0
- {safeuploads-1.1.0/.forgejo → safeuploads-1.1.1/.github}/PULL_REQUEST_TEMPLATE.md +1 -1
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/copilot-instructions.md +1 -1
- safeuploads-1.1.1/.github/dependabot.yml +56 -0
- {safeuploads-1.1.0/.forgejo → safeuploads-1.1.1/.github}/scripts/check_conventional_commits.py +3 -3
- safeuploads-1.1.1/.github/scripts/smoke_import.py +35 -0
- safeuploads-1.1.1/.github/workflows/audit-safeuploads.yml +58 -0
- {safeuploads-1.1.0/.forgejo → safeuploads-1.1.1/.github}/workflows/conventional-commits.yml +11 -8
- safeuploads-1.1.1/.github/workflows/docs.yml +81 -0
- {safeuploads-1.1.0/.forgejo → safeuploads-1.1.1/.github}/workflows/fuzz.yml +9 -6
- {safeuploads-1.1.0/.forgejo → safeuploads-1.1.1/.github}/workflows/lint-and-test.yml +16 -11
- safeuploads-1.1.1/.github/workflows/mirror.yml +30 -0
- safeuploads-1.1.1/.github/workflows/publish-safeuploads.yml +170 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/CHANGELOG.md +21 -7
- {safeuploads-1.1.0 → safeuploads-1.1.1}/CONTRIBUTING.md +2 -2
- {safeuploads-1.1.0 → safeuploads-1.1.1}/PKG-INFO +6 -6
- {safeuploads-1.1.0 → safeuploads-1.1.1}/README.md +5 -5
- {safeuploads-1.1.0 → safeuploads-1.1.1}/docs/index.md +8 -8
- {safeuploads-1.1.0 → safeuploads-1.1.1}/docs/rate-limiting.md +1 -1
- {safeuploads-1.1.0 → safeuploads-1.1.1}/mkdocs.yml +1 -1
- {safeuploads-1.1.0 → safeuploads-1.1.1}/pyproject.toml +3 -3
- {safeuploads-1.1.0 → safeuploads-1.1.1}/uv.lock +269 -259
- safeuploads-1.1.0/.forgejo/workflows/docs.yml +0 -59
- safeuploads-1.1.0/.forgejo/workflows/publish.yml +0 -60
- safeuploads-1.1.0/.forgejo/workflows/renovate.yml +0 -32
- safeuploads-1.1.0/renovate.json +0 -35
- {safeuploads-1.1.0/.forgejo → safeuploads-1.1.1/.github}/ISSUE_TEMPLATE/bug_report.md +0 -0
- {safeuploads-1.1.0/.forgejo → safeuploads-1.1.1/.github}/ISSUE_TEMPLATE/config.yml +0 -0
- {safeuploads-1.1.0/.forgejo → safeuploads-1.1.1/.github}/ISSUE_TEMPLATE/feature_request.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/agents/se-gitops-ci-specialist.agent.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/agents/se-security-reviewer.agent.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/agents/test-implementor.agent.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/agents/test-reviewer.agent.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/instructions/agents.instructions.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/instructions/instructions.instructions.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/instructions/prompt.instructions.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/instructions/python.instructions.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.github/instructions/security-and-owasp.instructions.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/.gitignore +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/CODE_OF_CONDUCT.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/LICENSE.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/SECURITY.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/docs/api.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/docs/security/architecture.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/docs/security/integration-checklist.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/docs/security/threat-model.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/examples/README.md +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/examples/fastapi_example.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/__init__.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/audit.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/config.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/enums.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/exceptions.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/file_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/inspectors/__init__.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/inspectors/base.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/inspectors/content_inspector.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/inspectors/gzip_inspector.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/inspectors/zip_inspector.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/protocols.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/py.typed +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/utils.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/validators/__init__.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/validators/base.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/validators/compression_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/validators/extension_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/validators/unicode_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/validators/windows_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/safeuploads/validators/xml_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/__init__.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/conftest.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/fuzz/__init__.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/fuzz/test_fuzz_config.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/fuzz/test_fuzz_filenames.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/fuzz/test_fuzz_images.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/fuzz/test_fuzz_zip.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/inspectors/__init__.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/inspectors/test_content_inspector.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/inspectors/test_gzip_inspector.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/inspectors/test_zip_inspector.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_audit.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_config.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_config_validation.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_enums.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_exceptions.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_fastapi_integration.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_file_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_performance.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_utils.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_validate_activity_file.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/test_validate_gzip_file.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/validators/__init__.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/validators/test_compression_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/validators/test_extension_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/validators/test_unicode_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/validators/test_windows_validator.py +0 -0
- {safeuploads-1.1.0 → safeuploads-1.1.1}/tests/validators/test_xml_validator.py +0 -0
|
@@ -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']
|
|
@@ -30,4 +30,4 @@ Check all that apply
|
|
|
30
30
|
|
|
31
31
|
## Notes for the reviewer
|
|
32
32
|
|
|
33
|
-
Anything that would help review this. Tricky areas, alternative approaches considered, screenshots, etc.
|
|
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 `.
|
|
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
|
+
- "*"
|
{safeuploads-1.1.0/.forgejo → safeuploads-1.1.1/.github}/scripts/check_conventional_commits.py
RENAMED
|
@@ -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
|
|
16
|
-
runner image. Uses only the standard library so there is no
|
|
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
|
|
@@ -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
|
|
@@ -5,16 +5,19 @@ on:
|
|
|
5
5
|
types: [opened, edited, synchronize, reopened]
|
|
6
6
|
|
|
7
7
|
concurrency:
|
|
8
|
-
group: ${{
|
|
8
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
|
9
9
|
cancel-in-progress: true
|
|
10
10
|
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
11
14
|
jobs:
|
|
12
15
|
validate:
|
|
13
|
-
runs-on:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
14
17
|
|
|
15
18
|
steps:
|
|
16
19
|
- name: Checkout code
|
|
17
|
-
uses:
|
|
20
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
18
21
|
with:
|
|
19
22
|
fetch-depth: 0
|
|
20
23
|
|
|
@@ -22,19 +25,19 @@ jobs:
|
|
|
22
25
|
# The PR title is passed through an environment variable to prevent
|
|
23
26
|
# shell injection via crafted titles (see security-and-owasp guidelines).
|
|
24
27
|
env:
|
|
25
|
-
PR_TITLE: ${{
|
|
28
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
26
29
|
run: |
|
|
27
|
-
python3 .
|
|
30
|
+
python3 .github/scripts/check_conventional_commits.py "$PR_TITLE"
|
|
28
31
|
|
|
29
32
|
- name: Validate commits in PR
|
|
30
33
|
# Validates every commit subject between the PR base and head. Use the
|
|
31
34
|
# base/head SHAs from the event payload so we don't depend on the local
|
|
32
35
|
# checkout ref layout.
|
|
33
36
|
env:
|
|
34
|
-
BASE_SHA: ${{
|
|
35
|
-
HEAD_SHA: ${{
|
|
37
|
+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
38
|
+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
36
39
|
run: |
|
|
37
40
|
set -euo pipefail
|
|
38
41
|
git fetch --no-tags origin "$BASE_SHA" "$HEAD_SHA"
|
|
39
42
|
git log --no-merges --format=%s "$BASE_SHA".."$HEAD_SHA" \
|
|
40
|
-
| python3 .
|
|
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,28 +6,31 @@ 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
|
-
group: ${{
|
|
15
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
16
|
cancel-in-progress: true
|
|
14
17
|
|
|
15
18
|
jobs:
|
|
16
19
|
fuzz:
|
|
17
20
|
name: Fuzz
|
|
18
|
-
runs-on:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
19
22
|
|
|
20
23
|
steps:
|
|
21
24
|
- name: Checkout repository
|
|
22
|
-
uses:
|
|
25
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
23
26
|
|
|
24
27
|
- name: Install system dependencies
|
|
25
|
-
run: apt-get update && apt-get install -y libmagic1
|
|
28
|
+
run: sudo apt-get update && sudo apt-get install -y libmagic1
|
|
26
29
|
|
|
27
30
|
- name: Install uv
|
|
28
|
-
uses:
|
|
31
|
+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
29
32
|
with:
|
|
30
|
-
|
|
33
|
+
version-file: pyproject.toml
|
|
31
34
|
|
|
32
35
|
- name: Install dependencies
|
|
33
36
|
run: uv sync --frozen --dev
|
|
@@ -6,10 +6,13 @@ 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
|
-
group: ${{
|
|
15
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
16
|
cancel-in-progress: true
|
|
14
17
|
|
|
15
18
|
jobs:
|
|
@@ -20,18 +23,20 @@ jobs:
|
|
|
20
23
|
# ----------------------------------------------------------------
|
|
21
24
|
lint:
|
|
22
25
|
name: Lint
|
|
23
|
-
runs-on:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
24
27
|
|
|
25
28
|
steps:
|
|
26
29
|
- name: Checkout code
|
|
27
|
-
uses:
|
|
30
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
28
31
|
with:
|
|
29
32
|
fetch-depth: 1
|
|
30
33
|
|
|
31
34
|
- name: Install uv
|
|
32
|
-
uses:
|
|
35
|
+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
33
36
|
with:
|
|
34
|
-
|
|
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
|
|
35
40
|
|
|
36
41
|
- name: Install dependencies
|
|
37
42
|
run: uv sync --frozen --dev
|
|
@@ -52,20 +57,20 @@ jobs:
|
|
|
52
57
|
# ----------------------------------------------------------------
|
|
53
58
|
test:
|
|
54
59
|
name: Test
|
|
55
|
-
runs-on:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
56
61
|
needs: lint
|
|
57
62
|
|
|
58
63
|
steps:
|
|
59
64
|
- name: Checkout repository
|
|
60
|
-
uses:
|
|
65
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
61
66
|
|
|
62
67
|
- name: Install system dependencies
|
|
63
|
-
run: apt-get update && apt-get install -y libmagic1
|
|
68
|
+
run: sudo apt-get update && sudo apt-get install -y libmagic1
|
|
64
69
|
|
|
65
70
|
- name: Install uv
|
|
66
|
-
uses:
|
|
71
|
+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
67
72
|
with:
|
|
68
|
-
|
|
73
|
+
version-file: pyproject.toml
|
|
69
74
|
|
|
70
75
|
- name: Install dependencies
|
|
71
76
|
run: uv sync --frozen --dev --extra fastapi
|
|
@@ -77,7 +82,7 @@ jobs:
|
|
|
77
82
|
run: uv run pytest --cov-fail-under=90
|
|
78
83
|
|
|
79
84
|
- name: Upload coverage report
|
|
80
|
-
uses:
|
|
85
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
81
86
|
if: always()
|
|
82
87
|
with:
|
|
83
88
|
name: safeuploads-coverage
|
|
@@ -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
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
name: Publish safeuploads
|
|
2
|
+
|
|
3
|
+
# Builds and publishes a release to PyPI when a ``v*`` tag is pushed. The
|
|
4
|
+
# unprivileged build job validates the release and uploads its artifacts; the
|
|
5
|
+
# separate ``pypi`` environment job receives OIDC permission only when it needs
|
|
6
|
+
# to generate PEP 740 attestations and publish through Trusted Publishing.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags:
|
|
11
|
+
- 'v*'
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
inputs:
|
|
14
|
+
dry-run:
|
|
15
|
+
description: Build and verify, but do not upload to PyPI
|
|
16
|
+
required: false
|
|
17
|
+
default: true
|
|
18
|
+
type: boolean
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
concurrency:
|
|
24
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
25
|
+
cancel-in-progress: false
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
build:
|
|
29
|
+
name: Build and verify
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout code
|
|
33
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
34
|
+
with:
|
|
35
|
+
fetch-depth: 0
|
|
36
|
+
persist-credentials: false
|
|
37
|
+
|
|
38
|
+
- name: Install uv
|
|
39
|
+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
40
|
+
with:
|
|
41
|
+
# Resolve uv from `[tool.uv] required-version` in pyproject.toml, so CI
|
|
42
|
+
# and local development are held to the same floor and ceiling.
|
|
43
|
+
version-file: pyproject.toml
|
|
44
|
+
enable-cache: false
|
|
45
|
+
|
|
46
|
+
# A tag that disagrees with the packaged version produces a release whose
|
|
47
|
+
# artifact name does not match its tag - catch it before anything is
|
|
48
|
+
# uploaded, because a PyPI version can never be re-used.
|
|
49
|
+
- name: Verify the tag matches the project version
|
|
50
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
51
|
+
env:
|
|
52
|
+
TAG_REF: ${{ github.ref }}
|
|
53
|
+
run: |
|
|
54
|
+
set -euo pipefail
|
|
55
|
+
tag_version="${TAG_REF#refs/tags/v}"
|
|
56
|
+
project_version="$(uv version --short)"
|
|
57
|
+
if [ "$tag_version" != "$project_version" ]; then
|
|
58
|
+
echo "Tag v${tag_version} does not match pyproject version ${project_version}." >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
echo "Releasing version ${project_version}."
|
|
62
|
+
|
|
63
|
+
- name: Verify the changelog documents this version
|
|
64
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
65
|
+
run: |
|
|
66
|
+
set -euo pipefail
|
|
67
|
+
version="$(uv version --short)"
|
|
68
|
+
if ! grep -q "^## \[${version}\]" CHANGELOG.md; then
|
|
69
|
+
echo "CHANGELOG.md has no '## [${version}]' section." >&2
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# The full gate runs on every PR, but a tag can point anywhere in history,
|
|
74
|
+
# so re-run it here: a release must never ship code that does not pass.
|
|
75
|
+
- name: Sync dependencies
|
|
76
|
+
run: uv sync --frozen --dev --extra fastapi
|
|
77
|
+
|
|
78
|
+
- name: Lint, type check and test
|
|
79
|
+
run: |
|
|
80
|
+
set -euo pipefail
|
|
81
|
+
uv run --no-sync ruff check safeuploads tests examples
|
|
82
|
+
uv run --no-sync ruff format --check safeuploads tests examples
|
|
83
|
+
uv run --no-sync mypy
|
|
84
|
+
uv run --no-sync pytest --cov-fail-under=90
|
|
85
|
+
|
|
86
|
+
- name: Build sdist and wheel
|
|
87
|
+
run: uv build --no-sources
|
|
88
|
+
|
|
89
|
+
- name: Verify package metadata renders on PyPI
|
|
90
|
+
run: uvx twine check --strict dist/*
|
|
91
|
+
|
|
92
|
+
# Prove the wheel is self-contained: install it into a clean environment
|
|
93
|
+
# with no dev groups and no optional extras, then import and assemble it
|
|
94
|
+
# from outside the repository (so the source tree cannot shadow it).
|
|
95
|
+
- name: Smoke-test the built wheel
|
|
96
|
+
run: |
|
|
97
|
+
set -euo pipefail
|
|
98
|
+
smoke_venv="$RUNNER_TEMP/smoke"
|
|
99
|
+
uv venv "$smoke_venv"
|
|
100
|
+
VIRTUAL_ENV="$smoke_venv" uv pip install dist/*.whl
|
|
101
|
+
cd "$RUNNER_TEMP"
|
|
102
|
+
VIRTUAL_ENV="$smoke_venv" uv run --no-project python "$GITHUB_WORKSPACE/.github/scripts/smoke_import.py"
|
|
103
|
+
|
|
104
|
+
# An SBOM of what the *distribution* actually pulls in, taken from the
|
|
105
|
+
# clean venv the smoke test just installed the wheel into. Running it
|
|
106
|
+
# against the build environment instead would describe this workflow's own
|
|
107
|
+
# toolchain — ruff, mypy, pytest — which is not what a consumer inherits.
|
|
108
|
+
- name: Generate SBOM (CycloneDX)
|
|
109
|
+
run: |
|
|
110
|
+
set -euo pipefail
|
|
111
|
+
mkdir -p release-metadata
|
|
112
|
+
uvx --from cyclonedx-bom cyclonedx-py environment "$RUNNER_TEMP/smoke" \
|
|
113
|
+
--output-format json --output-file release-metadata/safeuploads-sbom.cdx.json
|
|
114
|
+
|
|
115
|
+
- name: Generate checksums
|
|
116
|
+
run: |
|
|
117
|
+
set -euo pipefail
|
|
118
|
+
sha256sum dist/* > release-metadata/SHA256SUMS
|
|
119
|
+
cat release-metadata/SHA256SUMS
|
|
120
|
+
|
|
121
|
+
# ``dist/`` holds the wheel and the sdist and nothing else: the publish job
|
|
122
|
+
# attests everything it downloads, so a stray checksum or SBOM file there
|
|
123
|
+
# would end up in the attestation.
|
|
124
|
+
- name: Upload build artifacts
|
|
125
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
126
|
+
with:
|
|
127
|
+
name: safeuploads-dist
|
|
128
|
+
path: dist/
|
|
129
|
+
if-no-files-found: error
|
|
130
|
+
retention-days: 90
|
|
131
|
+
|
|
132
|
+
- name: Upload release metadata
|
|
133
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
134
|
+
with:
|
|
135
|
+
name: safeuploads-release-metadata
|
|
136
|
+
path: release-metadata/
|
|
137
|
+
if-no-files-found: error
|
|
138
|
+
retention-days: 90
|
|
139
|
+
|
|
140
|
+
publish:
|
|
141
|
+
name: Publish to PyPI
|
|
142
|
+
if: ${{ startsWith(github.ref, 'refs/tags/v') && inputs.dry-run != true }}
|
|
143
|
+
needs: build
|
|
144
|
+
runs-on: ubuntu-latest
|
|
145
|
+
environment:
|
|
146
|
+
name: pypi
|
|
147
|
+
url: https://pypi.org/p/safeuploads
|
|
148
|
+
permissions:
|
|
149
|
+
id-token: write
|
|
150
|
+
steps:
|
|
151
|
+
- name: Install uv
|
|
152
|
+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
153
|
+
with:
|
|
154
|
+
# This job never checks the repository out, so there is no
|
|
155
|
+
# pyproject.toml to read `required-version` from; keep this in step
|
|
156
|
+
# with the floor declared there.
|
|
157
|
+
version: "0.12.5"
|
|
158
|
+
enable-cache: false
|
|
159
|
+
|
|
160
|
+
- name: Download build artifacts
|
|
161
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
162
|
+
with:
|
|
163
|
+
name: safeuploads-dist
|
|
164
|
+
path: dist/
|
|
165
|
+
|
|
166
|
+
- name: Generate PEP 740 attestations
|
|
167
|
+
uses: astral-sh/attest-action@f589a42a7efb6fe400b4f400de60b4bc90390027 # v0.0.6
|
|
168
|
+
|
|
169
|
+
- name: Publish to PyPI
|
|
170
|
+
run: uv publish --trusted-publishing always dist/*.whl dist/*.tar.gz
|