Find and fix unbounded TypeScript I/O before it ships.
hardened is a deterministic CLI that scans your TypeScript project for production-reliability risks: HTTP calls without timeouts, database queries that can hang, fetch() calls without lifecycle signals, and risky async fan-outs. It applies reviewable auto-fixes where the local transformation is clear. No AI, no hidden state. Same input always produces the same finding set and fix.
Your code today:
import axios from "axios"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export async function chargeInvoice(id: string, amount: number) {
const invoice = await prisma.invoice.findUnique({ where: { id } })
return axios.post("/api/payments/charge", { invoiceId: id, amount })
}After npx hardened risk fix:
import axios from "axios"
import { resilient } from "hardened-runtime"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export async function chargeInvoice(id: string, amount: number) {
const invoice = await resilient(
() => prisma.invoice.findUnique({ where: { id } }),
{ timeout: 10_000 },
)
return resilient(
() => axios.post("/api/payments/charge", { invoiceId: id, amount }),
{ timeout: 10_000 },
)
}Two calls that could have hung your whole API under load now have caller-side deadlines. If the deadline fires, resilient() rejects the caller's promise; the underlying operation may continue unless the client supports its own abort mechanism.
Use ESLint too. hardened is narrower: it focuses on production I/O boundaries where a finding often needs more than a local token edit. Its auto-fixes can wrap calls, add imports, request runtime dependencies, and keep the diff reviewable across a codebase. The CLI also ships a small runtime package, opens draft PRs with --pr, and emits SARIF directly for code scanning. ESLint rules are excellent for broad style, correctness, and type-aware checks; hardened deliberately avoids that surface. The overlap is intentional only where zero-config reliability checks are useful on projects that do not already have type-aware linting configured.
npm install -D hardened # or: pnpm / yarn / bunNo install required to try it:
npx hardened risk scan # report findings without changing anything# 1. Generate a tailored config (detects axios/prisma/pg/etc. in your package.json).
npx hardened init
# 2. See what it would flag.
npx hardened risk scan
# 3. Apply fixes locally.
npx hardened risk fix
# 4. Or: apply fixes on a new branch and open a draft PR for review.
npx hardened risk fix --prSeven rules as of v0.1, covering the most common reliability gaps. See docs/RULES.md for the full catalogue (auto-generated from source metadata).
| Rule | Severity | Auto-fix | What it catches |
|---|---|---|---|
risk/http-no-timeout |
π΄ error | yes | axios, got, ky, fetch, native http/https calls missing a timeout |
risk/db-no-query-timeout |
π΄ error | yes | pg / mysql2 query calls missing a statement timeout |
risk/prisma-no-timeout |
π΄ error | yes | Prisma findMany / update / delete / etc. without a configured timeout |
risk/fetch-no-abort-signal |
π‘ warning | finding-only | fetch() calls without AbortSignal for component/request lifecycle cleanup |
risk/floating-promise |
π‘ warning | finding-only | Promise-returning calls that nothing awaits, catches, or chains |
risk/await-in-loop |
π‘ warning | finding-only | Sequential N+1 patterns where parallelism may be intended |
risk/promise-all-no-settled |
π΅ info | finding-only | Fire-and-forget Promise.all that should use Promise.allSettled |
Zero-config by default. To customize, keep or edit the file npx hardened init created:
// hardened.config.ts
import { defineConfig } from "hardened"
export default defineConfig({
rules: {
"risk/http-no-timeout": "error",
"risk/fetch-no-abort-signal": "warning",
"risk/await-in-loop": "off", // silence a rule
},
ignore: [
"**/legacy/**", // exclude paths
],
runtime: {
defaults: {
timeout: 5_000, // your default timeout
retries: 2, // retry count for idempotent calls
backoff: "exponential",
},
},
})Put // hardened-ignore-next-line before the call. Blank lines and comment-only lines may sit between the directive and the call; the directive applies only to the next non-comment, non-blank line.
// hardened-ignore-next-line
return axios.get(`/api/legacy/${id}`) // intentionally no timeoutDrop hardened into your pipeline in two shapes:
- SARIF upload to GitHub code-scanning β findings show up in the Security tab. Output:
hardened risk scan --sarif out.sarif, then pass throughgithub/codeql-action/upload-sarif@v3. - Sticky PR comment bot β see
docs/recipes/ci-comment-bot.mdfor a copy-paste workflow that posts a per-PR findings summary and updates in place.
GITHUB_TOKENβ required only forhardened risk fix --pr. Any token withreposcope works. If unset,--prmode falls back togh auth tokenfrom the GitHub CLI. Unset is fine for all other commands.HARDENED_CORPUS_READONLY=1β operator-controlled write gate. When set,risk fix,risk fix --pr, andinitrefuse to run and exit 1.risk scanremains functional. Useful for running the scanner against a code snapshot that must not be modified.
hardened init # generate hardened.config.ts
hardened risk scan # report findings
hardened risk scan --json # machine-readable output
hardened risk scan --sarif [file] # SARIF 2.1.0 for code-scanning
hardened risk fix # apply auto-fixes to disk
hardened risk fix --dry-run # show what would change, don't write
hardened risk fix --pr # open a draft GitHub PR on a new branchresilient() enforces a caller-side deadline; it does not abort the wrapped syscall by itself. AbortSignal propagation is planned for a follow-on release. Prisma raw template-tagged calls such as prisma.$queryRaw\SELECT ...`are outside v0.1 detection because they areTaggedTemplateExpression` nodes, not normal call expressions.
- Zero/near-zero config. Reasonable defaults. Config is opt-in, not required.
- Deterministic output. Same input β byte-identical fix. Proven by a cross-corpus determinism test suite.
- Safe to run automatically. Rules where the fix isn't uniquely determined stay finding-only. The things we auto-apply are the things no human disputes.
- Opinionated but not ideological.
// hardened-ignore-next-linealways wins β legitimate sequential code, known unsafe opt-outs, and research snippets stay exactly how you wrote them.
| Version | Status | What it adds |
|---|---|---|
v0.1 |
Alpha | hardened risk β 7 rules, auto-fix for 3 of them, PR flow, SARIF |
v1.0 |
Planned | Hardening of the risk ruleset + stable config API |
v2.0 |
Reserved | hardened config β extract hardcoded values to config layer |
v3.0 |
Reserved | hardened schema β DB β API β frontend naming consistency |
The v2 and v3 packages exist as empty slots in the monorepo today so future commands ship without core refactors.
See CONTRIBUTING.md. Apache 2.0.
Bugs, rule proposals, and design feedback welcome via GitHub Issues. Rule proposals are reviewed against three criteria: determinism, false-positive risk, and whether the local transformation is safe enough to automate.
Apache 2.0. See LICENSE.