Skip to main content

SessionArmor

Tests PyPI version Python 3.10+ Django 4.2-5.2 License: MIT

Framework-neutral session-assurance contracts with an optional Django adapter for session integrity, privacy-aware audit logging, and configurable request gates.

Part of the Tunet ecosystem — alongside SwapLayer and InfraGlyph. See the SessionArmor product page for the public capability overview.


What It Does

The dependency-free core provides immutable assurance evidence and typed gate decisions. The optional Django adapter provides three drop-in middleware classes that harden an application's session layer:

Middleware Purpose
SessionSecurityMiddleware Keyed continuity binding, validated absolute/idle timeouts, claim drift detection, and typed assurance evidence
AuditMiddleware Structured, privacy-aware security audit events for authenticated request outcomes
GateMiddleware Typed, identity-bound workflow gates with bounded, revocation-aware caching

All three are hookable — override methods to customize behavior without touching internals.

References

SessionArmor adds controls around an existing Django authentication and session setup; it does not replace HTTPS, secure cookie settings, CSRF protection, authorization, MFA, or incident response. Fingerprint binding is a replay signal rather than proof of device identity and can require tuning for mobile or privacy-network traffic. See the security model for deployment assumptions, limitations, and a release checklist.


Installation

pip install SessionArmor

The core install has no framework dependency. Install the Django adapter when middleware integration is required:

pip install "SessionArmor[django]"

Quick Start

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    # ... your auth middleware ...
    'session_armor.adapters.django.SessionSecurityMiddleware',
    'session_armor.adapters.django.AuditMiddleware',
    # ... rest of your middleware ...
]

# Optional settings (shown with defaults)
SESSION_ARMOR_ABSOLUTE_TIMEOUT = 28800
SESSION_ARMOR_IDLE_TIMEOUT = 1800
SESSION_ARMOR_BIND_IP = True
SESSION_ARMOR_BIND_USER_AGENT = True
SESSION_ARMOR_DETECT_CLAIM_DRIFT = True
SESSION_ARMOR_REQUIRE_BINDING = True
SESSION_ARMOR_CLOCK_SKEW = 60
SESSION_ARMOR_LAST_ACTIVE_RESOLUTION = 0
SESSION_ARMOR_NO_STORE = True
SESSION_ARMOR_STATE_KEY = SECRET_KEY
SESSION_ARMOR_AUDIT_PEPPER = SECRET_KEY

# Forwarded headers are ignored unless both values are configured.
SESSION_ARMOR_TRUSTED_PROXY_DEPTH = 0
SESSION_ARMOR_TRUSTED_PROXY_CIDRS = ()

Note on audit IP hashing. Audit logs pseudonymize the client IP with a keyed HMAC-SHA256 so the value can't be reversed from the (low-entropy) IPv4 space. Set a dedicated SESSION_ARMOR_AUDIT_PEPPER to rotate it independently of SECRET_KEY, or to share it across services for cross-log correlation. If unset, it falls back to SECRET_KEY.

Note on proxy trust. The secure default (DEPTH = 0) ignores X-Forwarded-For. To use it, configure both the exact number of proxy hops and their CIDR ranges. Each trusted hop is checked from the socket peer back toward the client; malformed chains and catch-all trusted networks are rejected.

Building a Gate

from session_armor import GateDecision
from session_armor.adapters.django import GateMiddleware
from django.shortcuts import redirect

class ComplianceGate(GateMiddleware):
    gate_id = 'compliance'
    cache_ttl_setting = 'COMPLIANCE_CACHE_TTL'
    default_cache_ttl = 3600
    requires_trusted_session = True

    def check(self, request) -> GateDecision:
        if user_accepted_current_terms(request):
            return GateDecision.allow('current_terms_accepted')
        return GateDecision.reject('terms_acceptance_required')

    def on_reject(self, request, decision):
        return redirect('/accept-terms/')

"Check once" gates

For gates that should only kick in once (onboarding, compliance), set recheck_after_pass = False: a passing result stays cached in the session with no TTL re-checks. To push new requirements to already-passed sessions (a new document version, a new onboarding step), implement get_state_version() against a shared cache key and bump that key on publish — every active session re-checks exactly once, with no per-request database hits:

from django.core.cache import cache

class ComplianceGate(GateMiddleware):
    gate_id = 'compliance'
    recheck_after_pass = False  # Pass once, stay passed

    def get_state_version(self, request) -> str | None:
        # Bumped by admin tooling when a new document version is published
        return cache.get(f'compliance_version:{request.user.platform}', '')

    def check(self, request) -> GateDecision:
        return (
            GateDecision.allow('current_terms_accepted')
            if user_accepted_current_terms(request)
            else GateDecision.reject('terms_acceptance_required')
        )

Sticky passes and revocation. With recheck_after_pass = False, a pass is cached until the session ends, invalidate() is called, or the state version changes — so it is not appropriate for authorization/entitlement gates where access may be revoked. For those, use a normal TTL gate, drive get_state_version(), or set sticky_pass_max_age (seconds) to cap how long a sticky pass may be reused before a forced re-check.

Customizing Session Security

from session_armor.adapters.django import SessionSecurityMiddleware

class MySessionSecurity(SessionSecurityMiddleware):
    exempt_paths = ('/health/', '/static/')
    last_active_resolution = 60  # only rewrite last-active timestamp every 60s

    def get_critical_claims(self, user):
        # Auth0 / OIDC claims that must not change mid-session
        return [user.sub, user.organization_uuid, user.platform]

    def get_login_url(self, request):
        platform = getattr(request.user, 'platform', '')
        return f'/{platform}/login/' if platform else '/login/'

# After a reviewed server-side claim update, use the public API rather than
# writing SessionArmor's private session keys.
MySessionSecurity(lambda request: request).rebaseline_claims(request)

AccessGate integration

SessionArmor and AccessGate are complementary boundaries. SessionArmor decides whether the authenticated session is still trustworthy and attaches an immutable request.session_assurance record. AccessGate's Django adapter carries that record into authorization context, where SessionAssurancePolicy denies stale, missing, malformed, or subject-mismatched evidence before business policies may grant an action:

from access_gate import AuthorizationEngine, RolePolicy, SessionAssurancePolicy

engine = AuthorizationEngine([
    SessionAssurancePolicy(actions={'records.read'}),
    RolePolicy({'records_reader'}, actions={'records.read'}),
])

The assurance policy is deny-only: a trusted session never grants a permission by itself. See docs/ACCESSGATE_INTEGRATION.md.

Customizing Audit Logging

from session_armor.adapters.django import AuditMiddleware

class MyAudit(AuditMiddleware):
    exempt_paths = ('/health/', '/static/', '/favicon.ico')

    def get_user_identity(self, request):
        return {
            'uid': request.user.sub,
            'platform': request.user.platform,
            'org': request.user.organization_uuid,
        }

Development

# Clone and install
git clone https://github.com/Tunet-xyz/session_armor.git
cd session_armor
pip install -e ".[dev]"

# Run tests
pytest

# Lint
ruff check src/session_armor tests

# Type check
mypy src/session_armor

Public Agent MCP Contract

SessionArmor includes a public agent-operability contract in mcp/. It describes how external agents can safely help with middleware ordering, security settings, audit customization, gate design, browser-visible docs, and rollout planning without requiring source-code access or production session data.

From a checkout, run the dependency-free contract server with python mcp/server.py. After installation, agents can use the packaged stdio command:

sessionarmor-mcp

License

MIT — Copyright (c) 2024-2026 Tunet Ltd. See LICENSE.

Metadata

Release files for SessionArmor 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for SessionArmor 0.5.0
File Size Uploaded Provenance Actions
sessionarmor-0.5.0.tar.gz 52.8 kB With provenance Details for sessionarmor-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for SessionArmor 0.5.0
File Interpreter ABI Platform Actions
sessionarmor-0.5.0-py3-none-any.whl Python 3 none any Details for sessionarmor-0.5.0-py3-none-any.whl

Total release size: 84.5 kB

Release files / sessionarmor-0.5.0.tar.gz

Download URL sessionarmor-0.5.0.tar.gz
Size 52.8 kB
Tags Source
SHA-256 checksum
How to use checksums
fd8e95be4ec35f0e47b3c50665d6786c4ea4e5aae4b2c0fa1de43d10e1f30db9
BLAKE2b-256 checksum
How to use checksums
83bef80255d7473c39989efc622bc67486697c95cb0b5dedb4da92b1ad807209
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

Release files / sessionarmor-0.5.0-py3-none-any.whl

Download URL sessionarmor-0.5.0-py3-none-any.whl
Size 31.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
73f13bef8664586cdfcce6153e63483896c0799bc24a1be8136f764d7ef8bb02
BLAKE2b-256 checksum
How to use checksums
c7e586dc12989cc4a306d67afc0587ebde93a53670f92d7e895fb63b71e7d23c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

Security for SessionArmor 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for sessionarmor-0.5.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 17, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page