GitHub - P1rate5ec/key-kettle: Password-policy auditor scoring 11 NIST/OWASP/CISA-aligned controls with cited remediation. · GitHub
Skip to content

About

Password-policy auditor scoring 11 NIST/OWASP/CISA-aligned controls with cited remediation.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

6 Commits

Folders and files

Repository files navigation

🫖 Key Kettle

Audit password-policy requirements against modern NIST / OWASP / CISA guidance.

python license tests interface transmits

"One uppercase, one number, one symbol" — the rule that produced P@ssw0rd1 a billion times.


Key Kettle takes a password policy expressed as JSON and returns a scored, graded, prioritized report: what's wrong, how to fix it, why it matters, and which standard says so. It's the tool you run before a client meeting so you can hand over "here are your 6 failing controls, worst first" instead of a vague "your password rules feel old."

It is a defensive, offline advisory tool. It reads a policy description — it never touches a directory, an authentication system, or a real password. The only network surface is an optional local report UI, and that is gated (see Safety & authorization).


Why it exists

Most password policies in the wild still carry controls that current guidance actively discourages: short maximum lengths, forced periodic rotation, rigid composition rules, blocked paste / password managers, and fast general-purpose hashes for storage. Key Kettle turns those requirements into a standards-aligned improvement plan a compliance consultant, MSP, or internal security team can act on.

Every finding cites its source:

This is advisory tooling, not legal advice. A regulator or contract may demand controls stricter than baseline guidance; treat the report as a starting point.


How it works

policy JSON ──▶ PasswordPolicy.from_dict ──▶ analyze_policy ──▶ PolicyReport
              (validate + coerce +          (one check per       (score, grade,
               fill weak defaults)           control)             summary, recs)
                     │                                                │
                     ├─ policy_key_warnings ──▶ stderr notes          ├─ rich table/panel  (humans)
                     │                                                └─ pure JSON          (machines)

1. Parse & validate (policy.py). The input is treated as hostile. Fields are coerced to their real types (booleans accept true/"yes"/1, rotation accepts 0/""/null as "disabled"), ranges are checked (min_length within 1..4096, max_length ≥ min_length, no negative or absurd values), and bad input raises a PolicyError with the offending field named. Missing fields fall back to the weak sample on purpose — an audit tool should assume the worst about controls it wasn't told about, not invent a flattering default. Unknown/typo'd and omitted fields are surfaced as non-fatal notes on stderr.

2. Analyze (analyzer.py). Eleven controls are evaluated, each producing exactly one recommendation — a pass or an issue — so the output is a complete checklist, not just a pile of complaints:

Control Passes when Weight if failing
Minimum length ≥ 15 (or ≥ 8 when MFA is required) high
Maximum length ≥ 64 medium
Composition rules none required medium (advisory)
Periodic rotation none medium/low (advisory)
Compromised-password screening enabled high
Multi-factor authentication required high
Password managers & paste both allowed medium
Rate limiting 1–10 failed attempts high
Password storage salted hashing critical
Password hashing algorithm Argon2id / bcrypt / scrypt / PBKDF2 high (advisory)
Character allowance spaces + Unicode low (advisory)

3. Score & grade. Start at 100, subtract each non-passing control's severity weight (critical 28, high 18, medium 10, low 4), floor at 0. Grades: A ≥ 90, B ≥ 80, C ≥ 70, D ≥ 60, else F. Recommendations are sorted worst-first in both the table and the JSON.

4. Render. Humans get a rich summary panel + colour-coded table (severity is critical/high → red, medium → yellow, low → cyan, pass → green). Machines get pure JSON. The two never mix.

Data model

PasswordPolicy (frozen dataclass, no database) captures: min_length, max_length, the four require_* composition flags, rotation_days, breach_screening, mfa_required, paste_allowed, password_manager_allowed, lockout_threshold, stores_hashed, hashing_algorithm, allows_spaces, allows_unicode. Run key-kettle sample-policy to see the full shape.


Install

cd key-kettle
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -r requirements.txt
pip install -e .                 # provides the `key-kettle` command
cp .env.example .env             # optional: sets default serve host/port

No editable install? Every command also works as PYTHONPATH=src python -m key_kettle.cli ....


CLI usage

Global: --plain / --no-color disables colour, emoji, and box-drawing (also auto-honoured when NO_COLOR is set or output isn't a TTY). --version prints the version. JSON output is always pure regardless of these.

sample-policy — print a policy to edit

key-kettle sample-policy            # a deliberately dated weak baseline
key-kettle sample-policy --strong   # a modern, NIST-aligned baseline
key-kettle sample-policy > policy.json

Prints policy JSON to stdout (the branded header, if any, goes to stderr, so the JSON stays pipeable).

analyze — score a policy

key-kettle analyze --policy policy.json           # human report
key-kettle analyze --policy - < policy.json       # read from stdin
key-kettle sample-policy | key-kettle analyze --policy -   # pipe straight in
key-kettle analyze --policy policy.json --json | jq        # machine-readable
key-kettle analyze --policy policy.json --fail-under 80     # stricter CI gate
Flag Meaning
--policy PATH Policy JSON file, or - for stdin (required). Files are capped at 1 MiB.
--json Emit pure JSON to stdout (no colour, no header).
--fail-under SCORE Exit non-zero if score < SCORE (default 70).

Exit codes: 0 = score meets the threshold; 1 = below threshold or an input error (missing file, bad JSON, invalid field). Handy as a CI guard.

Sample human output (strong policy):

┌───────────────────────── Audit result ─────────────────────────┐
│   100/100   grade A                                             │
│   0 failing controls, 0 warnings.                               │
│   0 failing · 0 advisory · 11 passing · 11 controls reviewed    │
└─────────────────────────────────────────────────────────────────┘
  Sev      Control                          Finding            Recommended fix
 ─────────────────────────────────────────────────────────────────────────────
  ✓ pass   Multi-factor authentication      MFA is required.   Keep MFA required…
  ✓ pass   Rate limiting                    Failed attempts…   Keep tight rate…
  …

And for a weak policy, the same view leads with the worst findings:

  X high   Compromised-password screening   No blocklist…      Compare new passwords…
  X high   Minimum length                   Minimum length 8…  Set minimum length to 15…
  X high   Multi-factor authentication      MFA is not req…    Require MFA…

serve — local report UI

key-kettle serve                                # http://127.0.0.1:5065
key-kettle serve --host 127.0.0.1 --port 5065

Opens a Flask page where you paste policy JSON and get the rendered report. Defaults to loopback. Binding anywhere else requires an explicit acknowledgement (see below).


Safety & authorization

Key Kettle is for auditing policies you own or are authorized to assess. It performs no scanning and sends no email. The one surface worth care is serve:

  • Binds to 127.0.0.1 by default. The report UI has no authentication and evaluates policy JSON pasted by whoever can reach it.
  • Non-loopback binds are gated. serve --host 0.0.0.0 (or any LAN IP / hostname) is refused unless you pass --allow-nonlocal, an explicit "I own this network and accept the exposure" acknowledgement. Refusal exit code is 2.
  • --debug on a non-loopback host is always refused — the Werkzeug interactive debugger allows remote code execution.
  • Input is treated as hostile. File reads are capped (1 MiB CLI / 256 KiB HTTP body), non-object and out-of-range JSON is rejected with a tidy message, and error responses never leak a traceback. Output is Jinja-autoescaped and served with X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and a restrictive Content-Security-Policy.
  • No secrets are read or logged. .env only holds a default host/port.

For any real deployment beyond a laptop, put a real WSGI server and authentication in front of it.


Development & tests

cd key-kettle
PYTHONPATH=src python -m pytest -q

Tests are fast and offline (no network, no sleeps): the Flask endpoints are exercised via the in-process test client. Coverage spans the analyzer (scoring, grade boundaries, full-checklist invariants), the policy validator (range and type rejection, weak-default fill, key warnings), the CLI (exit codes, JSON purity, file/stdin handling, size caps, the loopback gate), and the app (400-not-500 on bad input, 413 on oversize, security headers).

Layout

key-kettle/
  src/key_kettle/
    policy.py       # PasswordPolicy dataclass, validation, sample baselines
    analyzer.py     # 11 controls → scored PolicyReport
    ui.py           # shared rich theme, banner, report renderer
    cli.py          # sample-policy / analyze / serve
    app.py          # Flask JSON input + report UI (hardened)
    templates/
  tests/

🏷️ Why "Key Kettle"?

Put the policy on to boil and see what survives. Most password rules are folklore passed between compliance documents, and they optimise for looking strict rather than being strong. NIST rewrote its guidance in 2017; a startling number of policies never got the memo. This audits yours against 11 controls drawn from NIST, OWASP and CISA — and cites the source for every complaint, so the fix argument is already won before the meeting starts.


🔬 How this tool was built

One of ten defensive tools rebuilt to a single standard — understand, reimagine, build the CLI, harden, document, verify. The goal was never to change what these tools are, but to take each one as far as it could honestly go on correctness, safety and the experience of the person actually running it at 2am.

Purpose. A password-policy auditor that reads as a complete, triage-ordered checklist rather than a pile of complaints. Every one of 11 NIST/OWASP/CISA-aligned controls reports pass or fail with a cited fix.

How it works. It ingests a policy (JSON file, stdin, or a generated sample-policy), validates and coerces the fields, evaluates all 11 controls, scores and grades the result, and sorts recommendations worst-first. The same data drives the CLI table, the JSON, and the local report UI.

Standout CLI. A color-coded, triage-ordered table with a score/grade summary panel; the banner goes to stderr for machine commands so stdout stays pipeable; --policy - reads from stdin so sample-policy | analyze --policy - works; and --fail-under SCORE turns analyze into a tunable CI gate.

Key improvements. Functional: the audit now reports every control as pass or fail (11 total) instead of only listing problems, so you see complete posture; strict PolicyError validation on ranges and types; real-world type coercion (boolean strings, 0/""/null as disabled rotation); broadened GOOD_HASHES to include argon2i/argon2d. Security: serve refuses non-loopback binds without --allow-nonlocal (the UI has no auth and evaluates pasted JSON) and refuses --debug off loopback; MAX_CONTENT_LENGTH 256 KiB plus an in-handler re-check; CLI caps reads at 1 MiB; strict CSP and the standard header set.

Example.

PYTHONPATH=src python -m key_kettle.cli sample-policy --strong \
  | PYTHONPATH=src python -m key_kettle.cli analyze --policy -


⚖️ Authorized use & safety charter

These are defensive tools for systems, files, networks, and people you own or are explicitly authorized to assess. Read this before you run anything:

  • Authorization is not optional. Phishing simulations, network scans, IP reputation lookups, and web-app probing all touch other people's systems or data. Get written authorization first. Several tools refuse to act until you assert it (--yes, --authorized-training, --i-have-authorization, --i-am-authorized, and friends).
  • Safe by default. Every web UI binds to 127.0.0.1 (loopback). Egress, live sends, and active scans are gated behind explicit flags — dry-run, passive, or refuse-and-warn is always the default.
  • No accidental foot-guns. Flask --debug (the Werkzeug interactive debugger is remote code execution on any traceback) is warned about loudly and refused entirely off loopback. Untrusted input is size-capped, validated, and rendered inertly so a hostile file or log line can't crash — or take over — your terminal.
  • No secrets in output. API keys stay in request headers, passwords come from the environment, and nothing sensitive is logged or printed.

These are not offensive tooling. They contain no exploits, no credential harvesters, and no payloads. If a tool could be misused, it is built to resist that misuse by construction.


🧪 Development

python -m venv .venv && source .venv/bin/activate
pip install -e .
pytest -q                 # 49 tests, no network, no sleeps
key-kettle --help

📄 License

Released under the MIT License. See LICENSE.

Defensive tooling. No exploits, no payloads, no credential harvesters.

About

Password-policy auditor scoring 11 NIST/OWASP/CISA-aligned controls with cited remediation.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages