Skip to content

Repository files navigation

Password Policy State-Space Lab

Exact combinatorics and one application-level uniform-rank selection for constrained password policies. Instead of assembling required characters and shuffling—or retrying until a candidate happens to pass—the lab counts the complete valid state space, assigns every member one stable rank, and samples that rank uniformly.

Real server-side validation run

This is a real loopback Waitress session, not a mockup. The capture visits the live GET page, submits an invalid length to the real Flask handler, and records its 400 response. A process-local guard makes the evidence build fail if the sampler is called, so no password is generated or placed in repository media.

What makes the core auditable

Guarantee Mechanism Review surface
Exact policy cardinality bounded bottom-up dynamic programming with arbitrary-precision integers CLI JSON/CSV and the web audit panel
Uniform sampling exactly one secrets.randbelow(total) followed by deterministic unrank source-derived sampling diagram and rank-selection call tests
Complete reversible ordering tested rank/unrank bijection over every valid string exhaustive small-space Cartesian oracles
Bounded failure malformed, impossible, or excessive policies fail before enumeration policy, request, and budget tests
Reproducible evidence deterministic raw outputs, source hashes, browser assertions, and artifact digests docs/evidence/manifest.json

Ranks are deterministic inspection tools, not credentials to reuse. A rank is a reversible encoding of its candidate under one exact ordered policy.

Setup and verify

Setup, verification, and local-run workflow

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'
make check

The package supports Python 3.11 or newer. make check runs Ruff, formatting, strict mypy, exhaustive and independent mathematical oracles, real Flask request tests, 100% combined line/branch coverage, the distribution attestation, and the committed-evidence integrity check.

Start the pinned production WSGI server on loopback:

waitress-serve \
  --host 127.0.0.1 \
  --port 5000 \
  --max-request-body-size=1024 \
  --max-request-header-size=16384 \
  --channel-timeout=30 \
  --call password_policy_lab.web:create_app

Then open http://127.0.0.1:5000.

Reproducible distribution contract

Measured distribution build, rebuild, and installed-smoke flow

make distribution-check does not trust the migrated worktree's file modes or an implicit setuptools file list. It snapshots exactly 15 stage-zero Git blobs, materializes two normalized source trees, builds both with the pinned setuptools==83.0.0, and validates exact archive inventories:

  • the wheel has 17 regular members with fixed ZIP metadata and a complete, canonical RECORD;
  • the sdist has 23 regular files and six directories, with fixed modes, epoch, owner fields, member order, and gzip header;
  • a wheel rebuilt from the safely materialized canonical sdist is byte-for-byte equal to the canonicalized wheel produced by each primary build;
  • an offline pip --target install from an external working directory imports package code and metadata from that target, finds both package resources, and runs deterministic inspect --length 20 --format json twice without sampling.

Real distribution attestation terminal output

Run the attested path with:

make distribution-check

The raw distribution-check.txt and canonical distribution-attestation.json record the input digest, complete member-level SHA-256 inventory, canonical archive hashes, rebuild equality, toolchain, smoke result, and negative claim boundaries. make build remains a conventional backend build for local inspection; its raw sdist contains environment-dependent metadata and is not presented as the canonical release artifact.

The attestation is deliberately unofficial. It does not claim a license, artifact signature, dependency integrity, cross-platform reproducibility, or safety for arbitrary archives. The install smoke uses the current pinned checker dependencies without resolving them; it is not a fresh, hash-locked dependency environment.

Architecture

Source-derived package architecture

The browser and CLI share the same versioned visible-ASCII policy, exact PasswordSpace, and inspection model. The web route constructs one space, derives the displayed metrics from that same object, and then performs one sample. Invalid requests stop before policy construction. The CLI inspection path is deterministic and consumes no entropy.

AST-verified uniform sampling flow

The sampling diagram is checked against the current syntax tree: the production path selects an integer with secrets.randbelow(total) and passes it directly to unrank. Project code has no modulo reduction, candidate-rejection loop, or use of the random module. (secrets.randbelow may perform its own internal random-bit rejection sampling.)

Exact state-space evidence

Exact policy state space across lengths 8 through 32

The figure is rendered from the committed state-space-sweep.csv, produced by the real CLI for lengths 8–32. It pairs exact entropy bounds with the policy-satisfying share on an honest 0–100% scale. Counts and entropy describe a uniform draw from this state space; they are not a password-strength score or an authentication guarantee.

Exact one-step policy-minimum sensitivity

Exact one-step impact of relaxing each class minimum

The sensitivity command keeps length, alphabet, class order, and every other minimum fixed, then lowers exactly one class minimum by at most one. For the canonical length-20 policy the baseline contains 2,585,908,648,140,078,948,280,078,326,668,093,030,400 valid strings. The independently recomputed one-step additions are:

Relaxed minimum Exact strings added Relaxed total
lowercase 1 → 0 4,282,773,611,815,523,203,733,351,229,397,401,600 2,590,191,421,751,894,471,483,811,677,897,490,432,000
uppercase 1 → 0 4,282,773,611,815,523,203,733,351,229,397,401,600 2,590,191,421,751,894,471,483,811,677,897,490,432,000
digits 1 → 0 305,512,318,837,986,411,479,186,500,202,608,459,776 2,891,420,966,978,065,359,759,264,826,870,701,490,176
punctuation 1 → 0 683,500,551,758,275,124,507,688,616,801,075,200 2,586,592,148,691,837,223,404,586,015,284,894,105,600

Real deterministic policy-sensitivity CLI output

password-policy-lab sensitivity --length 20 --format text
password-policy-lab sensitivity --length 20 --format json
password-policy-lab sensitivity --length 20 --format csv

The canonical JSON and real text transcript retain each relaxed policy fingerprint, exact total, exact addition, and reduced baseline-to-relaxed fraction. This is a local one-step marginal analysis: the rows must not be added together, it is not a password-strength estimate, and it never constructs a candidate or consumes entropy. The SVG renderer fails closed unless those arithmetic and claim-boundary invariants agree with the core.

Differential dynamic-programming work evidence

Exact logical work counters for six fixed policy scenarios

The profiler constructs the real PasswordSpace under six fixed public policies and retains only deterministic logical counters. A context-managed iterator wrapper counts every deficit vector yielded to the production loop; cProfile independently records exact _build_layers and _consume call counts. Separate polynomial-convolution and class-population oracles must agree with layer occupancy and the final arbitrary-precision state-space total.

The controlled comparison keeps length 24, the same four class widths, and the same total minimum 24: balanced minima (6, 6, 6, 6) materialize 31,213 cells and perform 124,848 transitions, while skewed minima (21, 1, 1, 1) materialize 2,288 cells and perform 9,148 transitions. This isolates the shape of the deficit state space instead of presenting a wall-clock benchmark.

Observed layer occupancy for five accepted policies and one explicit rejection boundary

The near-budget eight-class case is accepted at 164,025 / 216,513 cells and 1,312,192 / 1,732,104 transitions. Raising every minimum from 2 to 3 pushes the conservative bounds to 2,162,688 cells and 17,301,504 transitions; profiling observes zero product, layer-build, and consume calls because rejection happens before enumeration. The maximum-length one-class case remains accepted and reaches a 1,678-bit count. That bit length describes integer arithmetic magnitude, not allocated memory.

Real deterministic complexity profiler output

Run the concise profile with make complexity-profile. The complete canonical JSON receipt retains every accepted layer, while the real command transcript stays short enough to review. The evidence makes no elapsed-time, throughput, RSS, allocation, CPU, hardware, password-strength, or cross-machine performance claim. It consumes no entropy and constructs or emits no password candidate.

Deterministic audit CLI

The inspection commands never sample a password. They emit exact decimal integers, a reduced satisfying fraction, rank interval, integer entropy bounds, bounded-DP limits, and a SHA-256 fingerprint of the complete ordered policy. Arbitrary-size JSON values remain strings so JavaScript readers cannot silently round them.

Real deterministic CLI inspection

password-policy-lab inspect --length 20 --format json
password-policy-lab sweep \
  --start-length 8 \
  --end-length 32 \
  --format csv

The raw, machine-reviewable counterpart is cli-inspect.txt. Outputs contain no timestamps, machine paths, locale-dependent formatting, or randomness.

rank and unrank exist only for public deterministic test vectors. Both require --acknowledge-reversible-output; rank accepts its candidate only through bounded standard input or hidden terminal input, never an argument. Do not use either command with a credential.

Local web interface

Real desktop GET response from the local policy lab Real mobile-width GET response from the local policy lab
Desktop: exact metrics and uniform-sampler workflow. Mobile: the same semantic flow without horizontal overflow.

Real rejected request with audit metrics withheld

The form accepts lengths from 8 through 128. Every generated value satisfies the fixed lowercase, uppercase, digit, and ASCII-punctuation minima. The interface is entirely server-rendered: one package-local stylesheet, no JavaScript, CDN, web font, external request, cookie, session, or storage.

The documented Waitress command binds only to loopback; the Flask application trusts only loopback host names. With that command, Flask and Waitress both enforce the 1 KiB request-body boundary. Responses are non-cacheable and carry a restrictive CSP, origin isolation, frame denial, no-referrer, and MIME-sniffing protections. The current endpoint has no server-side mutation, session, or authenticated identity, so it has no CSRF token; adding any of those features requires revisiting that decision.

The server does not log or retain a generated value, but Python, the browser, and the clipboard cannot promise memory zeroization. Run the app as an unprivileged user, and do not change the bind address without transport security and a deliberate deployment boundary.

These choices follow the official Flask security guidance and deployment guidance.

Security reporting and repository controls

Report suspected vulnerabilities through the security policy, using synthetic data and the private channel linked there. The policy also records the loopback deployment boundary, unsupported uses, reversible-command warning, lack of memory-zeroization guarantees, and best-effort support scope.

Weekly update automation for the exact Python and GitHub Actions pins is declared in the Dependabot configuration. Direct dependencies are version-pinned, but the development install still resolves transitive distributions without a hash lock. The distribution attestation therefore continues to exclude dependency-integrity and signature claims.

Secret scanning, push protection, code scanning, vulnerability alerts, and default-branch rules are GitHub-hosted operational settings. They are not reproducible properties of a Git commit or claims covered by the evidence manifest; their current status must be verified in the repository Security and Rules settings.

Rebuild the evidence

Install the pinned Chromium build into the ignored repository-local directory, then regenerate every raw and visual artifact:

make evidence-browser
make evidence
make evidence-check

make evidence keeps browser binaries, profiles, Matplotlib state, caches, and temporary files under this repository. On a minimal Linux image, Chromium's OS runtime libraries still need to be supplied by that environment; the target never invokes a privileged system-package install.

The capture contract waits for fonts and settled layout, preserves the declared viewport during full-page screenshots, and pins Chromium to one raster thread. That removes subpixel shadow races without replacing the real server-rendered interface with a mockup; the exact launch argument is recorded in the manifest.

The byte-exact browser baseline is canonical only on the GitHub-hosted Ubuntu 24.04 job and the pinned Python, Playwright, and Chromium stack in the verify workflow. System fonts and renderer packages are not vendored, so another Linux image can satisfy every semantic assertion while producing different raster bytes. CI treats such drift as a review event and preserves only an allowlisted capture that already passed evidence validation.

Real project quality gate

Evidence provenance stays next to the visuals:

  • manifest.json — source and artifact SHA-256 digests, dimensions, tool versions, HTTP statuses, DOM assertions, and the zero-sampler-call guard result;
  • web-validation-reference.png — the three lossless, full-resolution browser frames used to independently verify every decoded GIF frame;
  • quality-gate.txt — normalized output from the real Ruff, format, mypy, pytest, coverage, and dependency checks;
  • state-space-sweep.csv — exact plot input from the public CLI;
  • cli-inspect.txt — exact deterministic inspection output.

No timestamp or absolute machine path is accepted into the manifest or raw evidence. make evidence-check recomputes hashes and semantic invariants so a code change cannot silently leave stale portfolio media behind.

About

Exact password-policy state-space counting and uniform sampling with a reproducible Flask and CLI lab.

Topics

Resources

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages