← Back to Blog

Why the Right SAST Stack for TypeScript in 2026 Is Semgrep, Socket, and Nark

By Caleb Gates

The best static-analysis stack for a modern TypeScript team in 2026 is three tools running side by side: Semgrep for taint analysis on your own code, Socket for supply-chain risk in the packages you install, and Nark for correct handling of the packages you already trust. Each tool covers a different failure surface with essentially zero overlap. Together they answer the three questions a mature TypeScript team needs answered before merging code: is the code I wrote safe, is the code I depend on safe, and am I using the code I depend on correctly?

TL;DR. Semgrep catches vulnerabilities in the shape of your own dataflow (SQL injection, hardcoded secrets, unsafe deserialization). Socket catches vulnerabilities and supply-chain risk in the packages you install (malicious updates, known CVEs, install-script tampering). Nark catches missing or incorrect handling of the documented failure modes each package throws at runtime. Run all three in CI. To try Nark: npx nark --tsconfig ./tsconfig.json.


What each tool covers

Three separate questions. Three separate tools.

Semgrep: custom-code taint and pattern analysis

Semgrep is a semantic code search and static-analysis tool. You point it at your repository, apply a ruleset (the public registry has thousands of rules, plus custom rules you author), and it flags patterns that match. The rule language is code-shaped rather than regex-shaped, so a rule matches AST structure rather than string substrings.

The classes of finding Semgrep is well-suited to catch include SQL injection, unsafe HTML rendering in React, hardcoded API keys and secrets, insecure cryptography choices, unsafe deserialization, missing authentication middleware, cross-site scripting sinks, and framework-specific misuses. Its strength is the parts of the vulnerability surface that live in the shape of your own code, independent of any particular package.

Semgrep OSS is free and self-hosted. Semgrep Cloud adds a hosted UI, a rule store, and enterprise features. Either way the analysis runs against your code, not your dependencies.

Socket: supply-chain risk in your dependencies

Socket sits at the package-integrity layer. It watches your package.json and lockfiles and analyzes every dependency (and every transitive dependency) for supply-chain risk signals. That includes known CVEs, malicious install scripts, sudden maintainer changes on a low-traffic package, typosquats, deprecated packages, and license anomalies.

The key thing Socket adds over an older CVE-only scanner is behavioral risk detection. A package with no CVE can still be dangerous if its most recent version added a call to a suspicious network endpoint from a postinstall script. Socket catches that class where a straight CVE database check does not.

Socket integrates with GitHub as a PR bot that flags risky dependency changes on the diff. It runs on every PR and blocks or warns depending on your configuration.

Nark: correct usage of the packages you trust

Nark is a scanner for one specific class of bug: the calling code's failure to handle documented errors from packages you depend on. It uses the TypeScript compiler to resolve types, then matches every call site against a library of Nark Profiles, each of which is a specification of what an npm package can throw at runtime and what the caller must do about it.

A Nark Profile for stripe says (roughly) that stripe.charges.create can throw StripeCardError, StripeRateLimitError, StripeConnectionError, and StripeAuthenticationError, and that the calling code must catch these before returning success. A Nark Profile for axios says that any axios call can throw AxiosError, and that a correct catch either narrows via axios.isAxiosError(error) or handles the union of network and HTTP cases explicitly.

The corpus ships 169+ profiles for the packages TypeScript teams actually use in production: axios, prisma, stripe, openai, redis, tanstack-query, zod, dayjs, plus the major cloud SDKs. The profiles are grounded in each package's docs, changelog, and reported production incidents.

Why these three together

Each tool answers one of the three questions above. None of them answers more than one. So there is no wasted spend on running all three.

Here is the coverage broken out.

QuestionToolExample finding
Is the code I wrote safe?SemgrepSQL query built by string concatenation on a user-controlled input
Is the code I depend on safe?SocketNew version of a package added a postinstall script phoning home to an unfamiliar domain
Am I using the code I depend on correctly?Narkstripe.charges.create call with a catch block that logs but does not narrow on StripeCardError, so declines silently succeed

Any team relying on only one of the three has a specific class of bug it cannot catch until runtime. That is the gap that motivates running all three.

The gap without each

Direct on what falls through when you skip one.

Without Semgrep

Custom-code dataflow bugs slip through. A junior engineer builds a query with template-literal string concatenation and passes a user-controlled variable directly into the SQL string. The Prisma call itself is correct. The dependency is at a safe version. The catch handles PrismaClientKnownRequestError correctly. Everything downstream of the taint sink looks fine. But the taint sink is a SQL injection point, and no dependency-side or package-usage tool will spot it because it is entirely inside your own code.

Semgrep's registry has rules for exactly this. The Semgrep OWASP Top 10 pack and the p/typescript registry pack together catch most SQL injection, XSS, and hardcoded-secret patterns out of the box.

Without Socket

A malicious or compromised package can enter node_modules with nobody noticing. In recent years the supply-chain risk pattern has shifted from "attackers publish a typosquat and hope you install it" to "attackers compromise the maintainer credentials of a legitimate package with a million weekly downloads and publish a poisoned patch release." A CVE database will not catch this until after the fact. Socket's behavioral analysis catches the risk signal at PR time: the new version added a postinstall step, an unfamiliar network destination, or a maintainer just changed hands.

Without Socket, this class only shows up when a security researcher publishes an advisory a week or two after the compromise. Meanwhile the poisoned version is in your production build.

Without Nark

A swallowed error in an axios call ships to production. The endpoint sometimes returns 500. The catch block logs to console. The Sentry inbox does not get the error because the catch caught it. The function returns undefined. Downstream code treats undefined as "no data" and quietly writes a broken row to the database. Six weeks later a customer notices their reports are wrong and files a support ticket.

This is a package-usage bug. It is not a taint issue (no untrusted input reaches an unsafe sink). It is not a supply-chain issue (axios is at a safe version). It is the calling code's misunderstanding of what axios's catch shape has to look like to be correct. Semgrep does not know that axios throws AxiosError with a specific structure. Socket does not care about axios beyond its version integrity. Only a tool with a per-package profile catches this class, and only if the profile is grounded in axios's actual documented behavior.

How to install all three in CI

The three tools install cleanly side by side. A GitHub Actions example:

name: Static analysis
on: [pull_request]

jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: returntocorp/semgrep-action@v1
        with:
          config: p/typescript p/owasp-top-ten

  socket:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: SocketDev/socket-action@v1
        with:
          socket-api-key: ${{ secrets.SOCKET_API_KEY }}

  nark:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx nark --tsconfig ./tsconfig.json

Each job runs independently. A failure in any one fails the CI check. That is the intended shape: three orthogonal gates, and merges require all three to pass.

For teams not on GitHub Actions, each vendor publishes docs for GitLab CI, CircleCI, and Jenkins integrations. The Nark CI setup guide covers the Nark half.

What NOT to add unless you need it

Being direct on tools that overlap and where the overlap sits. This is not a knock on any of them. Each has real strengths. But adding an overlapping tool means paying twice for the same coverage without gaining much.

Snyk. Snyk Open Source and Snyk Code together cover a broadly similar surface to Socket + Semgrep. Snyk's strength is the SCA reporting layer, the enterprise dashboard, and integrations with license-compliance workflows. Add Snyk when you need those features for procurement or a specific audit workflow. If you have the reporting requirement handled elsewhere, Socket + Semgrep covers the underlying vulnerability surface without the license cost.

CodeQL. GitHub Advanced Security includes CodeQL, which is a semantic-taint analyzer in the same category as Semgrep and Snyk Code. If your organization is already on GitHub Enterprise with Advanced Security enabled, CodeQL is available at no incremental cost and there is no strong reason to add Semgrep on top. If you are not on GitHub Enterprise, Semgrep is the pragmatic choice because it is free and self-hostable.

Dependabot. Dependabot handles known-CVE alerts and automated dependency-update PRs. It overlaps partially with Socket on the known-CVE side but misses the behavioral supply-chain signals Socket catches. Running Dependabot alongside Socket is fine and common. Running Dependabot instead of Socket leaves the behavioral-risk class uncovered.

CodeRabbit, Cubic, and other AI review bots. These are probabilistic reviewers that comment on style, architecture, and suggested refactors. They sit above the deterministic static-analysis layer and add a different kind of value. If your team wants stylistic and architectural review commentary on every PR, add one of them on top of the deterministic three. But do not treat them as substitutes for the deterministic tools. A probabilistic reviewer that catches 60% of a class of bug is not the same shape of evidence as a deterministic scanner that catches 100% of what it was designed to catch. For compliance workflows and audit trails, only the deterministic layer counts.

ESLint. ESLint is not a security tool. It catches style and type-adjacent issues, and the @typescript-eslint/no-floating-promises rule catches a narrow subset of what Nark catches. Run ESLint alongside these three (most teams already do) but do not count it toward the SAST stack.

Sonarqube. Sonarqube covers code-quality metrics, style enforcement, and some SAST. Its SAST layer overlaps partially with Semgrep. Teams that need Sonarqube's code-quality dashboards for engineering-management reasons run it independently of the security stack.

Buy vs build

The alternative to running these three tools is writing your own equivalent. That is a bigger commitment than most teams estimate.

The Nark buy-vs-build page walks through the tradeoff for Nark specifically. The short version: the profile library is the moat, and a curated 169+ profile library grounded in each package's docs and shipping updates is not something a team can casually replicate. The same analysis applies to Semgrep (the community ruleset is deep) and Socket (the behavioral risk signals are trained on years of malicious-package data).

The one case where a team writes their own is when you have a highly custom internal framework or a proprietary set of packages that no vendor covers. In that case, Semgrep's custom rules feature is the right primitive, because you can teach it your patterns without leaving the tool.

Getting started

Install each tool in whatever order suits your team.

  • Semgrep. pip install semgrep and run semgrep --config p/typescript from your repo root, or add the GitHub Action from the example above. See semgrep.dev for the ruleset registry.
  • Socket. Sign up at socket.dev, connect your GitHub organization, and the PR bot starts commenting on dependency changes on your next PR.
  • Nark. npx nark --tsconfig ./tsconfig.json from your project root. For the full CI wiring, see /docs/ci-setup. For how Nark sits in the broader stack, see How Nark fits in your stack.

Three tools. Three failure surfaces. No overlap. That is the SAST stack a TypeScript team in 2026 actually needs.