SessionArmor 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sessionarmor-0.1.0/.github/workflows/publish.yaml +41 -0
- sessionarmor-0.1.0/.github/workflows/tests.yaml +49 -0
- sessionarmor-0.1.0/.gitignore +94 -0
- sessionarmor-0.1.0/CHANGELOG.md +20 -0
- sessionarmor-0.1.0/LICENSE +21 -0
- sessionarmor-0.1.0/PKG-INFO +174 -0
- sessionarmor-0.1.0/README.md +138 -0
- sessionarmor-0.1.0/SECURITY.md +44 -0
- sessionarmor-0.1.0/pyproject.toml +98 -0
- sessionarmor-0.1.0/src/session_armor/__init__.py +29 -0
- sessionarmor-0.1.0/src/session_armor/audit.py +163 -0
- sessionarmor-0.1.0/src/session_armor/gate.py +213 -0
- sessionarmor-0.1.0/src/session_armor/py.typed +0 -0
- sessionarmor-0.1.0/src/session_armor/session.py +263 -0
- sessionarmor-0.1.0/src/session_armor/utils.py +93 -0
- sessionarmor-0.1.0/tests/__init__.py +0 -0
- sessionarmor-0.1.0/tests/conftest.py +9 -0
- sessionarmor-0.1.0/tests/settings.py +36 -0
- sessionarmor-0.1.0/tests/test_audit.py +117 -0
- sessionarmor-0.1.0/tests/test_gate.py +161 -0
- sessionarmor-0.1.0/tests/test_session.py +154 -0
- sessionarmor-0.1.0/tests/test_utils.py +133 -0
- sessionarmor-0.1.0/tests/urls.py +11 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
pypi-publish:
|
|
13
|
+
name: Upload release to PyPI
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
environment:
|
|
16
|
+
name: pypi
|
|
17
|
+
url: https://pypi.org/p/SessionArmor
|
|
18
|
+
permissions:
|
|
19
|
+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
|
20
|
+
contents: read
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout code
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
|
|
30
|
+
- name: Install build dependencies
|
|
31
|
+
run: |
|
|
32
|
+
python -m pip install --upgrade pip
|
|
33
|
+
pip install build
|
|
34
|
+
|
|
35
|
+
- name: Build package
|
|
36
|
+
run: python -m build
|
|
37
|
+
|
|
38
|
+
- name: Publish distribution 📦 to PyPI
|
|
39
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
40
|
+
with:
|
|
41
|
+
print_hash: true
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "main" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "main" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install .[dev]
|
|
28
|
+
|
|
29
|
+
- name: Lint with ruff
|
|
30
|
+
run: |
|
|
31
|
+
ruff check src/session_armor tests
|
|
32
|
+
|
|
33
|
+
- name: Type check with mypy
|
|
34
|
+
run: |
|
|
35
|
+
mypy src/session_armor --ignore-missing-imports
|
|
36
|
+
continue-on-error: true
|
|
37
|
+
|
|
38
|
+
- name: Run tests with coverage
|
|
39
|
+
run: |
|
|
40
|
+
export PYTHONPATH=$PYTHONPATH:$(pwd)/src
|
|
41
|
+
pytest tests/ --cov=session_armor --cov-report=xml --cov-report=term-missing
|
|
42
|
+
|
|
43
|
+
- name: Upload coverage to Codecov
|
|
44
|
+
uses: codecov/codecov-action@v4
|
|
45
|
+
if: matrix.python-version == '3.12'
|
|
46
|
+
with:
|
|
47
|
+
files: ./coverage.xml
|
|
48
|
+
fail_ci_if_error: false
|
|
49
|
+
verbose: true
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
*.manifest
|
|
31
|
+
*.spec
|
|
32
|
+
|
|
33
|
+
# Installer logs
|
|
34
|
+
pip-log.txt
|
|
35
|
+
pip-delete-this-directory.txt
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
htmlcov/
|
|
39
|
+
.tox/
|
|
40
|
+
.nox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
*.py,cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
cover/
|
|
51
|
+
|
|
52
|
+
# Translations
|
|
53
|
+
*.mo
|
|
54
|
+
*.pot
|
|
55
|
+
|
|
56
|
+
# Django stuff:
|
|
57
|
+
*.log
|
|
58
|
+
local_settings.py
|
|
59
|
+
db.sqlite3
|
|
60
|
+
db.sqlite3-journal
|
|
61
|
+
|
|
62
|
+
# Environments
|
|
63
|
+
.env
|
|
64
|
+
.envrc
|
|
65
|
+
.venv
|
|
66
|
+
env/
|
|
67
|
+
venv/
|
|
68
|
+
ENV/
|
|
69
|
+
env.bak/
|
|
70
|
+
venv.bak/
|
|
71
|
+
|
|
72
|
+
# IDE
|
|
73
|
+
.idea/
|
|
74
|
+
.vscode/
|
|
75
|
+
*.swp
|
|
76
|
+
*.swo
|
|
77
|
+
*~
|
|
78
|
+
|
|
79
|
+
# mypy
|
|
80
|
+
.mypy_cache/
|
|
81
|
+
.dmypy.json
|
|
82
|
+
dmypy.json
|
|
83
|
+
|
|
84
|
+
# ruff
|
|
85
|
+
.ruff_cache/
|
|
86
|
+
|
|
87
|
+
# Pyre type checker
|
|
88
|
+
.pyre/
|
|
89
|
+
|
|
90
|
+
# pytype static type analyzer
|
|
91
|
+
.pytype/
|
|
92
|
+
|
|
93
|
+
# Cython debug symbols
|
|
94
|
+
cython_debug/
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-05-22
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **SessionSecurityMiddleware** — Client fingerprint binding (IP + User-Agent hash), absolute/idle session timeouts, JWT claim drift detection, session fixation prevention via `cycle_key()`.
|
|
14
|
+
- **AuditMiddleware** — Privacy-preserving audit trail for authenticated requests with request ID correlation, hashed IPs, sanitized log entries.
|
|
15
|
+
- **GateMiddleware** — Base class for building cacheable, hookable conditional gates (compliance, onboarding, feature flags, maintenance).
|
|
16
|
+
- **Utilities** — `get_client_ip()` with proxy-depth awareness, `hash_value()`, `sanitize_for_log()` with control character stripping, `is_authenticated()`.
|
|
17
|
+
- Full hook system on all three middleware classes for zero-subclass-needed customization.
|
|
18
|
+
- `SESSION_ARMOR_TRUSTED_PROXY_DEPTH` setting to prevent X-Forwarded-For spoofing.
|
|
19
|
+
- `fail_open=False` as defense-grade default on GateMiddleware.
|
|
20
|
+
- CI/CD with GitHub Actions (tests on Python 3.10–3.13, PyPI publishing via trusted publishing).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Alex C
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: SessionArmor
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Defense-grade Django middleware: session security, audit logging, and configurable request gates with hooks.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Tunet-xyz/session_armor
|
|
6
|
+
Project-URL: Documentation, https://github.com/Tunet-xyz/session_armor#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/Tunet-xyz/session_armor
|
|
8
|
+
Project-URL: Issues, https://github.com/Tunet-xyz/session_armor/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Tunet-xyz/session_armor/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Alex C <alex@coded.uk>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: audit,django,gates,middleware,nist,owasp,security,session
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Framework :: Django :: 4.2
|
|
16
|
+
Classifier: Framework :: Django :: 5.0
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Security
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: django>=4.2
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: django-stubs>=4.2.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-django>=4.5; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# SessionArmor
|
|
38
|
+
|
|
39
|
+
[](https://github.com/Tunet-xyz/session_armor/actions/workflows/tests.yaml)
|
|
40
|
+
[](https://pypi.org/project/SessionArmor/)
|
|
41
|
+
[](https://www.python.org/downloads/)
|
|
42
|
+
[](https://www.djangoproject.com/)
|
|
43
|
+
[](https://opensource.org/licenses/MIT)
|
|
44
|
+
|
|
45
|
+
**Defense-grade Django middleware: session security, audit logging, and configurable request gates with hooks.**
|
|
46
|
+
|
|
47
|
+
Part of the [Tunet](https://github.com/Tunet-xyz) ecosystem — alongside [SwapLayer](https://github.com/Tunet-xyz/swap_layer) and [InfraGlyph](https://github.com/Tunet-xyz/infra_glyph).
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## What It Does
|
|
52
|
+
|
|
53
|
+
SessionArmor provides three drop-in Django middleware classes that harden your application's session layer:
|
|
54
|
+
|
|
55
|
+
| Middleware | Purpose |
|
|
56
|
+
|-----------|---------|
|
|
57
|
+
| `SessionSecurityMiddleware` | Fingerprint binding, absolute/idle timeouts, claim drift detection |
|
|
58
|
+
| `AuditMiddleware` | Privacy-preserving security audit trail for every authenticated request |
|
|
59
|
+
| `GateMiddleware` | Base class for building cacheable conditional gates (compliance, onboarding, feature flags) |
|
|
60
|
+
|
|
61
|
+
All three are **hookable** — override methods to customize behavior without touching internals.
|
|
62
|
+
|
|
63
|
+
## References
|
|
64
|
+
|
|
65
|
+
- [NIST SP 800-53 AC-12](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final) (Session Termination)
|
|
66
|
+
- [NIST SP 800-53 SC-23](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final) (Session Authenticity)
|
|
67
|
+
- [NIST SP 800-53 AU-3/AU-12](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final) (Audit Content/Generation)
|
|
68
|
+
- [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install SessionArmor
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
# settings.py
|
|
82
|
+
MIDDLEWARE = [
|
|
83
|
+
'django.middleware.security.SecurityMiddleware',
|
|
84
|
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
85
|
+
# ... your auth middleware ...
|
|
86
|
+
'session_armor.session.SessionSecurityMiddleware',
|
|
87
|
+
'session_armor.audit.AuditMiddleware',
|
|
88
|
+
# ... rest of your middleware ...
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
# Optional settings (shown with defaults)
|
|
92
|
+
SESSION_ABSOLUTE_TIMEOUT = 28800 # 8 hours
|
|
93
|
+
SESSION_IDLE_TIMEOUT = 1800 # 30 minutes
|
|
94
|
+
SESSION_BIND_IP = True
|
|
95
|
+
SESSION_BIND_USER_AGENT = True
|
|
96
|
+
SESSION_DETECT_CLAIM_DRIFT = True
|
|
97
|
+
SESSION_ARMOR_TRUSTED_PROXY_DEPTH = 1 # Number of trusted reverse proxies
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Building a Gate
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from session_armor import GateMiddleware
|
|
104
|
+
from django.shortcuts import redirect
|
|
105
|
+
|
|
106
|
+
class ComplianceGate(GateMiddleware):
|
|
107
|
+
gate_id = 'compliance'
|
|
108
|
+
cache_ttl_setting = 'COMPLIANCE_CACHE_TTL'
|
|
109
|
+
default_cache_ttl = 3600
|
|
110
|
+
fail_open = False # Block access if check fails
|
|
111
|
+
|
|
112
|
+
def check(self, request) -> bool:
|
|
113
|
+
return user_accepted_current_terms(request)
|
|
114
|
+
|
|
115
|
+
def on_reject(self, request):
|
|
116
|
+
return redirect('/accept-terms/')
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Customizing Session Security
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from session_armor import SessionSecurityMiddleware
|
|
123
|
+
|
|
124
|
+
class MySessionSecurity(SessionSecurityMiddleware):
|
|
125
|
+
exempt_paths = ('/health/', '/static/')
|
|
126
|
+
|
|
127
|
+
def get_critical_claims(self, user):
|
|
128
|
+
# Auth0 / OIDC claims that must not change mid-session
|
|
129
|
+
return [user.sub, user.organization_uuid, user.platform]
|
|
130
|
+
|
|
131
|
+
def get_login_url(self, request):
|
|
132
|
+
platform = getattr(request.user, 'platform', '')
|
|
133
|
+
return f'/{platform}/login/' if platform else '/login/'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Customizing Audit Logging
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from session_armor import AuditMiddleware
|
|
140
|
+
|
|
141
|
+
class MyAudit(AuditMiddleware):
|
|
142
|
+
exempt_paths = ('/health/', '/static/', '/favicon.ico')
|
|
143
|
+
|
|
144
|
+
def get_user_identity(self, request):
|
|
145
|
+
return {
|
|
146
|
+
'uid': request.user.sub,
|
|
147
|
+
'platform': request.user.platform,
|
|
148
|
+
'org': request.user.organization_uuid,
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Clone and install
|
|
158
|
+
git clone https://github.com/Tunet-xyz/session_armor.git
|
|
159
|
+
cd session_armor
|
|
160
|
+
pip install -e ".[dev]"
|
|
161
|
+
|
|
162
|
+
# Run tests
|
|
163
|
+
pytest
|
|
164
|
+
|
|
165
|
+
# Lint
|
|
166
|
+
ruff check src/session_armor tests
|
|
167
|
+
|
|
168
|
+
# Type check
|
|
169
|
+
mypy src/session_armor
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# SessionArmor
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Tunet-xyz/session_armor/actions/workflows/tests.yaml)
|
|
4
|
+
[](https://pypi.org/project/SessionArmor/)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](https://www.djangoproject.com/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
**Defense-grade Django middleware: session security, audit logging, and configurable request gates with hooks.**
|
|
10
|
+
|
|
11
|
+
Part of the [Tunet](https://github.com/Tunet-xyz) ecosystem — alongside [SwapLayer](https://github.com/Tunet-xyz/swap_layer) and [InfraGlyph](https://github.com/Tunet-xyz/infra_glyph).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What It Does
|
|
16
|
+
|
|
17
|
+
SessionArmor provides three drop-in Django middleware classes that harden your application's session layer:
|
|
18
|
+
|
|
19
|
+
| Middleware | Purpose |
|
|
20
|
+
|-----------|---------|
|
|
21
|
+
| `SessionSecurityMiddleware` | Fingerprint binding, absolute/idle timeouts, claim drift detection |
|
|
22
|
+
| `AuditMiddleware` | Privacy-preserving security audit trail for every authenticated request |
|
|
23
|
+
| `GateMiddleware` | Base class for building cacheable conditional gates (compliance, onboarding, feature flags) |
|
|
24
|
+
|
|
25
|
+
All three are **hookable** — override methods to customize behavior without touching internals.
|
|
26
|
+
|
|
27
|
+
## References
|
|
28
|
+
|
|
29
|
+
- [NIST SP 800-53 AC-12](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final) (Session Termination)
|
|
30
|
+
- [NIST SP 800-53 SC-23](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final) (Session Authenticity)
|
|
31
|
+
- [NIST SP 800-53 AU-3/AU-12](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final) (Audit Content/Generation)
|
|
32
|
+
- [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install SessionArmor
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# settings.py
|
|
46
|
+
MIDDLEWARE = [
|
|
47
|
+
'django.middleware.security.SecurityMiddleware',
|
|
48
|
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
49
|
+
# ... your auth middleware ...
|
|
50
|
+
'session_armor.session.SessionSecurityMiddleware',
|
|
51
|
+
'session_armor.audit.AuditMiddleware',
|
|
52
|
+
# ... rest of your middleware ...
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
# Optional settings (shown with defaults)
|
|
56
|
+
SESSION_ABSOLUTE_TIMEOUT = 28800 # 8 hours
|
|
57
|
+
SESSION_IDLE_TIMEOUT = 1800 # 30 minutes
|
|
58
|
+
SESSION_BIND_IP = True
|
|
59
|
+
SESSION_BIND_USER_AGENT = True
|
|
60
|
+
SESSION_DETECT_CLAIM_DRIFT = True
|
|
61
|
+
SESSION_ARMOR_TRUSTED_PROXY_DEPTH = 1 # Number of trusted reverse proxies
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Building a Gate
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from session_armor import GateMiddleware
|
|
68
|
+
from django.shortcuts import redirect
|
|
69
|
+
|
|
70
|
+
class ComplianceGate(GateMiddleware):
|
|
71
|
+
gate_id = 'compliance'
|
|
72
|
+
cache_ttl_setting = 'COMPLIANCE_CACHE_TTL'
|
|
73
|
+
default_cache_ttl = 3600
|
|
74
|
+
fail_open = False # Block access if check fails
|
|
75
|
+
|
|
76
|
+
def check(self, request) -> bool:
|
|
77
|
+
return user_accepted_current_terms(request)
|
|
78
|
+
|
|
79
|
+
def on_reject(self, request):
|
|
80
|
+
return redirect('/accept-terms/')
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Customizing Session Security
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from session_armor import SessionSecurityMiddleware
|
|
87
|
+
|
|
88
|
+
class MySessionSecurity(SessionSecurityMiddleware):
|
|
89
|
+
exempt_paths = ('/health/', '/static/')
|
|
90
|
+
|
|
91
|
+
def get_critical_claims(self, user):
|
|
92
|
+
# Auth0 / OIDC claims that must not change mid-session
|
|
93
|
+
return [user.sub, user.organization_uuid, user.platform]
|
|
94
|
+
|
|
95
|
+
def get_login_url(self, request):
|
|
96
|
+
platform = getattr(request.user, 'platform', '')
|
|
97
|
+
return f'/{platform}/login/' if platform else '/login/'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Customizing Audit Logging
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from session_armor import AuditMiddleware
|
|
104
|
+
|
|
105
|
+
class MyAudit(AuditMiddleware):
|
|
106
|
+
exempt_paths = ('/health/', '/static/', '/favicon.ico')
|
|
107
|
+
|
|
108
|
+
def get_user_identity(self, request):
|
|
109
|
+
return {
|
|
110
|
+
'uid': request.user.sub,
|
|
111
|
+
'platform': request.user.platform,
|
|
112
|
+
'org': request.user.organization_uuid,
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Clone and install
|
|
122
|
+
git clone https://github.com/Tunet-xyz/session_armor.git
|
|
123
|
+
cd session_armor
|
|
124
|
+
pip install -e ".[dev]"
|
|
125
|
+
|
|
126
|
+
# Run tests
|
|
127
|
+
pytest
|
|
128
|
+
|
|
129
|
+
# Lint
|
|
130
|
+
ruff check src/session_armor tests
|
|
131
|
+
|
|
132
|
+
# Type check
|
|
133
|
+
mypy src/session_armor
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
| ------- | ------------------ |
|
|
7
|
+
| 0.1.x | :white_check_mark: |
|
|
8
|
+
|
|
9
|
+
## Reporting a Vulnerability
|
|
10
|
+
|
|
11
|
+
We take security seriously. If you discover a security vulnerability in SessionArmor, please report it responsibly.
|
|
12
|
+
|
|
13
|
+
### How to Report
|
|
14
|
+
|
|
15
|
+
**Please DO NOT open a public GitHub issue for security vulnerabilities.**
|
|
16
|
+
|
|
17
|
+
Instead, please email security concerns to: **alex@coded.uk**
|
|
18
|
+
|
|
19
|
+
Include the following in your report:
|
|
20
|
+
- Description of the vulnerability
|
|
21
|
+
- Steps to reproduce
|
|
22
|
+
- Potential impact
|
|
23
|
+
- Suggested fix (if any)
|
|
24
|
+
|
|
25
|
+
### Response Timeline
|
|
26
|
+
|
|
27
|
+
- **Acknowledgement:** Within 48 hours
|
|
28
|
+
- **Initial assessment:** Within 5 business days
|
|
29
|
+
- **Fix release:** Within 30 days for critical issues
|
|
30
|
+
|
|
31
|
+
### Scope
|
|
32
|
+
|
|
33
|
+
The following are in scope:
|
|
34
|
+
- Session fixation or hijacking bypasses
|
|
35
|
+
- Fingerprint binding bypasses
|
|
36
|
+
- Log injection via sanitization bypass
|
|
37
|
+
- X-Forwarded-For spoofing past proxy depth controls
|
|
38
|
+
- Any bypass of gate middleware security checks
|
|
39
|
+
|
|
40
|
+
### Out of Scope
|
|
41
|
+
|
|
42
|
+
- Vulnerabilities in Django itself (report to Django security team)
|
|
43
|
+
- Issues requiring physical access to the server
|
|
44
|
+
- Social engineering attacks
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[tool.hatch.build.targets.wheel]
|
|
6
|
+
packages = ["src/session_armor"]
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "SessionArmor"
|
|
10
|
+
version = "0.1.0"
|
|
11
|
+
description = "Defense-grade Django middleware: session security, audit logging, and configurable request gates with hooks."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
license = "MIT"
|
|
15
|
+
keywords = ["django", "middleware", "security", "session", "audit", "gates", "nist", "owasp"]
|
|
16
|
+
authors = [
|
|
17
|
+
{ name = "Alex C", email = "alex@coded.uk" },
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Framework :: Django",
|
|
21
|
+
"Framework :: Django :: 4.2",
|
|
22
|
+
"Framework :: Django :: 5.0",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"License :: OSI Approved :: MIT License",
|
|
29
|
+
"Topic :: Security",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
31
|
+
"Intended Audience :: Developers",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"Django>=4.2",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=8.0",
|
|
40
|
+
"pytest-django>=4.5",
|
|
41
|
+
"pytest-cov>=4.0.0",
|
|
42
|
+
"ruff>=0.4",
|
|
43
|
+
"mypy>=1.0.0",
|
|
44
|
+
"django-stubs>=4.2.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://github.com/Tunet-xyz/session_armor"
|
|
49
|
+
Documentation = "https://github.com/Tunet-xyz/session_armor#readme"
|
|
50
|
+
Repository = "https://github.com/Tunet-xyz/session_armor"
|
|
51
|
+
Issues = "https://github.com/Tunet-xyz/session_armor/issues"
|
|
52
|
+
Changelog = "https://github.com/Tunet-xyz/session_armor/blob/main/CHANGELOG.md"
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
python_files = ["test_*.py"]
|
|
57
|
+
addopts = "-v --tb=short"
|
|
58
|
+
DJANGO_SETTINGS_MODULE = "tests.settings"
|
|
59
|
+
|
|
60
|
+
[tool.mypy]
|
|
61
|
+
python_version = "3.10"
|
|
62
|
+
warn_return_any = true
|
|
63
|
+
warn_unused_configs = true
|
|
64
|
+
disallow_untyped_defs = false
|
|
65
|
+
check_untyped_defs = true
|
|
66
|
+
plugins = ["mypy_django_plugin.main"]
|
|
67
|
+
ignore_missing_imports = true
|
|
68
|
+
|
|
69
|
+
[tool.django-stubs]
|
|
70
|
+
django_settings_module = "tests.settings"
|
|
71
|
+
|
|
72
|
+
[tool.ruff]
|
|
73
|
+
target-version = "py310"
|
|
74
|
+
line-length = 100
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint]
|
|
77
|
+
select = [
|
|
78
|
+
"E", # pycodestyle errors
|
|
79
|
+
"W", # pycodestyle warnings
|
|
80
|
+
"F", # pyflakes
|
|
81
|
+
"I", # isort
|
|
82
|
+
"B", # flake8-bugbear
|
|
83
|
+
"C4", # flake8-comprehensions
|
|
84
|
+
"UP", # pyupgrade
|
|
85
|
+
]
|
|
86
|
+
ignore = [
|
|
87
|
+
"E501", # line too long (handled by formatter)
|
|
88
|
+
"B008", # do not perform function calls in argument defaults
|
|
89
|
+
"B904", # Within except clause, raise with from err
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
[tool.ruff.lint.isort]
|
|
93
|
+
known-first-party = ["session_armor"]
|
|
94
|
+
|
|
95
|
+
[tool.coverage.run]
|
|
96
|
+
source = ["src/session_armor"]
|
|
97
|
+
branch = true
|
|
98
|
+
omit = ["*/tests/*", "*/__pycache__/*"]
|