Skip to content
Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

IsMyPwdSafe

zxcvbn, but readable.

A privacy-first password audit for developers and CI/CD, with a CLI and an importable Python API.
zxcvbn-first scoring by default: zxcvbn-ts with Node.js 18+, zxcvbn-python otherwise, and a clean exit 4 if neither is installed. HIBP is on by default, exit codes are semantic, JSON is strict, and the GitHub Action fails closed. imps scan audits repository .env files without ever displaying secret values.

PyPI version Install with pipx

Python 3.8+ Node.js 18+ (ts only) License: MIT CI

IsMyPwdSafe terminal demo: a pwned password gets a zero score and a breach warning, then a strong password gets a clean report.

The demo uses the real CLI and a public throwaway password; the first scene makes a live HIBP request, so its result and exit code are real. The second scene runs offline.

Node.js 18+ is required only for --engine=ts. With --engine=auto (the default), imps uses the bundled zxcvbn-python engine when Node is unavailable. See Scoring engines.

Version 2.1.1. 2.1.0 added imps scan for recurring CI use, a separate scan JSON contract, git-aware .env discovery, honest token labeling, private-key detection, and actionable scan exit codes. 2.1.1 is a GitHub Action Marketplace metadata patch; the single password contract, scores, privacy, and network behavior are unchanged. Pre-commit integration follows in 2.1.2.

A small wrapper around excellent work — see A note on humility for what this tool cannot do.

Read this first: passing the password as an argument (imps "my-password") exposes it to shell history, terminal scrollback, and process listings. Run imps without arguments to use the hidden prompt, or pipe from stdin with imps -. This is an audit tool, not a password manager.


Contents


Quick start

Install

pipx install ismypwdsafe

pip works too:

pip install ismypwdsafe

The default install includes the zxcvbn-python fallback, so a clean install has a usable scoring engine without Node.js. Node.js 18+ is still needed for --engine=ts.

Run

imps                         # hidden prompt
imps --offline               # hidden prompt, no network calls
imps - < password.txt        # password from stdin
imps --json - < password.txt # machine-readable output

The default engine is auto:

  1. zxcvbn-ts 4.x, if Node.js 18+ is on PATH;
  2. otherwise the bundled zxcvbn-python engine;
  3. if neither is usable, exit 4 with an explanation.

native is never selected silently. It is the explicit legacy 1.x heuristic; see Legacy native engine.

imps --engine=ts "correct-horse-battery-staple"
imps --engine=python "correct-horse-battery-staple"   # bundled fallback

Node.js

Node.js is not bundled or downloaded. Install it with your platform package manager:

# macOS
brew install node

# Debian / Ubuntu
sudo apt install nodejs npm

# Windows
winget install OpenJS.NodeJS.LTS

Verify with node --version. Node is needed only for --engine=ts and for the ts branch of auto. If Node is unavailable, auto uses the bundled zxcvbn-python engine rather than pretending the native heuristic is zxcvbn.


Scan a repository

imps scan is the recurring CI use case: it finds password-like values in dotenv files and tells you what to fix without printing the secrets. Pre-commit integration using staged files is planned for 2.1.2.

imps scan                              # tracked .env files in this repository
imps scan .env .env.local              # explicit files always work
imps scan --include-untracked          # opt into local/ignored .env files
imps scan --no-git                     # non-git checkout
imps scan --json                       # values-free scan JSON
imps scan --offline                    # no HIBP or other network calls
imps scan --min-score 70               # stricter score threshold
imps scan --engine=ts --include-untracked

Discovery is git-aware by default: tracked files matching .env, .env.*, *.env, or *.env.* are scanned. Explicit paths override discovery, so an ignored .env.local is scanned when you name it. --include-untracked adds untracked and ignored files. Outside a git work tree, the command exits 2 and asks for --no-git or an explicit path.

The scanner:

  • never prints, logs, or persists a value, a value fragment, or a password in an error message;
  • scores PASSWORD, PASSWD, PASSPHRASE, PWD, and PASS keys with the selected zxcvbn engine;
  • labels SECRET, TOKEN, API_KEY, ACCESS_KEY, PRIVATE_KEY, CLIENT_SECRET, and AUTH_TOKEN values as TOKEN with no numeric score and no HIBP lookup — HIBP is a password corpus, not an API-key leak database;
  • treats a private-key PEM block as CRITICAL, exits 6, and never sends it to HIBP;
  • skips hashes, salts, file paths, URLs, usernames, lengths, reset tokens, and other metadata suffixes;
  • ignores empty values, ${VAR} references, and obvious placeholders;
  • reports path:line, key name, score, rating, pattern labels, token format, and breach status.

HIBP is on by default for password-class findings. Duplicate values share one request; concurrency is four; each request has a five-second timeout; the whole scan has a 60-second deadline. On a timeout, completed findings are retained, the result is marked incomplete, and the exit code is 5 unless a more severe finding exists. --offline and --no-breach-check never produce exit 5.

The scan has its own JSON contract, schema/scan.schema.json, with schema_version: "1.0" and kind: "scan". --json-full is intentionally unsupported by scan and exits 2: scan never emits values.

{
  "schema_version": "1.0",
  "kind": "scan",
  "tool_version": "2.1.1",
  "exit_code": 3,
  "summary": {
    "files": 1,
    "values": 1,
    "weak": 1,
    "medium": 0,
    "strong": 0,
    "pwned": 1,
    "unknown": 0,
    "critical": 0,
    "unparseable_lines": 0,
    "empty_values": 0,
    "references": 0,
    "placeholders": 0,
    "skipped_large": 0
  },
  "findings": [
    {
      "path": ".env",
      "line": 4,
      "key": "DB_PASSWORD",
      "kind": "password",
      "score": 0,
      "rating": "weak",
      "patterns": ["dictionary"],
      "breach": {
        "checked": true,
        "pwned": true,
        "breach_count": 12345,
        "reason": null
      },
      "token_format": null,
      "message": null
    }
  ],
  "warnings": [],
  "incomplete": false,
  "unchecked_values": 0,
  "engine_error": null
}

Full details, including parsing and the exit precedence, are in docs/scan-spec.md.


A note on humility

IsMyPwdSafe is a small wrapper around other people's excellent work. The hard parts - zxcvbn, the language dictionaries, and the HaveIBeenPwned range API - are not ours. What we try to add is a readable report, a small privacy budget, and predictable failure modes.

It can help you notice that a password is a dictionary word, a keyboard walk, a date, a repeat, or a known breach. It cannot prove that a password is safe. A clean result is not a certificate, an audit opinion, or a guarantee. It means "this particular heuristic did not find a problem in this particular run".

We would rather say UNKNOWN and exit 5 than tell you a password is safe when the breach check did not run. That is the main design principle behind 2.0, and it is also why the tool is occasionally annoying in CI.


What it reports

The default zxcvbn path renders:

  • a readable 0-100 score; zxcvbn's raw 0-4 score is kept in JSON;
  • estimated time to crack under four attacker models;
  • pattern matches that explain the score: dictionary matches, keyboard walks, repeats, dates, sequences, regexes, and brute force;
  • zxcvbn feedback (warning + suggestions) under WHAT TO DO;
  • HIBP breach status.

The report uses the full terminal width. On wide terminals, time-to-crack and pattern matches sit side by side; on narrow terminals they stack. Empty zxcvbn sections are omitted when the legacy native engine is used. On an interactive TTY the banner and sections fade in, section titles briefly resolve from scrambled characters, and the summary score bar fills with an eased motion; non-TTY output and NO_COLOR are printed immediately.

The legacy native engine is explicitly limited; see Legacy native engine.

Score mapping

zxcvbn Percentage Rating
0 0 weak
1 25 weak
2 50 medium
3 75 strong
4 100 strong

If HIBP says the password is pwned, the final score is forced to 0 and the result uses exit code 3. The reason is exposed as score.capped_by = "hibp". The zxcvbn sequence, guesses, and crack-time estimates are still reported, and score.raw_score_0_4 keeps the original zxcvbn score; only the final score is capped.


Scoring engines

"Available" is not the same as "used". The JSON contains both values:

  • engine_requested is what you asked for (auto, ts, python, or native);
  • engine_used is the engine that actually produced the result;
  • engine_version is the version reported by that engine;
  • engine_fallback is true only when auto fell back to zxcvbn-python because zxcvbn-ts was unavailable.

For example, without Node.js, --engine=auto reports engine_requested: "auto", engine_used: "python", and engine_fallback: true. If auto cannot find any zxcvbn engine, the command exits 4 and engine_used is null. It does not quietly run the native heuristic. Consumers should branch on engine_used, not on engine_requested.

Engine engine_version
ts zxcvbn-ts version reported by the bundle, for example "4.2.0"
python zxcvbn-python package version, for example "4.5.0"
native null
error / no usable engine null

Legacy native engine

--engine=native keeps the 1.x ASCII-oriented entropy and pattern model for compatibility. It is explicit only and is not recommended for new audits. It does not produce zxcvbn sequence, feedback, or crack-time data:

JSON field Value with --engine=native
engine_version null
score.raw_score_0_4 null
sequence []
feedback {"warning": null, "suggestions": []}
guesses / guesses_log10 null
crack_times_seconds / crack_times_display {}
calc_time null

The score.value and score.rating still come from the legacy heuristic.


HaveIBeenPwned is ON by default

By default imps makes exactly one outbound request: HIBP's Pwned Passwords range API.

  • The password and the full SHA-1 hash never leave the machine.
  • Only the first five characters of the SHA-1 hash are sent.
  • Responses use Add-Padding: true.
  • The local suffix match happens in memory.
  • Timeout: 5 seconds.

Skip it explicitly:

imps --no-breach-check "my-password"   # skip HIBP
imps --offline "my-password"           # skip HIBP and the optional update check

When skipped, breach.checked is false, breach.pwned is null, and breach.reason is "disabled" (or "offline" for --offline). The exit code is based only on the score (0 or 1), never 5.

If HIBP cannot be reached, imps does not call the password safe:

  • the report says UNKNOWN;
  • the JSON has breach.checked = true, breach.pwned = null, breach.reason = "api_error";
  • the exit code is 5.

The zxcvbn score is still shown, but CI must treat exit 5 as a failure.

The GitHub update check is opt-in and separate: --check-updates.

Command HIBP GitHub update check
imps "pwd" yes no
imps --no-breach-check "pwd" no no
imps --check-updates "pwd" yes yes
imps --offline "pwd" no no

CLI reference

imps [--engine {auto,ts,python,native}] [-M] [-o | --no-breach-check]
     [-s | -dw] [--check-updates] [--json | --json-full | -q] [password|-]

imps scan [--engine {auto,ts,python,native}] [--min-score N]
          [--offline | --no-breach-check] [--include GLOB]...
          [--include-untracked | --no-git] [--json | -q] [path ...]
Flag Description
--engine auto (default), ts, python, or native.
-M, --Mask Mask the password and matched tokens in the terminal UI. JSON is unaffected.
-s, --short Show the score, breach verdict, and engine line.
-dw, --darkweb Show only the HIBP breach check.
-o, --offline Skip HIBP and all other network calls.
--no-breach-check Skip only HIBP.
--check-updates Enable the opt-in GitHub version check.
--json Strict JSON output; matched tokens are redacted.
--json-full JSON including matched tokens. Use with care.
-q, --quiet Suppress all output; return only the exit code.
password Password to audit. Omit for hidden prompt; use - for stdin.

Flag notes:

  • -s and -dw are mutually exclusive.
  • -s changed in 2.0: it now includes the breach verdict and engine line, not only the score. See the CHANGELOG.
  • -M affects the terminal UI only. It does not change --json or --json-full: --json redacts matched tokens; --json-full includes them. Use --json-full only when you explicitly need them.
  • -dw is named for historical reasons. It shows the HIBP breach check, not a dark-web scan.
  • -q suppresses output but does not change input handling. If stdin is a TTY, the hidden prompt still appears. For non-interactive use, pipe the password (echo 'pwd' | imps -q -) or read it from a file (imps -q - < password.txt). Passing it as an argument is possible but exposes it to shell history; see the warning at the top.
  • In a non-interactive shell with no password and no stdin, imps exits 2 instead of hanging.
  • imps scan is a subcommand. To audit the literal password scan, use imps -- scan. --json-full is not supported by scan and exits 2.

Exit codes

Code Meaning
0 OK: score >= 50, not pwned, breach status known or disabled
1 Weak: score < 50
2 Usage/input error, including an empty password
3 Pwned: found in HIBP
4 Engine error: no usable engine, Node too old, engine failure
5 HIBP status unknown: do not treat the password as safe
6 imps scan only: a private-key block was found
130 Interrupted with Ctrl+C

imps scan uses the same codes with a different precedence: 6 (critical) > 3 (pwned) > 5 (HIBP unknown or incomplete) > 4 (engine error) > 1 (below --min-score) > 0 (clean). A usage error is 2.

For CI, use -q when you only need the code, or --json when you need the data:

imps -q --engine=ts - < password.txt
echo $?

JSON contract v2.0

--json emits a strict, versioned document validated against schema/result.schema.json. Non-finite floats are converted to null; the output never contains Infinity or NaN.

By default, sequence[].token, matched_word, base_token, sub_display, regex_match, and subs are replaced with "<redacted>". Use --json-full only when you explicitly need the matched tokens.

redacted is true for --json and false for --json-full. Native-engine JSON follows the same flag even though it has no tokens to reveal.

The default JSON has no top-level password field, and the password is not included in input, breach, feedback, or the redacted sequence. The test suite checks this with a unique marker password against the selected engine; see tests/test_privacy.py. --json-full may reveal password fragments through sequence[].token, so treat it as sensitive output.

Example (abridged):

{
  "schema_version": "2.0",
  "tool_version": "2.1.1",
  "exit_code": 0,
  "error": null,
  "input": {
    "length": 28,
    "charset_size": 59,
    "has_lower": true,
    "has_upper": false,
    "has_number": false,
    "has_symbol": true,
    "has_non_ascii": false
  },
  "score": {
    "value": 100,
    "rating": "strong",
    "raw_score_0_4": 4,
    "capped_by": null
  },
  "guesses": 100000000001.0,
  "guesses_log10": 11.0,
  "crack_times_seconds": {
    "online_throttling_100_per_hour": 3600000000036.0,
    "online_no_throttling_10_per_second": 10000000000.1,
    "offline_slow_hashing_1e4_per_second": 10000000.0001,
    "offline_fast_hashing_1e10_per_second": 10.0000000001
  },
  "crack_times_display": {
    "online_throttling_100_per_hour": "centuries",
    "online_no_throttling_10_per_second": "centuries",
    "offline_slow_hashing_1e4_per_second": "4 months",
    "offline_fast_hashing_1e10_per_second": "10 seconds"
  },
  "feedback": {
    "warning": null,
    "suggestions": []
  },
  "sequence": [
    {
      "pattern": "bruteforce",
      "token": "<redacted>",
      "i": 0,
      "j": 27,
      "guesses": 100000000000
    }
  ],
  "breach": {
    "checked": true,
    "pwned": false,
    "breach_count": 0,
    "reason": null
  },
  "engine_requested": "auto",
  "engine_used": "ts",
  "engine_version": "4.2.0",
  "engine_fallback": false,
  "calc_time": 11.0,
  "redacted": true
}

calc_time is in milliseconds. zxcvbn-ts and zxcvbn-python report it; the legacy native engine returns null. The field name is unchanged in the 2.0 schema. A calc_time_ms alias is deferred to a future minor release rather than changing a published schema in a patch.

JSON in error cases

When the exit code is non-zero, --json still emits a valid schema document unless --quiet is also used. The envelope is stable:

Exit JSON emitted error input score breach engine_used
0/1/3 yes null object object object real engine
2 yes string null null null null
4 yes string null null null null
5 yes null object object, with no HIBP cap pwned: null, reason: "api_error" real engine
130 not guaranteed — — — — —

For exit 5, score is computed and present, but it is not a safety verdict: breach.pwned is null and the command fails closed. For exit 2 and 4, score is null; a consumer that does imps --json | jq .score.value must handle that case.

For exit 130 (Ctrl+C), JSON may be partially emitted or absent. Do not rely on it; check the exit code instead.

If --quiet and --json are combined, --quiet wins and no JSON is written. --json-full does not change the error envelope.

Native engine JSON

--engine=native is valid JSON with the same top-level shape, but the zxcvbn-specific fields are empty rather than absent:

{
  "engine_requested": "native",
  "engine_used": "native",
  "engine_version": null,
  "score": {
    "value": 100,
    "rating": "strong",
    "raw_score_0_4": null,
    "capped_by": null
  },
  "guesses": null,
  "crack_times_seconds": {},
  "crack_times_display": {},
  "feedback": {
    "warning": null,
    "suggestions": []
  },
  "sequence": [],
  "calc_time": null
}

Python API

The same analysis is available from Python; the CLI is one front end. The supported surface is analyze_password, AuditResult, result_to_dict, render_json, SCHEMA_VERSION, and __version__.

See Python API for examples, error handling, engine requirements, and JSON output. For other languages, use imps --json; this is a Python API, not a cross-language SDK.


GitHub Action

name: Password audit
on: [push, pull_request]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Audit password
        id: audit
        uses: Roma476/IsMyPwdSafe@v2
        with:
          mode: audit
          password: ${{ secrets.PASSWORD_TO_AUDIT }}
          engine: auto          # auto | ts | python | native
          node-version: "20"
          min-score: 70
          offline: false        # true skips HIBP

      - name: Print result
        run: echo "Score ${{ steps.audit.outputs.score }} (${{ steps.audit.outputs.rating }})"

The Action passes the password through stdin, not as a process argument. It fails closed on weak, pwned, engine-error, and HIBP-unknown results.

Scan mode

Use scan mode in a repository checkout to audit .env files without passing a secret to the Action:

- uses: actions/checkout@v4

- name: Scan repository .env files
  uses: Roma476/IsMyPwdSafe@v2
  with:
    mode: scan
    engine: auto
    min-score: 70
    offline: false            # HIBP on by default
    annotation-limit: 20
    scan-json-artifact: ismypwdsafe-scan

Scan mode emits file:line annotations for critical private keys, pwned passwords, unknown HIBP status, and weak scores. It writes a values-free JSON artifact for the full report. Private-key findings annotate as errors and exit 6; the artifact is uploaded even when the scan fails.

For exit 2 and 4, score is null and the Action's score output is empty; use exit-code as the reliable signal. The Action already exits before applying min-score in those cases.

Input Default Description
mode audit audit for one password or scan for repository .env files.
password — Required in audit mode. Always pass through GitHub Secrets.
engine auto auto, ts, python, or native.
node-version 20 Node version for auto/ts.
min-score 70 Fail below this score.
offline false Skip HIBP and all network calls.
scan-paths — Optional space-separated files/directories; empty uses git discovery.
scan-include — Optional comma-separated extra glob(s).
include-untracked false Scan mode: include untracked/ignored files.
no-git false Scan mode: walk the current directory instead of git discovery.
annotation-limit 10 Maximum scan annotations; the JSON artifact has the full report.
scan-json-artifact ismypwdsafe-scan Artifact name; set empty to skip upload.
Output Description
score 0-100 audit score. Empty in scan mode.
rating weak, medium, or strong. Empty in scan mode.
exit-code The imps exit code for the selected mode.
scan-findings Scan mode: number of findings.
scan-critical Scan mode: number of critical private-key findings.
scan-pwned Scan mode: number of pwned password findings.
scan-incomplete Scan mode: true when HIBP timed out.

Privacy

  • No telemetry, analytics, crash reporting, accounts, or local audit history.
  • No logs, cache, or config written on first run.
  • One request by default: HIBP k-anonymity. The GitHub update check is opt-in.
  • --offline disables all outbound requests.
  • JSON is token-redacted by default.

What is sent to HIBP

sha1    = SHA1(password)        # local only
prefix  = sha1[:5].upper()      # transmitted
suffix  = sha1[5:].upper()      # matched locally

Not transmitted: plaintext password, full SHA-1, password length, character composition, account, or identifier. HIBP receives the prefix and the HTTP metadata inherent to any request.


The five 2.0 decisions that differ from the original plan

While building 2.0 we changed five decisions. They are listed here because they are part of the contract, not hidden implementation details.

# Decision Why Cost
1 auto is the default engine, not ts and not native. A normal install should use the best available zxcvbn engine without silently changing the meaning of the result. If neither zxcvbn-ts nor zxcvbn-python is available, the command fails with exit 4.
2 HIBP is on by default, and an unreachable HIBP is UNKNOWN with exit 5. A missing breach check must not be confused with a clean breach check. One anonymous range request per audit; --offline is required for a strictly local run.
3 zxcvbn's 0-4 score maps to 0 / 25 / 50 / 75 / 100. The percentage is easier to read than a bare 0-4, and the weak/medium/strong boundary is explicit. The number is a presentation choice, not extra evidence; it does not make zxcvbn more precise.
4 A pwned password is forced to score 0, rating weak, with score.capped_by = "hibp". "Strong but already breached" must not look safe in a dashboard or a CI summary. The zxcvbn details are still reported, but the final score is deliberately dominated by the breach.
5 --json redacts matched tokens by default; --json-full is the explicit opt-in. JSON often ends up in logs, artifacts, and issue comments. The default should not leak password fragments. Users who need the matched tokens must ask for them and handle the output carefully.

The exact exit-code table and score mapping are documented above. If you build a CI policy on this tool, use those tables rather than assumptions from 1.x.


What a clean result does not mean

This is the most important section in the README.

A password that scores 100, is not found in HIBP, and has a long crack-time estimate is not proven safe. It has passed one heuristic and one partial breach corpus at one point in time. That is all.

In particular:

  • zxcvbn is a heuristic. It is very good at recognising common patterns and bad at knowing your personal context. A password can be strong by its model and still be reused across five services.
  • A clean HIBP result is not a guarantee. HIBP is a large corpus, not every breach. A password can be absent from HIBP and still be compromised or reused.
  • HIBP availability matters. If HIBP is unreachable, the result is UNKNOWN, breach.pwned is null, and the exit code is 5. The zxcvbn score is still shown, but it is not a safety verdict.
  • Crack times are scenarios, not predictions. They assume fixed hash rates and do not model rainbow tables, targeted dictionaries, GPU optimisations, side channels, or the attacker who already knows something about you.
  • The native engine is legacy. --engine=native uses the 1.x ASCII-oriented entropy model and a finite pattern list. It is retained for compatibility, not recommended for new use.
  • Node is external. --engine=auto/ts needs Node.js 18+. The default install includes zxcvbn-python; if that fallback is unavailable or broken, imps exits 4.
  • Bundled languages are English. The shipped zxcvbn-ts bundle includes the language-common and language-en dictionaries. It does not claim 20+ bundled languages.
  • This is not a password manager, a policy engine, or a compliance tool. It audits one password at a time, or .env files in scan mode; it does not store, generate, rotate, or centrally enforce credentials. It also cannot tell whether a password is reused, shared, written on a sticky note, or sent over an insecure channel.

If you need a guarantee, you need a password manager, a reviewed policy, multi-factor authentication, and an incident process. This tool can be one small input to that work, not a replacement for it.


Compatibility and verification

Area What is covered
Python runtime >=3.8 in package metadata; CI covers 3.8, 3.10, and 3.12
Recommended engine Node.js 18+ and zxcvbn-ts 4.2.0
Fallback engine Bundled zxcvbn-python 4.4+
Operating systems CI coverage on Ubuntu, macOS, and Windows
JSON contract validated against schema/result.schema.json and schema/scan.schema.json
zxcvbn-ts bundle rebuilt and checked for reproducibility in CI on Node 18 and 20
HIBP failure path unit tests plus a simulated missing-fetch run; exit 5
Action smoke-tested with strong, weak, below-threshold passwords, and scan mode

"CI covers" means the workflow is configured to run those jobs; it is not a claim that every combination has been manually exercised on every machine. The 2.0.0 release was exercised as a release candidate on TestPyPI before the stable tag; patch releases follow the same tag-based publish workflow.


Development

git clone https://github.com/Roma476/IsMyPwdSafe.git
cd IsMyPwdSafe
python -m pip install -e ".[test,dev]"
pytest

Node 18+ is required for the golden suite that covers the ts engine. Without Node, those golden tests are skipped; the native and python paths still run.

Rebuild the zxcvbn-ts bundle after changing ismypwdsafe/js/score.mjs or the JS dependencies:

cd ismypwdsafe/js
npm ci
npm run build

Regenerate golden fixtures:

python scripts/generate_golden.py --engine ts

Regenerate the terminal demo GIF after changing the CLI output:

vhs docs/assets/demo.tape

Project layout:

ismypwdsafe/
├── engines/          # auto resolution, ts, python, native
├── js/               # score.mjs, package.json, committed zxcvbn-ts bundle
├── core.py           # result model, HIBP overlay, exit codes
├── render.py         # strict JSON + redaction
├── cli.py            # argparse, stdin, output
├── scan.py           # imps scan orchestration and scan JSON
├── scan_parser.py    # dotenv parser, key classifier, PEM detection
├── ui.py             # terminal report
├── network.py        # HIBP + optional update check
└── metrics.py        # native/legacy metrics

Release process

The release is intentionally boring. version.txt is the single source of truth, and a tag is only created after the candidate has been exercised.

  1. Set version.txt to the candidate version (for example 2.1.0rc1) and update the changelog.
  2. Run the full local checks:
    pytest
    ruff check .
    mypy ismypwdsafe
    python -m build
  3. Push the candidate tag. The publish workflow sends tags containing a, b, or rc to TestPyPI.
  4. Install from TestPyPI in a clean environment and run the GitHub Action with the candidate tag, for example:
    uses: Roma476/IsMyPwdSafe@v2.1.0rc1
  5. Let the candidate sit for a short soak period. Check the TestPyPI artifact, the Action result, and the CLI output before promoting it.
  6. Only then create the stable tag (v2.1.0), publish to PyPI, and merge the release branch into main.
  7. Move the v2 major-version tag to the released commit so uses: Roma476/IsMyPwdSafe@v2 follows the 2.x line.

Patch releases do not rewrite already published tags or PyPI provenance. If a release needs a correction, publish a new patch rather than moving a tag.

PyPI and TestPyPI publishing uses GitHub OIDC Trusted Publishing. The repository must have the pypi and testpypi environments, and the corresponding trusted publishers must be configured on PyPI/TestPyPI for this repository and the publish.yml workflow. No long-lived PyPI token belongs in GitHub secrets.


License

MIT. See LICENSE.

Releases

Packages

Contributors

Languages