{"id":50901679,"url":"https://github.com/didrod205/jwtlens","last_synced_at":"2026-06-16T03:04:39.797Z","repository":{"id":362599802,"uuid":"1259922559","full_name":"didrod205/jwtlens","owner":"didrod205","description":"Decode, audit \u0026 verify JWTs entirely offline — stop pasting live tokens into jwt.io. Local signature verification (HS/RS/PS/ES/EdDSA) with your own secret/PEM/JWK, plus a security lint (alg:none, exp, aud/iss). Deterministic CLI, JSON/MD reports.","archived":false,"fork":false,"pushed_at":"2026-06-05T02:51:08.000Z","size":67,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-05T04:10:27.199Z","etag":null,"topics":["appsec","auth","authentication","cli","json-web-token","jws","jwt","jwt-cli","jwt-decode","node","offline","security","token","typescript","verify"],"latest_commit_sha":null,"homepage":"https://didrod205.github.io/jwtlens/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/didrod205.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-05T01:54:31.000Z","updated_at":"2026-06-05T02:51:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/didrod205/jwtlens","commit_stats":null,"previous_names":["didrod205/jwtlens"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/didrod205/jwtlens","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fjwtlens","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fjwtlens/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fjwtlens/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fjwtlens/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/didrod205","download_url":"https://codeload.github.com/didrod205/jwtlens/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Fjwtlens/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34388681,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["appsec","auth","authentication","cli","json-web-token","jws","jwt","jwt-cli","jwt-decode","node","offline","security","token","typescript","verify"],"created_at":"2026-06-16T03:04:39.725Z","updated_at":"2026-06-16T03:04:39.780Z","avatar_url":"https://github.com/didrod205.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 🔍 jwtlens\n\n### Decode, audit \u0026 verify JWTs — entirely offline. Stop pasting live tokens into jwt.io.\n\n[![npm version](https://img.shields.io/npm/v/jwtlens.svg?color=success)](https://www.npmjs.com/package/jwtlens)\n[![CI](https://github.com/didrod205/jwtlens/actions/workflows/ci.yml/badge.svg)](https://github.com/didrod205/jwtlens/actions/workflows/ci.yml)\n[![types](https://img.shields.io/npm/types/jwtlens.svg)](https://www.npmjs.com/package/jwtlens)\n[![license](https://img.shields.io/npm/l/jwtlens.svg)](./LICENSE)\n\n**[🌐 Try the browser playground →](https://didrod205.github.io/jwtlens/)** \u0026nbsp;·\u0026nbsp; paste a JWT, see it decoded + audited + verified. Nothing is uploaded — it all runs client-side.\n\n\u003c/div\u003e\n\nAn API rejects your request, so you grab the JWT from a log line and — to read its\nclaims — paste it into **jwt.io**. That token is a **production credential**, and\nyou just sent it to a third-party website. Then you eyeball `exp`/`aud`/`alg` by\nhand, and to actually check the signature you'd need the key loaded *somewhere*.\n\n**jwtlens decodes, security-audits, and *verifies* a JWT entirely on your\nmachine.** No website, no API key, no network. Decode + lint are pure; signature\nverification runs locally via `node:crypto` with **your own secret, PEM, or JWK**.\n\n```bash\necho \"$TOKEN\" | npx jwtlens scan --key public.pem\n```\n\n```\nHeader\n  alg      \"RS256\"     typ \"JWT\"     kid \"k1\"\nPayload\n  iss      \"https://auth.example.com\"\n  sub      \"user-4012\"\n  exp      1893456900  (in 15m)\n  iat      1893456000  (in 0s)\n\nStatus     active\nSignature  ✓ verified (RS256) — signature matches the supplied key\nScore  100/100 (A)\n```\n\n---\n\n## Why jwtlens?\n\n- 🔒 **Offline by design.** Your token (and your signing key) never leave the\n  machine — the whole reason not to use jwt.io for a live credential.\n- ✅ **It verifies, not just decodes.** Real `node:crypto` signature checks for\n  **HS256/384/512, RS*, PS*, ES256/384/512 and EdDSA** — including the ECDSA raw\n  (P1363) signature format that trips up naive verifiers.\n- 🛡️ **It audits the security posture.** `alg: none`, a signed header with no\n  signature, no/over-long `exp`, future-dated `iat`, missing `aud`/`iss`/`sub`/\n  `jti`, and the HS↔RS **alg-confusion** trap — each with the fix.\n- 🤖 **Deterministic \u0026 CI-ready.** Same token → same result. Gate your issuer in\n  CI: fail the build if it ever emits `alg: none` or a forever-token.\n\nWhy not ask an LLM? base64url, claim timing, and **signature math** are exact — a\nchatbot can't (and shouldn't) hold your secret to verify a signature, and you need\nthis on every token, in CI, not once.\n\n## Install\n\n```bash\n# run it now\necho \"$TOKEN\" | npx jwtlens scan\n\n# or add it\nnpm install -g jwtlens      # global CLI\nnpm install -D jwtlens      # CI / library\n```\n\nNode ≥ 18. Decode + lint are dependency-free and browser-safe; verification uses\n`node:crypto`.\n\n## Quick start\n\n```bash\njwtlens scan \"$TOKEN\"                          # decode + security lint\necho \"$TOKEN\" | jwtlens scan                   # from stdin (Bearer prefix ok)\njwtlens scan token.txt --key public.pem        # + verify RS/ES/EdDSA\njwtlens scan \"$TOKEN\" --secret \"$HS_SECRET\"    # + verify HS*\njwtlens verify token.txt --jwk jwks.json       # just verify (exit 0/1)\njwtlens scan \"$TOKEN\" --now 2030-01-01         # evaluate timing at a fixed date\njwtlens scan \"$TOKEN\" --min-score 80 --md report.md   # CI gate + report\n```\n\nSee [`examples/sample-report.md`](./examples/sample-report.md) and the bundled\n`examples/*.jwt.txt` (a strong RS256 token, a weak HS256 one, and an `alg: none`).\n\n## What it checks\n\n| Group | Examples |\n| ----- | -------- |\n| **Signature \u0026 algorithm** | `alg: none` (error), signed header with empty signature, unknown alg, symmetric-alg (alg-confusion) warning |\n| **Expiry \u0026 timing** | missing `exp` (never expires), over-long lifetime, `iat` in the future |\n| **Claims** | missing `aud` / `iss` / `sub` / `jti` |\n| **Token status** | expired, not-yet-valid (`nbf`), with human-readable \"3h ago\" / \"in 15m\" |\n| **Verification** | local signature check with `--secret` / `--key` (PEM) / `--jwk` (JWK or JWKS, by `kid`) |\n\nFindings roll up to a 0–100 score and an A–F grade you can gate in CI.\n\n## Real scenarios\n\n**1. Inspect a prod token safely.** Pull it from a log, pipe it in, read the\nclaims and timing — without handing the credential to a website.\n\n```bash\nkubectl logs api | grep -o 'eyJ[^ ]*' | head -1 | jwtlens scan\n```\n\n**2. Verify a token's signature locally.** Confirm a token really came from your\nissuer using the public JWKS, offline:\n\n```bash\njwtlens verify \"$TOKEN\" --jwk jwks.json \u0026\u0026 echo \"authentic\"\n```\n\n**3. Gate your issuer in CI.** Assert the tokens your auth service mints are\nwell-formed (signed, short-lived, scoped):\n\n```bash\nyour-cli mint-test-token | npx jwtlens scan --min-score 85\n```\n\n## Configuration\n\n`jwtlens init` writes `jwtlens.config.json`:\n\n```jsonc\n{\n  \"ignore\": [],                 // rule ids to skip, e.g. [\"no-jti\"]\n  \"maxLifetimeSeconds\": 86400,  // warn above this token lifetime\n  \"clockSkewSeconds\": 60,       // tolerance for iat/nbf \"in the future\"\n  \"minScore\": 0                 // CI gate threshold\n}\n```\n\n## Library API\n\n```ts\nimport { analyzeToken, verifyJwt, decodeJwt, DEFAULT_CONFIG } from \"jwtlens\";\n\nconst now = Math.floor(Date.now() / 1000);\nconst { decoded, findings } = analyzeToken(token, DEFAULT_CONFIG, now);\nconst result = verifyJwt(decoded, { secret: process.env.HS_SECRET });\nconsole.log(result.valid, findings.map((f) =\u003e f.rule));\n```\n\n`decodeJwt` and `lintJwt` are pure (browser-safe); `verifyJwt` uses `node:crypto`.\n\n## Roadmap\n\n- 🤖 **Optional `--ai` layer (bring-your-own key)** to explain a token's claims in\n  context. The core stays 100% offline and deterministic.\n- Fetch a JWKS from an `iss`/`.well-known` URL (explicit opt-in; off by default).\n- `x5c`/`x5t` certificate-chain awareness and key-type/alg mismatch checks.\n- Encrypted JWE structure inspection (header only).\n- ✅ **A browser playground** that runs decode + lint + verify fully client-side —\n  [live here](https://didrod205.github.io/jwtlens/) (nothing uploaded).\n\n## 💖 Sponsor\n\njwtlens is free and MIT-licensed, built and maintained in spare time. If it kept a\nlive token off a third-party website, please consider supporting it:\n\n- ⭐ **Star this repo** — the simplest free way to help others find it.\n- 🍋 **[Sponsor via Lemon Squeezy](https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010)** — one-time or recurring.\n\n## License\n\n[MIT](./LICENSE) © jwtlens contributors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidrod205%2Fjwtlens","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdidrod205%2Fjwtlens","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidrod205%2Fjwtlens/lists"}