fastapi-m8 3.1.0__tar.gz → 3.3.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.
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/.github/workflows/CI.yaml +26 -9
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/.github/workflows/PiPy.yml +5 -7
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/CHANGELOG.md +90 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/PKG-INFO +35 -15
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/README.md +33 -13
- fastapi_m8-3.3.0/constraints-all.txt +237 -0
- fastapi_m8-3.3.0/constraints.txt +114 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/_app.py +7 -6
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/_compat.py +16 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/_internal_auth.py +5 -2
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/_version.py +1 -1
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/config.py +68 -7
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/pyproject.toml +2 -2
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_app.py +107 -7
- fastapi_m8-3.3.0/tests/test_ci_policy.py +136 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_config.py +80 -0
- fastapi_m8-3.1.0/.github/workflows/ci.yml +0 -54
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/.codacy.yml +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/.dockerignore +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/.env.example +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/.gitattributes +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/.github/FUNDING.yml +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/.github/dependabot.yml +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/.gitignore +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/.pydocstyle +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/LICENSE +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/SECURITY.md +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/__init__.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/_async_stub.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/_deps.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/_engine.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/_events.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/_health.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/_revocation.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/scripts/__init__.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/scripts/docker_start.sh +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/fastapi_m8/scripts/pre_start.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/__init__.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/conftest.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_app_extra.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_async_stub.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_compat.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_config_file_secrets.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_deps.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_engine.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_event_signing_gate.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_events.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_health.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_host_header_routing.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_internal_auth.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_meta.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_pre_start.py +0 -0
- {fastapi_m8-3.1.0 → fastapi_m8-3.3.0}/tests/test_revocation.py +0 -0
|
@@ -6,6 +6,9 @@ on:
|
|
|
6
6
|
pull_request:
|
|
7
7
|
branches: ["main"]
|
|
8
8
|
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
9
12
|
env:
|
|
10
13
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
11
14
|
|
|
@@ -13,10 +16,10 @@ jobs:
|
|
|
13
16
|
lint:
|
|
14
17
|
runs-on: ubuntu-latest
|
|
15
18
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
17
20
|
|
|
18
21
|
- name: Set up Python
|
|
19
|
-
uses: actions/setup-python@
|
|
22
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
20
23
|
with:
|
|
21
24
|
python-version: "3.14"
|
|
22
25
|
cache: "pip"
|
|
@@ -33,10 +36,10 @@ jobs:
|
|
|
33
36
|
typecheck:
|
|
34
37
|
runs-on: ubuntu-latest
|
|
35
38
|
steps:
|
|
36
|
-
- uses: actions/checkout@
|
|
39
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
37
40
|
|
|
38
41
|
- name: Set up Python
|
|
39
|
-
uses: actions/setup-python@
|
|
42
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
40
43
|
with:
|
|
41
44
|
python-version: "3.14"
|
|
42
45
|
cache: "pip"
|
|
@@ -50,10 +53,10 @@ jobs:
|
|
|
50
53
|
security:
|
|
51
54
|
runs-on: ubuntu-latest
|
|
52
55
|
steps:
|
|
53
|
-
- uses: actions/checkout@
|
|
56
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
54
57
|
|
|
55
58
|
- name: Set up Python
|
|
56
|
-
uses: actions/setup-python@
|
|
59
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
57
60
|
with:
|
|
58
61
|
python-version: "3.14"
|
|
59
62
|
|
|
@@ -84,10 +87,10 @@ jobs:
|
|
|
84
87
|
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
85
88
|
|
|
86
89
|
steps:
|
|
87
|
-
- uses: actions/checkout@
|
|
90
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
88
91
|
|
|
89
92
|
- name: Set up Python ${{ matrix.python-version }}
|
|
90
|
-
uses: actions/setup-python@
|
|
93
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
91
94
|
with:
|
|
92
95
|
python-version: ${{ matrix.python-version }}
|
|
93
96
|
cache: "pip"
|
|
@@ -100,7 +103,7 @@ jobs:
|
|
|
100
103
|
|
|
101
104
|
- name: Upload coverage to Codecov
|
|
102
105
|
if: matrix.python-version == '3.11' && github.actor != 'dependabot[bot]'
|
|
103
|
-
uses: codecov/codecov-action@
|
|
106
|
+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
104
107
|
with:
|
|
105
108
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
106
109
|
files: coverage.xml
|
|
@@ -112,3 +115,17 @@ jobs:
|
|
|
112
115
|
with:
|
|
113
116
|
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
|
114
117
|
coverage-reports: coverage.xml
|
|
118
|
+
|
|
119
|
+
secret-scan:
|
|
120
|
+
name: Secret scan (gitleaks)
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
|
|
123
|
+
steps:
|
|
124
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
125
|
+
with:
|
|
126
|
+
fetch-depth: 0
|
|
127
|
+
|
|
128
|
+
- uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0
|
|
129
|
+
env:
|
|
130
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
131
|
+
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
|
|
@@ -12,9 +12,9 @@ jobs:
|
|
|
12
12
|
runs-on: ubuntu-latest
|
|
13
13
|
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
15
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
16
16
|
|
|
17
|
-
- uses: actions/setup-python@
|
|
17
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
18
18
|
with:
|
|
19
19
|
python-version: "3.x"
|
|
20
20
|
|
|
@@ -52,8 +52,6 @@ jobs:
|
|
|
52
52
|
|
|
53
53
|
- name: Publish release distributions to PyPI
|
|
54
54
|
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
55
|
-
with:
|
|
56
|
-
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
57
55
|
|
|
58
56
|
sbom:
|
|
59
57
|
name: Generate & attach SBOM
|
|
@@ -65,16 +63,16 @@ jobs:
|
|
|
65
63
|
id-token: write
|
|
66
64
|
|
|
67
65
|
steps:
|
|
68
|
-
- uses: actions/checkout@
|
|
66
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
69
67
|
|
|
70
|
-
- uses: actions/setup-python@
|
|
68
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
71
69
|
with:
|
|
72
70
|
python-version: "3.x"
|
|
73
71
|
|
|
74
72
|
- name: Install package
|
|
75
73
|
run: pip install -e ".[all]"
|
|
76
74
|
|
|
77
|
-
- uses: anchore/sbom-action@
|
|
75
|
+
- uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
|
|
78
76
|
with:
|
|
79
77
|
artifact-name: fastapi-m8-${{ github.event.release.tag_name }}.sbom.spdx.json
|
|
80
78
|
format: spdx-json
|
|
@@ -5,6 +5,96 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · Versioning:
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## [3.3.0] — 2026-07-02 · legacy consumer private-auth is development-only (item 11.2b) + supply-chain hardening (11.6/11.7/11.8)
|
|
13
|
+
|
|
14
|
+
> **MINOR — security remediation, no public API removed.** The only runtime behaviour change
|
|
15
|
+
> is a new fail-closed boot check: a production/strict consumer (`ENVIRONMENT=production` or
|
|
16
|
+
> `STRICT_PRODUCTION_MODE=true`) that still runs legacy single-shared-secret private auth
|
|
17
|
+
> (`INTROSPECTION_URL` set, `INTERNAL_CLIENT_ID` unset) now raises at settings construction.
|
|
18
|
+
> Such a consumer already could not authenticate against a hardened `fa-auth-m8` issuer
|
|
19
|
+
> (item 11.2a retired the shared-secret fallback), so this converts a latent deployment fault
|
|
20
|
+
> into an explicit error rather than changing any working configuration. Local/development is
|
|
21
|
+
> unaffected. `auth-sdk-m8` floor raised to `>=2.1.1,<3.0.0` (carries the SDK's latest
|
|
22
|
+
> dependency/CI maintenance release; no new SDK API is required).
|
|
23
|
+
|
|
24
|
+
### Security
|
|
25
|
+
|
|
26
|
+
- **Legacy single-shared-secret private-auth mode is now development-only (OWASP remediation
|
|
27
|
+
item 11.2b).** `ConsumerServiceSettings` gained a `model_validator` that fails settings
|
|
28
|
+
construction when a production/strict consumer (`ENVIRONMENT=production` or
|
|
29
|
+
`STRICT_PRODUCTION_MODE=true`) has `INTROSPECTION_URL` configured but no
|
|
30
|
+
`INTERNAL_CLIENT_ID`. `fa-auth-m8` has retired the issuer's legacy single-shared-secret
|
|
31
|
+
private-API fallback (item 11.2a), so a production/strict consumer left in legacy
|
|
32
|
+
token-only mode is guaranteed to fail against a hardened issuer — this turns that latent
|
|
33
|
+
deployment fault into an explicit, fail-closed boot error. Legacy mode remains valid for
|
|
34
|
+
local/development.
|
|
35
|
+
- **Coherent per-consumer private-auth group validation.** The same validator now rejects
|
|
36
|
+
(in every environment) `SERVICE_TOKEN_EXCHANGE_ENABLED=true` without `INTERNAL_CLIENT_ID`
|
|
37
|
+
(exchange has no identity to present and would silently downgrade to legacy) and
|
|
38
|
+
`INTERNAL_CLIENT_ID` set without `PRIVATE_API_SECRET` (per-consumer mode needs its
|
|
39
|
+
bootstrap secret). No validation message echoes a secret value.
|
|
40
|
+
- **PyPI Trusted Publishing is now the sole publish mechanism (item 11.6).** The long-lived
|
|
41
|
+
`PYPI_API_TOKEN` secret was removed from `PiPy.yml`; the release job publishes via OIDC
|
|
42
|
+
(`id-token: write`) into a protected `pypi` environment. No API token is stored in the repo
|
|
43
|
+
or referenced by any workflow.
|
|
44
|
+
- **CI policy tests (item 11.7).** New `tests/test_ci_policy.py` asserts the supply-chain
|
|
45
|
+
posture in CI: no `PYPI_API_TOKEN` reference, `id-token: write` present, the protected
|
|
46
|
+
`pypi` environment is used, the duplicate `ci.yml` is gone, the secret-scan job is present,
|
|
47
|
+
every action is SHA-pinned in both workflows, and key runtime packages are pinned in
|
|
48
|
+
`constraints-all.txt`.
|
|
49
|
+
- **Locked dependency constraints (item 11.8).** `constraints.txt` and `constraints-all.txt`
|
|
50
|
+
are generated via `pip-compile` from public PyPI only (no custom index), giving CI a
|
|
51
|
+
reproducible, fully-pinned install closure.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- CI workflows consolidated — the secret-scan job was absorbed into `CI.yaml` and the
|
|
56
|
+
duplicate `ci.yml` removed; dependabot SHA bumps for `checkout`, `setup-python`,
|
|
57
|
+
`sbom-action`, `codecov-action`, and `gitleaks-action` applied across both workflows.
|
|
58
|
+
- `COMPAT_MATRIX` gains a `3.3` entry (`auth-sdk-m8 >=2.1.0,<3.0.0`, unchanged floor).
|
|
59
|
+
|
|
60
|
+
### Docs
|
|
61
|
+
|
|
62
|
+
- README *Per-consumer internal auth* section and the defaults-by-layer table now document
|
|
63
|
+
that legacy mode is development-only and fatal under production/strict; removed wording
|
|
64
|
+
that implied the issuer still offers a shared-secret fallback. Compatibility table updated
|
|
65
|
+
with the `3.1.0`–`3.3.0` rows.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## [3.2.0] — 2026-06-28 · ungated `/health` constant liveness body (item 9.4 Design B)
|
|
70
|
+
|
|
71
|
+
> **MINOR — additive, backward-compatible.** The only behaviour change is in the ungated
|
|
72
|
+
> `/health` response: callers without a valid `HEALTH_DETAIL_CREDENTIAL` now always
|
|
73
|
+
> receive `200 {"status":"ok"}` instead of the real aggregate status. Callers that
|
|
74
|
+
> already present the credential are unaffected — they still get the real status, per-check
|
|
75
|
+
> detail, and the 503 HTTP code when checks fail. `auth-sdk-m8` floor unchanged at
|
|
76
|
+
> `>=2.1.0,<3.0.0`.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- **Ungated `/health` body is now a constant liveness response (item 9.4 Design B).**
|
|
81
|
+
Callers without a valid `HEALTH_DETAIL_CREDENTIAL` (or `detail_public=True`) always
|
|
82
|
+
receive `200 {"status":"ok"}`, regardless of the real aggregate health. Previously the
|
|
83
|
+
ungated branch returned `{"status": overall.value}`, which leaked `degraded` — a
|
|
84
|
+
public attack-timing oracle that signals when fail-open degradation is active.
|
|
85
|
+
|
|
86
|
+
Health checks are **not run** for ungated requests (pure liveness, no dependency
|
|
87
|
+
probing); caching is unaffected for credentialed callers. Credentialed callers
|
|
88
|
+
(valid `X-Internal-Token` matching `HEALTH_DETAIL_CREDENTIAL`, or `detail_public=True`)
|
|
89
|
+
continue to receive the real aggregate status, the per-check body, and the correct
|
|
90
|
+
HTTP status code (200 or 503).
|
|
91
|
+
|
|
92
|
+
- **README `Health Detail Gating` section updated** to document the constant ungated
|
|
93
|
+
body and the Design B rationale; the defaults-by-layer table row for
|
|
94
|
+
`HEALTH_DETAIL_CREDENTIAL` updated accordingly.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
8
98
|
## [3.1.0] — 2026-06-24/25 · consumer-side post-1.0 remediation (9.1 / 5.5 / 7.x.1 / 9.2 / 10.1) + event-stream 9.1 follow-on
|
|
9
99
|
|
|
10
100
|
> **MINOR — additive, backward-compatible.** Default behaviour is unchanged: a consumer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-m8
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: FastAPI application framework for m8 consumer microservices.
|
|
5
5
|
Author-email: Eli Serra <e.serra173@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -216,7 +216,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
216
216
|
Classifier: Topic :: Software Development :: Libraries
|
|
217
217
|
Requires-Python: >=3.11
|
|
218
218
|
Requires-Dist: anyio>=4.0
|
|
219
|
-
Requires-Dist: auth-sdk-m8[config,events,fastapi,observability,security]<3.0.0,>=2.1.
|
|
219
|
+
Requires-Dist: auth-sdk-m8[config,events,fastapi,observability,security]<3.0.0,>=2.1.1
|
|
220
220
|
Requires-Dist: fastapi>=0.136.3
|
|
221
221
|
Requires-Dist: httpx>=0.27.0
|
|
222
222
|
Requires-Dist: packaging>=24.0
|
|
@@ -620,18 +620,27 @@ Required only when `TOKEN_MODE=stateful` and `AUTH_SERVICE_ROLE=consumer`.
|
|
|
620
620
|
#### Per-consumer internal auth (item 9.1)
|
|
621
621
|
|
|
622
622
|
By default a consumer authenticates private calls with the single shared
|
|
623
|
-
`PRIVATE_API_SECRET` (legacy mode)
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
623
|
+
`PRIVATE_API_SECRET` (legacy mode). **Legacy mode is development-only.** `fa-auth-m8`
|
|
624
|
+
has retired the issuer's legacy single-shared-secret fallback — its
|
|
625
|
+
`PRIVATE_API_CONSUMERS` registry is required in production/strict (item 11.2a) — so a
|
|
626
|
+
production/strict consumer left in legacy mode is guaranteed to fail against a hardened
|
|
627
|
+
issuer. Set `INTERNAL_CLIENT_ID` to switch to the **per-consumer** model:
|
|
628
|
+
`PRIVATE_API_SECRET` becomes this service's *bootstrap* secret, fa-auth authorizes each
|
|
629
|
+
private route by the credential's granted scope (deny-by-default), and the blast radius
|
|
630
|
+
collapses from the whole fleet to one consumer. Optionally exchange the bootstrap
|
|
631
|
+
credential for short-TTL `Authorization: Bearer` service tokens so rotation comes for
|
|
632
|
+
free from the token TTL. Selection is purely by config — the home lab keeps working
|
|
633
|
+
untouched.
|
|
634
|
+
|
|
635
|
+
> **Fatal under production/strict (item 11.2b).** When `ENVIRONMENT=production` or
|
|
636
|
+
> `STRICT_PRODUCTION_MODE=true`, settings validation fails at construction if
|
|
637
|
+
> `INTROSPECTION_URL` is set but `INTERNAL_CLIENT_ID` is unset. Two coherence rules
|
|
638
|
+
> apply in every environment: `SERVICE_TOKEN_EXCHANGE_ENABLED=true` requires
|
|
639
|
+
> `INTERNAL_CLIENT_ID`, and setting `INTERNAL_CLIENT_ID` requires `PRIVATE_API_SECRET`.
|
|
631
640
|
|
|
632
641
|
| Variable | Required | Default | Description |
|
|
633
642
|
|---|---|---|---|
|
|
634
|
-
| `INTERNAL_CLIENT_ID` | No | — | This consumer's `X-Internal-Client` id. Unset = legacy single-secret mode; set = per-consumer bootstrap mode. |
|
|
643
|
+
| `INTERNAL_CLIENT_ID` | No (Yes in prod/strict when `INTROSPECTION_URL` is set) | — | This consumer's `X-Internal-Client` id. Unset = legacy single-secret mode (development-only; rejected under production/strict once `INTROSPECTION_URL` is set); set = per-consumer bootstrap mode. |
|
|
635
644
|
| `SERVICE_TOKEN_EXCHANGE_ENABLED` | No | `false` | Exchange the bootstrap credential for short-TTL Bearer service tokens at `{issuer}/private/v1/service-token` (requires `INTERNAL_CLIENT_ID`). |
|
|
636
645
|
| `SERVICE_TOKEN_SCOPES` | No | `["introspection"]` | Scopes requested when minting a service token; fa-auth narrows to the subset the bootstrap credential was granted. |
|
|
637
646
|
| `SERVICE_TOKEN_REFRESH_LEEWAY_SECONDS` | No | `30` | Refresh a cached service token this many seconds before its `exp` so a call never races expiry. |
|
|
@@ -724,11 +733,19 @@ do not connect to Redis directly.
|
|
|
724
733
|
| `REDIS_PASSWORD` | Redis password |
|
|
725
734
|
| `REDIS_SSL` | Enable TLS (`true`/`false`, default `false`) |
|
|
726
735
|
|
|
727
|
-
### Health Detail Gating (
|
|
736
|
+
### Health Detail Gating (items 9.3 + 9.4)
|
|
728
737
|
|
|
729
738
|
| Variable | Default | Description |
|
|
730
739
|
|---|---|---|
|
|
731
|
-
| `HEALTH_DETAIL_CREDENTIAL` | — | Optional credential for the `/health` detail body gate. When **unset**, `/health` returns
|
|
740
|
+
| `HEALTH_DETAIL_CREDENTIAL` | — | Optional credential for the `/health` detail body gate. When **unset**, `/health` returns a constant `{"status":"ok"}` to all callers (fail-closed liveness response). When set, callers must present `X-Internal-Token: <value>` (constant-time match) to receive the real aggregate status, per-check breakdown, and the correct HTTP code (200 or 503). **Must not equal `PRIVATE_API_SECRET`** — accidental reuse is a fatal startup misconfiguration. Supports `_FILE` mount: set `HEALTH_DETAIL_CREDENTIAL_FILE=/run/secrets/health_cred.txt`. |
|
|
741
|
+
|
|
742
|
+
> **Constant ungated body (item 9.4 Design B):** ungated callers always receive
|
|
743
|
+
> `200 {"status":"ok"}` regardless of the real aggregate health (including `degraded`
|
|
744
|
+
> or `fail`). This prevents operational-state leakage on public-HTTPS stacks: a
|
|
745
|
+
> `degraded` response is a timing oracle that signals fail-open degradation is active.
|
|
746
|
+
> Only callers presenting a valid `HEALTH_DETAIL_CREDENTIAL` see the real status, the
|
|
747
|
+
> per-check detail, and the 503 HTTP code when checks fail. Liveness probes and
|
|
748
|
+
> `{API_PREFIX}/ping` are unaffected.
|
|
732
749
|
|
|
733
750
|
> **No-reuse enforcement (item 9.3):** at startup, `create_app` asserts that neither
|
|
734
751
|
> `HEALTH_DETAIL_CREDENTIAL` nor `METRICS_SCRAPE_CREDENTIAL` equals `PRIVATE_API_SECRET`.
|
|
@@ -827,8 +844,8 @@ inherited from `auth-sdk-m8`, and made *fatal* only when pointed at production.
|
|
|
827
844
|
| `ALLOWED_HOSTS` | inherited from `CommonSettings`; unset = no host check (dev) | unset under `STRICT_PRODUCTION_MODE` (strict fatal); wildcard `*` under strict |
|
|
828
845
|
| `EVENT_SIGNING_ENABLED` / `EVENT_SIGNING_ACCEPT_UNSIGNED` | inherited secure defaults (signing on, unsigned rejected); the gate fires through the consumer's auto-run config-health (item 7.x.1) | `ENABLED=false` under strict; `ACCEPT_UNSIGNED=true` under production or strict |
|
|
829
846
|
| `METRICS_SCRAPE_CREDENTIAL` | unset = `/metrics` relies on network isolation only; set = constant-time `Authorization: Bearer` gate. **Must not equal `PRIVATE_API_SECRET`** (fatal reuse check at startup). | never fatal (network-isolation is a valid posture); reuse of `PRIVATE_API_SECRET` is fatal |
|
|
830
|
-
| `HEALTH_DETAIL_CREDENTIAL` (
|
|
831
|
-
| `INTERNAL_CLIENT_ID` (
|
|
847
|
+
| `HEALTH_DETAIL_CREDENTIAL` (items 9.3 + 9.4) | unset = `/health` returns a constant `{"status":"ok"}` to all callers (fail-closed liveness; real status never leaked); set = `X-Internal-Token` must match to receive the real aggregate status, per-check breakdown, and correct HTTP code. **Must not equal `PRIVATE_API_SECRET`** (fatal reuse check at startup). | never fatal (no credential = constant liveness response, which is a valid posture); reuse of `PRIVATE_API_SECRET` is fatal |
|
|
848
|
+
| `INTERNAL_CLIENT_ID` (items 9.1 + 11.2b) | unset = legacy single `PRIVATE_API_SECRET` (development-only); set = per-consumer bootstrap / service-token auth on private calls (must be coordinated with the issuer's `PRIVATE_API_CONSUMERS`) | **fatal** under production/strict when `INTROSPECTION_URL` is set but `INTERNAL_CLIENT_ID` is unset; also fatal (any env) when `SERVICE_TOKEN_EXCHANGE_ENABLED=true` without `INTERNAL_CLIENT_ID`, or `INTERNAL_CLIENT_ID` set without `PRIVATE_API_SECRET` |
|
|
832
849
|
| `_FILE` secret mounts | **inherited** from `CommonSettings` — every secret (`PRIVATE_API_SECRET_FILE`, `DB_PASSWORD_FILE`, `METRICS_SCRAPE_CREDENTIAL_FILE`, `HEALTH_DETAIL_CREDENTIAL_FILE`, …) can be sourced from `/run/secrets/*` with no code change | a referenced `<FIELD>_FILE` path is missing (fails closed at construction) |
|
|
833
850
|
|
|
834
851
|
---
|
|
@@ -1405,6 +1422,9 @@ async def test_health(client):
|
|
|
1405
1422
|
|
|
1406
1423
|
| `fastapi-m8` | `auth-sdk-m8` | Python |
|
|
1407
1424
|
|---|---|---|
|
|
1425
|
+
| `3.3.0` | `>=2.1.1, <3.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1426
|
+
| `3.2.0` | `>=2.1.0, <3.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1427
|
+
| `3.1.0` | `>=2.1.0, <3.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1408
1428
|
| `3.0.0` | `>=2.0.1, <3.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1409
1429
|
| `2.1.0` | `>=1.5.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1410
1430
|
| `2.0.0` | `>=1.4.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
@@ -364,18 +364,27 @@ Required only when `TOKEN_MODE=stateful` and `AUTH_SERVICE_ROLE=consumer`.
|
|
|
364
364
|
#### Per-consumer internal auth (item 9.1)
|
|
365
365
|
|
|
366
366
|
By default a consumer authenticates private calls with the single shared
|
|
367
|
-
`PRIVATE_API_SECRET` (legacy mode)
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
367
|
+
`PRIVATE_API_SECRET` (legacy mode). **Legacy mode is development-only.** `fa-auth-m8`
|
|
368
|
+
has retired the issuer's legacy single-shared-secret fallback — its
|
|
369
|
+
`PRIVATE_API_CONSUMERS` registry is required in production/strict (item 11.2a) — so a
|
|
370
|
+
production/strict consumer left in legacy mode is guaranteed to fail against a hardened
|
|
371
|
+
issuer. Set `INTERNAL_CLIENT_ID` to switch to the **per-consumer** model:
|
|
372
|
+
`PRIVATE_API_SECRET` becomes this service's *bootstrap* secret, fa-auth authorizes each
|
|
373
|
+
private route by the credential's granted scope (deny-by-default), and the blast radius
|
|
374
|
+
collapses from the whole fleet to one consumer. Optionally exchange the bootstrap
|
|
375
|
+
credential for short-TTL `Authorization: Bearer` service tokens so rotation comes for
|
|
376
|
+
free from the token TTL. Selection is purely by config — the home lab keeps working
|
|
377
|
+
untouched.
|
|
378
|
+
|
|
379
|
+
> **Fatal under production/strict (item 11.2b).** When `ENVIRONMENT=production` or
|
|
380
|
+
> `STRICT_PRODUCTION_MODE=true`, settings validation fails at construction if
|
|
381
|
+
> `INTROSPECTION_URL` is set but `INTERNAL_CLIENT_ID` is unset. Two coherence rules
|
|
382
|
+
> apply in every environment: `SERVICE_TOKEN_EXCHANGE_ENABLED=true` requires
|
|
383
|
+
> `INTERNAL_CLIENT_ID`, and setting `INTERNAL_CLIENT_ID` requires `PRIVATE_API_SECRET`.
|
|
375
384
|
|
|
376
385
|
| Variable | Required | Default | Description |
|
|
377
386
|
|---|---|---|---|
|
|
378
|
-
| `INTERNAL_CLIENT_ID` | No | — | This consumer's `X-Internal-Client` id. Unset = legacy single-secret mode; set = per-consumer bootstrap mode. |
|
|
387
|
+
| `INTERNAL_CLIENT_ID` | No (Yes in prod/strict when `INTROSPECTION_URL` is set) | — | This consumer's `X-Internal-Client` id. Unset = legacy single-secret mode (development-only; rejected under production/strict once `INTROSPECTION_URL` is set); set = per-consumer bootstrap mode. |
|
|
379
388
|
| `SERVICE_TOKEN_EXCHANGE_ENABLED` | No | `false` | Exchange the bootstrap credential for short-TTL Bearer service tokens at `{issuer}/private/v1/service-token` (requires `INTERNAL_CLIENT_ID`). |
|
|
380
389
|
| `SERVICE_TOKEN_SCOPES` | No | `["introspection"]` | Scopes requested when minting a service token; fa-auth narrows to the subset the bootstrap credential was granted. |
|
|
381
390
|
| `SERVICE_TOKEN_REFRESH_LEEWAY_SECONDS` | No | `30` | Refresh a cached service token this many seconds before its `exp` so a call never races expiry. |
|
|
@@ -468,11 +477,19 @@ do not connect to Redis directly.
|
|
|
468
477
|
| `REDIS_PASSWORD` | Redis password |
|
|
469
478
|
| `REDIS_SSL` | Enable TLS (`true`/`false`, default `false`) |
|
|
470
479
|
|
|
471
|
-
### Health Detail Gating (
|
|
480
|
+
### Health Detail Gating (items 9.3 + 9.4)
|
|
472
481
|
|
|
473
482
|
| Variable | Default | Description |
|
|
474
483
|
|---|---|---|
|
|
475
|
-
| `HEALTH_DETAIL_CREDENTIAL` | — | Optional credential for the `/health` detail body gate. When **unset**, `/health` returns
|
|
484
|
+
| `HEALTH_DETAIL_CREDENTIAL` | — | Optional credential for the `/health` detail body gate. When **unset**, `/health` returns a constant `{"status":"ok"}` to all callers (fail-closed liveness response). When set, callers must present `X-Internal-Token: <value>` (constant-time match) to receive the real aggregate status, per-check breakdown, and the correct HTTP code (200 or 503). **Must not equal `PRIVATE_API_SECRET`** — accidental reuse is a fatal startup misconfiguration. Supports `_FILE` mount: set `HEALTH_DETAIL_CREDENTIAL_FILE=/run/secrets/health_cred.txt`. |
|
|
485
|
+
|
|
486
|
+
> **Constant ungated body (item 9.4 Design B):** ungated callers always receive
|
|
487
|
+
> `200 {"status":"ok"}` regardless of the real aggregate health (including `degraded`
|
|
488
|
+
> or `fail`). This prevents operational-state leakage on public-HTTPS stacks: a
|
|
489
|
+
> `degraded` response is a timing oracle that signals fail-open degradation is active.
|
|
490
|
+
> Only callers presenting a valid `HEALTH_DETAIL_CREDENTIAL` see the real status, the
|
|
491
|
+
> per-check detail, and the 503 HTTP code when checks fail. Liveness probes and
|
|
492
|
+
> `{API_PREFIX}/ping` are unaffected.
|
|
476
493
|
|
|
477
494
|
> **No-reuse enforcement (item 9.3):** at startup, `create_app` asserts that neither
|
|
478
495
|
> `HEALTH_DETAIL_CREDENTIAL` nor `METRICS_SCRAPE_CREDENTIAL` equals `PRIVATE_API_SECRET`.
|
|
@@ -571,8 +588,8 @@ inherited from `auth-sdk-m8`, and made *fatal* only when pointed at production.
|
|
|
571
588
|
| `ALLOWED_HOSTS` | inherited from `CommonSettings`; unset = no host check (dev) | unset under `STRICT_PRODUCTION_MODE` (strict fatal); wildcard `*` under strict |
|
|
572
589
|
| `EVENT_SIGNING_ENABLED` / `EVENT_SIGNING_ACCEPT_UNSIGNED` | inherited secure defaults (signing on, unsigned rejected); the gate fires through the consumer's auto-run config-health (item 7.x.1) | `ENABLED=false` under strict; `ACCEPT_UNSIGNED=true` under production or strict |
|
|
573
590
|
| `METRICS_SCRAPE_CREDENTIAL` | unset = `/metrics` relies on network isolation only; set = constant-time `Authorization: Bearer` gate. **Must not equal `PRIVATE_API_SECRET`** (fatal reuse check at startup). | never fatal (network-isolation is a valid posture); reuse of `PRIVATE_API_SECRET` is fatal |
|
|
574
|
-
| `HEALTH_DETAIL_CREDENTIAL` (
|
|
575
|
-
| `INTERNAL_CLIENT_ID` (
|
|
591
|
+
| `HEALTH_DETAIL_CREDENTIAL` (items 9.3 + 9.4) | unset = `/health` returns a constant `{"status":"ok"}` to all callers (fail-closed liveness; real status never leaked); set = `X-Internal-Token` must match to receive the real aggregate status, per-check breakdown, and correct HTTP code. **Must not equal `PRIVATE_API_SECRET`** (fatal reuse check at startup). | never fatal (no credential = constant liveness response, which is a valid posture); reuse of `PRIVATE_API_SECRET` is fatal |
|
|
592
|
+
| `INTERNAL_CLIENT_ID` (items 9.1 + 11.2b) | unset = legacy single `PRIVATE_API_SECRET` (development-only); set = per-consumer bootstrap / service-token auth on private calls (must be coordinated with the issuer's `PRIVATE_API_CONSUMERS`) | **fatal** under production/strict when `INTROSPECTION_URL` is set but `INTERNAL_CLIENT_ID` is unset; also fatal (any env) when `SERVICE_TOKEN_EXCHANGE_ENABLED=true` without `INTERNAL_CLIENT_ID`, or `INTERNAL_CLIENT_ID` set without `PRIVATE_API_SECRET` |
|
|
576
593
|
| `_FILE` secret mounts | **inherited** from `CommonSettings` — every secret (`PRIVATE_API_SECRET_FILE`, `DB_PASSWORD_FILE`, `METRICS_SCRAPE_CREDENTIAL_FILE`, `HEALTH_DETAIL_CREDENTIAL_FILE`, …) can be sourced from `/run/secrets/*` with no code change | a referenced `<FIELD>_FILE` path is missing (fails closed at construction) |
|
|
577
594
|
|
|
578
595
|
---
|
|
@@ -1149,6 +1166,9 @@ async def test_health(client):
|
|
|
1149
1166
|
|
|
1150
1167
|
| `fastapi-m8` | `auth-sdk-m8` | Python |
|
|
1151
1168
|
|---|---|---|
|
|
1169
|
+
| `3.3.0` | `>=2.1.1, <3.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1170
|
+
| `3.2.0` | `>=2.1.0, <3.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1171
|
+
| `3.1.0` | `>=2.1.0, <3.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1152
1172
|
| `3.0.0` | `>=2.0.1, <3.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1153
1173
|
| `2.1.0` | `>=1.5.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1154
1174
|
| `2.0.0` | `>=1.4.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.12
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --extra=all --extra=dev --no-emit-index-url --output-file=constraints-all.txt pyproject.toml
|
|
6
|
+
#
|
|
7
|
+
alembic==1.18.5
|
|
8
|
+
# via fastapi-m8
|
|
9
|
+
annotated-doc==0.0.4
|
|
10
|
+
# via fastapi
|
|
11
|
+
annotated-types==0.7.0
|
|
12
|
+
# via pydantic
|
|
13
|
+
anyio[trio]==4.14.1
|
|
14
|
+
# via
|
|
15
|
+
# fastapi-m8
|
|
16
|
+
# fastapi-m8 (pyproject.toml)
|
|
17
|
+
# httpx
|
|
18
|
+
# pytest-anyio
|
|
19
|
+
# starlette
|
|
20
|
+
asgi-lifespan==2.1.0
|
|
21
|
+
# via fastapi-m8 (pyproject.toml)
|
|
22
|
+
ast-serialize==0.6.0
|
|
23
|
+
# via mypy
|
|
24
|
+
attrs==26.1.0
|
|
25
|
+
# via
|
|
26
|
+
# outcome
|
|
27
|
+
# trio
|
|
28
|
+
auth-sdk-m8[config,events,fastapi,observability,security]==2.1.1
|
|
29
|
+
# via
|
|
30
|
+
# fastapi-m8
|
|
31
|
+
# fastapi-m8 (pyproject.toml)
|
|
32
|
+
bandit==1.9.4
|
|
33
|
+
# via fastapi-m8 (pyproject.toml)
|
|
34
|
+
boolean-py==5.0
|
|
35
|
+
# via license-expression
|
|
36
|
+
cachecontrol[filecache]==0.14.4
|
|
37
|
+
# via
|
|
38
|
+
# cachecontrol
|
|
39
|
+
# pip-audit
|
|
40
|
+
certifi==2026.6.17
|
|
41
|
+
# via
|
|
42
|
+
# httpcore
|
|
43
|
+
# httpx
|
|
44
|
+
# requests
|
|
45
|
+
cffi==2.0.0
|
|
46
|
+
# via cryptography
|
|
47
|
+
charset-normalizer==3.4.7
|
|
48
|
+
# via requests
|
|
49
|
+
coverage[toml]==7.14.3
|
|
50
|
+
# via
|
|
51
|
+
# fastapi-m8 (pyproject.toml)
|
|
52
|
+
# pytest-cov
|
|
53
|
+
cryptography==49.0.0
|
|
54
|
+
# via auth-sdk-m8
|
|
55
|
+
cyclonedx-python-lib==11.11.0
|
|
56
|
+
# via pip-audit
|
|
57
|
+
defusedxml==0.7.1
|
|
58
|
+
# via py-serializable
|
|
59
|
+
dnspython==2.8.0
|
|
60
|
+
# via email-validator
|
|
61
|
+
email-validator==2.3.0
|
|
62
|
+
# via auth-sdk-m8
|
|
63
|
+
fastapi==0.139.0
|
|
64
|
+
# via
|
|
65
|
+
# auth-sdk-m8
|
|
66
|
+
# fastapi-m8
|
|
67
|
+
# fastapi-m8 (pyproject.toml)
|
|
68
|
+
fastapi-m8[all,db,mysql,postgres] @ file:///workspace/fastapi-m8
|
|
69
|
+
# via fastapi-m8 (pyproject.toml)
|
|
70
|
+
filelock==3.29.4
|
|
71
|
+
# via cachecontrol
|
|
72
|
+
greenlet==3.5.3
|
|
73
|
+
# via sqlalchemy
|
|
74
|
+
h11==0.16.0
|
|
75
|
+
# via httpcore
|
|
76
|
+
httpcore==1.0.9
|
|
77
|
+
# via httpx
|
|
78
|
+
httpx==0.28.1
|
|
79
|
+
# via
|
|
80
|
+
# auth-sdk-m8
|
|
81
|
+
# fastapi-m8
|
|
82
|
+
# fastapi-m8 (pyproject.toml)
|
|
83
|
+
idna==3.18
|
|
84
|
+
# via
|
|
85
|
+
# anyio
|
|
86
|
+
# email-validator
|
|
87
|
+
# httpx
|
|
88
|
+
# requests
|
|
89
|
+
# trio
|
|
90
|
+
iniconfig==2.3.0
|
|
91
|
+
# via pytest
|
|
92
|
+
librt==0.12.0
|
|
93
|
+
# via mypy
|
|
94
|
+
license-expression==30.4.4
|
|
95
|
+
# via cyclonedx-python-lib
|
|
96
|
+
mako==1.3.12
|
|
97
|
+
# via alembic
|
|
98
|
+
markdown-it-py==4.2.0
|
|
99
|
+
# via rich
|
|
100
|
+
markupsafe==3.0.3
|
|
101
|
+
# via mako
|
|
102
|
+
mdurl==0.1.2
|
|
103
|
+
# via markdown-it-py
|
|
104
|
+
msgpack==1.2.1
|
|
105
|
+
# via cachecontrol
|
|
106
|
+
mypy==2.1.0
|
|
107
|
+
# via fastapi-m8 (pyproject.toml)
|
|
108
|
+
mypy-extensions==1.1.0
|
|
109
|
+
# via mypy
|
|
110
|
+
outcome==1.3.0.post0
|
|
111
|
+
# via trio
|
|
112
|
+
packageurl-python==0.17.6
|
|
113
|
+
# via cyclonedx-python-lib
|
|
114
|
+
packaging==26.2
|
|
115
|
+
# via
|
|
116
|
+
# fastapi-m8
|
|
117
|
+
# fastapi-m8 (pyproject.toml)
|
|
118
|
+
# pip-audit
|
|
119
|
+
# pip-requirements-parser
|
|
120
|
+
# pytest
|
|
121
|
+
pathspec==1.1.1
|
|
122
|
+
# via mypy
|
|
123
|
+
pip-api==0.0.34
|
|
124
|
+
# via pip-audit
|
|
125
|
+
pip-audit==2.10.1
|
|
126
|
+
# via fastapi-m8 (pyproject.toml)
|
|
127
|
+
pip-requirements-parser==32.0.1
|
|
128
|
+
# via pip-audit
|
|
129
|
+
platformdirs==4.10.0
|
|
130
|
+
# via pip-audit
|
|
131
|
+
pluggy==1.6.0
|
|
132
|
+
# via
|
|
133
|
+
# pytest
|
|
134
|
+
# pytest-cov
|
|
135
|
+
prometheus-client==0.25.0
|
|
136
|
+
# via auth-sdk-m8
|
|
137
|
+
psycopg2-binary==2.9.12
|
|
138
|
+
# via fastapi-m8
|
|
139
|
+
py-serializable==2.1.0
|
|
140
|
+
# via cyclonedx-python-lib
|
|
141
|
+
pycparser==3.0
|
|
142
|
+
# via cffi
|
|
143
|
+
pydantic==2.13.4
|
|
144
|
+
# via
|
|
145
|
+
# auth-sdk-m8
|
|
146
|
+
# fastapi
|
|
147
|
+
# pydantic-settings
|
|
148
|
+
# sqlmodel
|
|
149
|
+
pydantic-core==2.46.4
|
|
150
|
+
# via pydantic
|
|
151
|
+
pydantic-settings==2.14.2
|
|
152
|
+
# via auth-sdk-m8
|
|
153
|
+
pygments==2.20.0
|
|
154
|
+
# via
|
|
155
|
+
# pytest
|
|
156
|
+
# rich
|
|
157
|
+
pyjwt==2.13.0
|
|
158
|
+
# via auth-sdk-m8
|
|
159
|
+
pymysql==1.2.0
|
|
160
|
+
# via fastapi-m8
|
|
161
|
+
pyparsing==3.3.2
|
|
162
|
+
# via pip-requirements-parser
|
|
163
|
+
pytest==9.1.1
|
|
164
|
+
# via
|
|
165
|
+
# fastapi-m8 (pyproject.toml)
|
|
166
|
+
# pytest-anyio
|
|
167
|
+
# pytest-cov
|
|
168
|
+
pytest-anyio==0.0.0
|
|
169
|
+
# via fastapi-m8 (pyproject.toml)
|
|
170
|
+
pytest-cov==7.1.0
|
|
171
|
+
# via fastapi-m8 (pyproject.toml)
|
|
172
|
+
python-dotenv==1.2.2
|
|
173
|
+
# via pydantic-settings
|
|
174
|
+
pyyaml==6.0.3
|
|
175
|
+
# via bandit
|
|
176
|
+
requests==2.34.2
|
|
177
|
+
# via
|
|
178
|
+
# cachecontrol
|
|
179
|
+
# pip-audit
|
|
180
|
+
rich==15.0.0
|
|
181
|
+
# via
|
|
182
|
+
# bandit
|
|
183
|
+
# pip-audit
|
|
184
|
+
ruff==0.15.20
|
|
185
|
+
# via fastapi-m8 (pyproject.toml)
|
|
186
|
+
sniffio==1.3.1
|
|
187
|
+
# via
|
|
188
|
+
# asgi-lifespan
|
|
189
|
+
# trio
|
|
190
|
+
sortedcontainers==2.4.0
|
|
191
|
+
# via
|
|
192
|
+
# cyclonedx-python-lib
|
|
193
|
+
# trio
|
|
194
|
+
sqlalchemy==2.0.51
|
|
195
|
+
# via
|
|
196
|
+
# alembic
|
|
197
|
+
# fastapi-m8
|
|
198
|
+
# sqlmodel
|
|
199
|
+
sqlmodel==0.0.39
|
|
200
|
+
# via fastapi-m8
|
|
201
|
+
starlette==1.3.1
|
|
202
|
+
# via
|
|
203
|
+
# auth-sdk-m8
|
|
204
|
+
# fastapi
|
|
205
|
+
stevedore==5.8.0
|
|
206
|
+
# via bandit
|
|
207
|
+
tenacity==9.1.4
|
|
208
|
+
# via fastapi-m8 (pyproject.toml)
|
|
209
|
+
tomli==2.4.1
|
|
210
|
+
# via pip-audit
|
|
211
|
+
tomli-w==1.2.0
|
|
212
|
+
# via pip-audit
|
|
213
|
+
trio==0.33.0
|
|
214
|
+
# via anyio
|
|
215
|
+
typing-extensions==4.16.0
|
|
216
|
+
# via
|
|
217
|
+
# alembic
|
|
218
|
+
# anyio
|
|
219
|
+
# cyclonedx-python-lib
|
|
220
|
+
# fastapi
|
|
221
|
+
# mypy
|
|
222
|
+
# pydantic
|
|
223
|
+
# pydantic-core
|
|
224
|
+
# sqlalchemy
|
|
225
|
+
# sqlmodel
|
|
226
|
+
# starlette
|
|
227
|
+
# typing-inspection
|
|
228
|
+
typing-inspection==0.4.2
|
|
229
|
+
# via
|
|
230
|
+
# fastapi
|
|
231
|
+
# pydantic
|
|
232
|
+
# pydantic-settings
|
|
233
|
+
urllib3==2.7.0
|
|
234
|
+
# via requests
|
|
235
|
+
|
|
236
|
+
# The following packages are considered to be unsafe in a requirements file:
|
|
237
|
+
# pip
|