ciphersins
v1.3.2
Published
Static scanner for JWT, timing, RNG, and password-hashing footguns in Node/TS app code.
Downloads
1,254
Maintainers
Readme
CipherSins
Static analysis for cryptographic misuse in Node/TS app code — broken JWT verification, timing-unsafe compares, weak entropy, and legacy hashing in the paths that guard your users.
Like gitleaks for dangerous crypto call patterns — not secrets buried in strings, but how your app uses crypto libraries: decode-only JWT auth, timing-unsafe compares,
Math.random()in auth paths, MD5/SHA1 password storage, and weak bcrypt cost.
Catch jwt.decode() without jwt.verify() before it reaches production — not another regex grep on node_modules.
Status: 1.3.2 — single npm package (ciphersins) with CLI + programmatic API + GitHub Action and 19 rules.
Contents
- Why not regex (or npm audit)?
- Why use this
- Architecture
- Rules at a glance
- Install
- First success in 30 seconds
- Quickstart
- Documentation
- How this compares
- Examples
- Non-goals
- Development
Why not regex (or npm audit)?
Most crypto vulnerabilities in app code do not look like secrets. They look like ordinary API calls — until an attacker forges a token, guesses an HMAC byte-by-byte, or walks in through a decode-only auth path.
| Approach | Security focus | What it misses |
| ------------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------------------------------- |
| Secret scanners (gitleaks, trufflehog) | Exposed keys and credentials in the repo | Decode-only JWT auth, weak compare, bad RNG — no string to leak |
| npm audit | Known CVEs in dependency versions | Correct library, wrong cryptographic use in your handlers |
| Regex on source | Obvious decode( or Math.random string hits | Import aliases, destructured require, inline require('jsonwebtoken').decode |
| CipherSins | Cryptographic API misuse with import-aware AST rules | Cross-file call graphs (v1 same-file scope) |
CipherSins sits between dependency scanning and manual security review: it flags how your code uses JWT, compare, RNG, and hash primitives before those mistakes become auth bypasses.
What breaks without it
- Integrity skipped —
jwt.decode()reads payload bytes but never validates signature, issuer, audience, or expiry. - Timing side-channels —
===/==on tokens, secrets, or hashes when a crypto/auth import is present (CS-CMP-01). - Predictable entropy —
Math.random()in auth-named functions or bindings (CS-RNG-01). - Broken password storage — MD5/SHA1
createHashor weak-digest PBKDF2 in password-named code (CS-HASH-01); weak bcrypt cost (CS-HASH-02). - Algorithm confusion —
verify()without pinning allowed algorithms (CS-JWT-02); accepting or signing withnone(CS-JWT-03, critical); disabling expiry checks (CS-JWT-04).
Each rule ships with bad/good fixtures and vitest IDs so crypto regressions are caught in CI, not in incident response.
Why use this
- Import-aware AST rules — ties
decode,verify, and compare calls to their real module bindings (default/namespace/named import,require, inline require). - Conservative auth scope (v1) — same-file analysis; any
jwt.verify()in the file suppresses decode-only findings. - Actionable security output — severity, source snippet (API), line:column, and rule doc with fix guidance; CLI prints relative paths and message (snippet via
ciphersinsAPI). - Purpose-built rule set — JWT, timing, RNG, and hash categories instead of generic lint noise.
- Fixture-proven — every rule has
fixtures/<rule-id>/{bad,good}/and numbered tests so cryptographic edge cases stay covered.
Architecture
Your application source is resolved by glob, parsed into a TypeScript AST, and checked by registered rules that encode known cryptographic anti-patterns — not generic syntax warnings.
Design constraints: AST + binding analysis (no regex-only detection); each finding carries severity and a link to remediation docs; cross-file taint tracking is out of scope for v1.
Rule example (CS-JWT-01)
Diagram sources: docs/img/ (Mermaid .mmd + committed SVG). Regenerate with pnpm diagrams:build.
Package layout:
| Export | Role |
| ------------------ | ------------------------------------------------ |
| ciphersins (npm) | CLI binary + import { scan } from "ciphersins" |
Rules at a glance
MVP coverage targets the most common crypto footguns in Node auth and data-protection code:
| ID | Severity | Title | Status | | ---------------------------------------- | -------- | ---------------------------- | ----------- | | CS-JWT-01 | high | JWT decode without verify | implemented | | CS-JWT-02 | high | Verify without algorithms | implemented | | CS-JWT-03 | critical | Algorithm none / bypass | implemented | | CS-JWT-04 | medium | Missing exp validation | implemented | | CS-JWT-05 | medium | JWT sign without expiry | implemented | | CS-JWT-06 | medium | JWT sign with noTimestamp | implemented | | CS-CMP-01 | high | Timing-unsafe compare | implemented | | CS-RNG-01 | high | Math.random in auth context | implemented | | CS-RNG-02 | high | randomBytes length too small | implemented | | CS-HASH-01 | high | MD5/SHA1 for password | implemented | | CS-HASH-02 | medium | Weak bcrypt cost | implemented | | CS-HASH-03 | medium | PBKDF2 iterations too low | implemented | | CS-HASH-04 | medium | scrypt parameters too low | implemented | | CS-HASH-05 | medium | argon2 parameters too low | implemented | | CS-ENC-01 | medium | Hardcoded cipher key or IV | implemented | | CS-ENC-02 | high | AES-GCM static or reused IV | implemented | | CS-ENC-03 | high | Weak cipher algorithm | implemented | | CS-ENC-04 | high | ECB mode cipher | implemented | | CS-DEC-01 | medium | Deprecated createDecipher | implemented |
Full index: docs/rules/README.md.
Install
npm (recommended)
npm install -g ciphersins
# or one-off:
npx ciphersins scan ./srcLibrary API: npm install ciphersins
Requirements: Node.js 20+
GitHub Action (CI)
- uses: 01laky/CipherSins/.github/actions/[email protected]
with:
path: ./src
fail-on: high
format: sarif
upload-sarif: trueFull reference: docs/github-action.md
From source (contributors)
git clone https://github.com/01laky/CipherSins.git
cd ciphersins
pnpm install
./scripts/setup-githooks.sh
pnpm verifyRequires pnpm 9.15.9 (or npm install after clone).
Publish workflow for maintainers: docs/releasing.md.
First success in 30 seconds
After pnpm verify, scan intentionally insecure JWT handling in the bad fixtures:
pnpm exec ciphersins scan fixtures/cs-jwt-01/badfixtures/cs-jwt-01/bad/default-import-decode-only.ts:4:9 CS-JWT-01 high
jwt.decode() used without jwt.verify() in the same function scope.
https://github.com/01laky/CipherSins/blob/main/docs/rules/CS-JWT-01.mdThe good fixtures show the same APIs used with proper verification — expect No findings.:
pnpm exec ciphersins scan fixtures/cs-jwt-01/goodQuickstart
Point the scanner at your auth, API, or middleware layer — anywhere tokens and digests are handled:
# From repo root after pnpm install + build
pnpm exec ciphersins scan ./src
# Or scan a specific path
pnpm exec ciphersins scan path/to/your/appDefault scan root when no path is passed: ./src if it exists, otherwise ..
Include globs: **/*.{ts,tsx,js,jsx} (and uppercase variants).
Exclude: node_modules, dist, *.test.*, *.spec.*.
Documentation
| Doc | Description |
| ------------------------------------------------------- | ------------------------------------------ |
| About | Product positioning, tagline, what we find |
| Scope | Non-goals, rule philosophy, test tiers |
| Archived proposal | Original v1.0 implementation spec |
| Rules index | Per-rule docs and implementation status |
| CS-JWT-01 | JWT integrity — decode without verify |
| CS-JWT-02 | JWT verify without algorithms allowlist |
| CS-JWT-03 | JWT none algorithm bypass (critical) |
| CS-JWT-04 | JWT verify with ignoreExpiration: true |
| CS-CMP-01 | Timing-unsafe compare on auth material |
| CS-RNG-01 | Math.random in auth context |
| CS-HASH-01 | MD5/SHA1 password hashing |
| CS-HASH-02 | Weak bcrypt cost |
| Comparison | vs gitleaks, npm audit, Semgrep, ESLint |
| Architecture | Scan pipeline and rule detection diagrams |
| CLI reference | Commands, output format, exit codes |
| Architecture diagrams | Mermaid sources and SVG regeneration |
| FAQ | Common questions |
| Development | Contributor setup, adding rules |
| Config example | Config schema and example |
| Releasing | npm publish checklist for maintainers |
| Contributing | Commit standards, git hooks |
How this compares
| | CipherSins | gitleaks / trufflehog | npm audit | Semgrep / ESLint |
| --------------------- | -------------------------------- | --------------------- | --------------- | ----------------------- |
| Target | Cryptographic misuse in app code | Secrets in repo | Dependency CVEs | General patterns / lint |
| Example hit | jwt.decode() without verify | AWS key in .env | lodash CVE | Custom rule dependent |
| TS import context | Yes (AST + bindings) | N/A | N/A | Varies |
| npm package | Published (v1.3.2) | Published | Built-in | Published |
Full matrix: docs/comparison.md.
Examples
Scan JWT bad fixtures (should report findings)
pnpm exec ciphersins scan fixtures/cs-jwt-01/badCovers decode-only auth paths: default/named/namespace import, require, destructured require, inline require, TSX, type annotations, local wrappers.
Scan JWT good fixtures (should be clean)
pnpm exec ciphersins scan fixtures/cs-jwt-01/goodCovers verified tokens: decode+verify in same function, verify in nested/dead code, verify-only, local decode() not from jsonwebtoken, decode only in strings/comments. Flags decode in helper functions even when verify exists elsewhere in the file.
Programmatic scan (core API)
import { scan } from "ciphersins";
const result = await scan({ paths: ["./src"], cwd: process.cwd() });
console.log(result.findings, result.summary);Use in custom CI steps when you need structured findings before gating a deploy on crypto rule severity.
Non-goals
- Not a secret scanner — does not hunt API keys, private keys, or passwords in strings.
- Not
npm audit— does not report dependency CVEs or transitive package risk. - Not a full SAST suite — focused MVP rule set for crypto and auth primitive misuse.
- No cross-file call graphs in v1 — same-file scope per rule unless noted.
- Findings do not fail CI by default — use
--fail-on high(or configfailOn) for gating (docs/cli.md). - Inline suppressions —
// ciphersins-ignore-next-linewith optional--allow-critical-ignorefor CS-JWT-03 (docs/cli.md).
Development
pnpm install
./scripts/setup-githooks.sh
pnpm verify| Command | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------- |
| pnpm verify | format → typecheck → build → test → CLI smoke |
| pnpm test | Vitest — unit, per-rule, integration, CLI, audit, generated exhaustive (7777 at v1.3.2) |
| pnpm run generate:tests | Regenerate test/generated/ from scripts/generate-exhaustive-tests.mjs |
| pnpm exec ciphersins scan [path] | Run linked CLI |
| pnpm diagrams:build | Regenerate SVGs from docs/img/*.mmd |
| pnpm format:fix | Apply Prettier (tabs) |
Adding a rule: docs/development.md#adding-a-rule.
Author
Ladislav Kostolny — [email protected] · GitHub @01laky
License
MIT — see LICENSE. Copyright (c) 2026 Ladislav Kostolny.
