@hafizhpratama/stryx
v0.6.0
Published
Stack-aware security scanner for JavaScript and TypeScript backends.
Maintainers
Readme
@hafizhpratama/stryx
Stack-aware security for JavaScript and TypeScript backends.
A stack-aware security scanner for JavaScript and TypeScript backends. Stryx detects runtime, framework, database, validation, auth, and LLM SDK surfaces, then catches missing input validation, leaked secrets, weak auth, SQL injection, command injection, SSRF, open redirects, path traversal, and unsafe LLM prompt handling using cross-file taint analysis that single-file linters can't match.
Install
npm install -g @hafizhpratama/stryx
# or one-off
npx @hafizhpratama/stryx scanThe package ships prebuilt native binaries for macOS (x64 / arm64),
Linux (x64 / arm64-gnu), and Windows (x64-msvc). npm picks the right
platform binary at install time via optionalDependencies — total
download is ~1.5 MB per user.
Quick start
cd your-typescript-project
npx @hafizhpratama/stryx scanOutput:
critical flow/sql-injection app/api/search/route.ts:14:22
Untrusted request input reaches a raw-SQL call as the query
string without parameterisation (OWASP A03 / CWE-89).
help: Switch to `prisma.$queryRaw`...`` (tagged template),
which binds values instead of splicing.
1 finding(s): 1 critical, 0 high, 0 medium, 0 low, 0 infoExit code is non-zero when any finding meets or exceeds the
--fail-on threshold (default: high). Drop it into CI to gate
deploys.
Flags
| Flag | Default | Description |
|---|---|---|
| --format | human | Output format: human or json |
| --fail-on | high | Minimum severity for non-zero exit: info / low / medium / high / critical |
| --version | — | Show version |
| --help | — | Show help |
npx @hafizhpratama/stryx scan ./src --format=json
npx @hafizhpratama/stryx scan . --fail-on=mediumWhat it catches
47 rules in the registry. Highlights below — see the
full rule library
for the complete set across flow/, auth/, crypto/, express/,
and generic/ categories:
| Rule | Severity | Catches |
|---|---|---|
| flow/sql-injection | Critical | Body taint → $queryRawUnsafe / sql.raw / db.sequelize.query / raw query (with first-arg SQL-shape gate) |
| flow/command-injection-via-exec | Critical | Body taint → child_process exec/spawn — distinct severity for shell-parsed vs argv-form sinks |
| flow/eval-injection | Critical | Body taint → eval / Function / setTimeout-with-string + dynamic require(...) / await import(...) |
| flow/insecure-deserialize | Critical | Body taint → unserialize / yaml.load / vm.runInX + prototype-pollution shapes (Object.assign(target, src) / lodash merge / jQuery extend) |
| auth/jwt-hardcoded-secret | Critical | Literal secret passed to jwt.sign / jwt.verify (jsonwebtoken) or expressJwt({ secret }) (express-jwt) |
| auth/session-hardcoded-secret | Critical | Literal secret passed to session({ secret }) (express-session) or cookieSession({ secret }) (cookie-session) |
| flow/ssrf-via-fetch | High/Medium | Body taint → fetch / axios / got / needle / request / http(s).X |
| flow/nosql-injection | High | Body-shaped object → MongoDB collection.find/update/delete (DI-service receivers like this.articleService.findOne rejected) |
| flow/redirect-open | High | Body taint → NextResponse.redirect etc. (same-origin path literals + explicit safety wrappers recognised) |
| flow/unvalidated-body-to-db | High | Body → Prisma/Drizzle/TypeORM/Mongoose/NestJS injected service without zod/valibot/yup |
| flow/auth-bypass-via-wrapper | High | withAuth(...) wrapper that doesn't actually check |
| flow/secret-to-response | High | process.env.X reaching response body |
| flow/path-traversal | High | Body taint → fs.<method> path |
| flow/prompt-injection | High | Body taint → LLM prompt content (OpenAI / Anthropic) |
| flow/xss-via-dangerously-set-inner-html | High | Body taint → React dangerouslySetInnerHTML |
| generic/hardcoded-secret | Critical/High | Provider-prefix tokens (AWS / Anthropic / Stripe / GitHub / OpenAI) + credential-named bindings with Shannon-entropy + identifier-shape gating; test / i18n / translations / locales paths skipped |
Eleven rules trace flows across files — a route handler in
app/api/.../route.ts that hands data to a helper in lib/<x>.ts
which then sinks to a DB / fetch / exec call is caught at the route's
call site, even though no single file shows the full path. The three
new v0.4.0 categories (eval / NoSQL / deserialize) gained cross-file
flow in v0.5.0.
See the full rule library on GitHub.
Each rule page is also a fix guide: it explains the concrete safe pattern and what Stryx recognizes as fixed.
How it works
JavaScript / TypeScript source + package.json + lockfiles + configs
↓
Project profile: detect runtime / framework / data layer /
validator / auth / LLM SDK / deployment from
package metadata (no source parsing)
↓
Layer 1: oxc parser → arena AST (per file, parallel)
↓
Layer 2: project semantic index + 22 stack adapters + AST rules
+ taint engine (adapters consume the profile; rules consult
the active adapter set during taint propagation)
↓
Layer 3 (optional): LLM escalation on flagged uncertain zones, cached
↓
Findings (JSON or human text), prepended by a compact stack block:
stack: language: typescript • runtime: bun • framework: hono • ...Most findings come from deterministic Rust analysis in milliseconds. Genuinely ambiguous zones (a custom helper whose intent the engine can't decide statically) escalate to an LLM check — bring your own API key, or omit and stay fully local.
Suppress false positives
Inline:
// stryx-disable-next-line flow/sql-injection -- signed webhookFile-level:
// stryx-disable flow/sql-injectionFound a false positive? Open an issue with the real code that triggered it.
Performance
- ≤ 10 ms per 500-line TS file (p99)
- ≤ 30 s for a 10k-file repo with no LLM
- Sub-1 ms per rule per file
Status
v0.6.0 — source-origin classification + 31 new rules across 6
batches (16 → 47 registered). Closes every category where
Semgrep OSS measurably wins for JS/TS BE security per the
gap analysis:
JWT algorithm/verification flaws, CORS misconfiguration,
prototype-pollution loop, vm2 sandbox, cookie security attributes,
ReDoS, crypto correctness (GCM tag length, AEAD final, pseudo-random
bytes, argon2 config, buffer noAssert), server-template XSS
(Pug / EJS / Mustache / Handlebars / response-write / res.render),
XXE / XML-parsing (xml2json / libxmljs / sax / expat), Express
middleware config (csurf presence + ordering, directory listing,
X-Frame-Options, res.sendFile path-traversal), and JWT payload-data
exposure / non-revocation / passport / HMAC hardcoded-secret. ADR
0015 Phase 1 source-origin classifier ships in stryx_taint::origin;
22 rules opt into a per-rule accepts_origin policy. Cross-file
ConfigDerived (Phase 3) deferred to v0.7.0 — it's what would close
the remaining 64 redirect-open findings in better-auth's OAuth
provider files structurally. An official
GitHub Action
with sticky PR comments + annotations, plus a stryx install
subcommand that writes per-agent backend-security rule files for
Claude Code, Cursor, Codex, OpenCode, and the AGENTS.md convention.
APIs follow SemVer. See the
changelog
for release-by-release detail, the
calibration record
for per-target dogfood verification, and the
roadmap
for what's next.
Links
- Repo: hafizhpratama/stryx
- Issues: github.com/hafizhpratama/stryx/issues
- Rule library: docs/rules/
- License: Apache 2.0
