npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@safeguard-sh/iast

v1.0.1

Published

Safeguard IAST agent for Node.js: tracks untrusted input from source to dangerous sink at runtime

Readme

Safeguard IAST agent — Node.js

Covers JavaScript and TypeScript. Tracks untrusted input from the point it enters the application to the point a dangerous operation consumes it, and reports the flows it observes to the collector hosted by a running Safeguard scan.

There is no separate TypeScript agent: TypeScript compiles to JavaScript before it runs, so the agent instruments the emitted JavaScript and resolves each finding's position back through the source map to the .ts file you actually edit. Findings resolved that way are marked sourceMapped.

Install

No dependencies, deliberately — the agent runs inside someone else's application, and forcing a package into their tree risks conflicting with their own versions.

# .npmrc
@safeguard-sh:registry=https://registry.safeguard.sh/npm/

npm install @safeguard-sh/[email protected]

The package is public on registry.safeguard.sh; no token is needed to install it.

The package was first published as @safeguard/iast. On npmjs.com the @safeguard scope belongs to someone else, so the agent moved to Safeguard's own scope, @safeguard-sh. @safeguard/iast still installs from registry.safeguard.sh, unchanged, but npm shows a deprecation notice; switch to @safeguard-sh/iast (same code) and the .npmrc line above.

Always-on mode

For a long-running application, report to Safeguard's persistent collector instead of a scan's. Issue an agent token in the app (Integrations > Configure > IAST agents) and set it; that is the whole opt-in:

SAFEGUARD_IAST_TOKEN=sg_iast_... \
SAFEGUARD_IAST_APP=acme-api \
node --require @safeguard-sh/iast/register server.js

| Variable | Meaning | |---|---| | SAFEGUARD_IAST_TOKEN | An sg_iast_ agent token selects always-on mode | | SAFEGUARD_IAST_ENDPOINT | Collector base URL (default https://api.safeguard.sh/gpt/api/v1/iast) | | SAFEGUARD_IAST_ENABLED | 0 turns an always-on agent off without removing it |

In always-on mode the agent also sends a heartbeat every 5 minutes (so "last seen" means running), re-sends a flow it already reported at most every 15 minutes with only the new observations, keeps findings across a collector outage (bounded queue), and tracks at most 10,000 distinct flows. Scan mode (SAFEGUARD_IAST_COLLECTOR + SAFEGUARD_IAST_ENABLED=1) works exactly as before.

Full guide: https://docs.safeguard.sh/integrations/iast-agents

Run (scan mode)

SAFEGUARD_IAST_ENABLED=1 \
SAFEGUARD_IAST_COLLECTOR=http://scanner-host:7878 \
SAFEGUARD_IAST_TOKEN=<the scan's iast_agent_token> \
SAFEGUARD_IAST_APP=acme-api \
node -r @safeguard-sh/iast/register server.js

| Variable | Meaning | |---|---| | SAFEGUARD_IAST_ENABLED | 1/true to turn the agent on. Default off. | | SAFEGUARD_IAST_COLLECTOR | Collector base URL, e.g. http://scanner:7878 | | SAFEGUARD_IAST_TOKEN | Bearer token; must equal the scan's iast_agent_token | | SAFEGUARD_IAST_APP | Application name shown against findings | | SAFEGUARD_IAST_DEBUG | 1 to log what was instrumented |

The preload puts the module hook in place before your code loads its database driver, and wraps the http/https servers so every request's query, headers and cookies are registered with no code change. Mounting the middleware as well lets the agent see what Express has parsed (req.query, req.body, cookies) — first, before any body parser or route:

const iast = require('@safeguard-sh/iast');
iast.install();                 // not needed if you used -r @safeguard-sh/iast/register

app.use(iast.middleware());     // Express / Connect / NestJS

Without Express:

http.createServer(iast.wrapHandler(handler));

What it detects

Sources: query parameters, path parameters, headers, cookies, and the parsed body (registered lazily on first read, since body parsers run after the middleware). Anything else, mark yourself with iast.track(value, kind, name).

| Class | Covers | |---|---| | sql | mysql, mysql2, pg, sqlite3 (Database.run/all/get/each/exec/prepare), better-sqlite3 (prepare/exec), mssql, oracledb, sequelize, knex | | command | child_process.exec/execSync, and spawn/execFile when shell: true | | eval | new Function(...) / Function(...) | | path | fs and fs.promises read/write/unlink/rename/mkdir/readdir/open, streams (a value passed through path.join/resolve/normalize is still recognised) | | ssrf | axios, node-fetch, got | | nosql | mongodb collection queries |

What it will not report, on purpose

  • Bound query parameters. query(sql, params) is correct, and params are where user input belongs. Only the statement is inspected.
  • spawn/execFile without a shell. The argument array goes to the OS directly; no shell parses it, so a tainted argument is an argument.
  • Short values. Anything under 6 characters is never matched — en, id and 1 appear inside ordinary SQL by coincidence.
  • The host header. Set by infrastructure and present in ordinary URLs throughout an application.

How it works, and what it cannot do

The Python agent carries taint on a str subclass. JavaScript does not allow that, and it is worth being precise about why, because it rules the approach out rather than merely complicating it:

  • a boxed String is not interchangeable with a primitive (typeof is "object", === "abc" is false), and handing one to application code would change how that code behaves;
  • primitives cannot be WeakMap keys, so no side table can attach to the value;
  • template literals, + and every String method produce new primitives.

So this agent tracks by recognition: inputs are registered per request, and a sink checks whether one appears verbatim in what it is about to execute. When a request parameter is present inside the SQL being run, that is not an inference — the attacker's exact bytes are in the statement.

The safeguards that make this sound rather than noisy are the minimum length, the per-request scope, the cap on tracked values, and registering the decoded form of each parameter as well as the raw one.

Per-request scope uses AsyncLocalStorage, which is a genuine advantage over the Python agent's thread-local: Node is single-threaded and asynchronous, so a thread-local equivalent would attribute a flow to whichever request happened to be on the stack. The scope follows the request through awaits, callbacks and promise chains, and concurrent requests cannot see each other's input — there is a test for exactly that.

Known limitations, stated rather than papered over:

  • CommonJS only. Modules are intercepted through Module._load. An application using native ESM import for its own code does not go through it. Most Node servers and effectively all database drivers still load via CJS.
  • eval cannot be wrapped. It is a syntactic form with access to the caller's scope; replacing the binding would change what code means. new Function(...) is covered, which is the form that takes a string built at runtime.
  • The trace has two frames, the source and the sink. The agent recognises the value at the sink, so it honestly knows where the input entered and where it was consumed. Inventing intermediate hops would be fabricating evidence.

Safety

  • Never throws into application code.
  • Never blocks a request: findings go on a bounded queue drained by an unref'd timer, so the agent cannot keep a process alive or stall a request.
  • One vulnerable call produces one finding — nested sinks (exec → spawn) are guarded.
  • Deduplicates locally, reporting a count rather than thousands of copies.

Tests

npm test

test/endtoend.test.js also writes the golden report that pkg/appsec/iast/crosslang_test.go decodes. Regenerate it with:

SAFEGUARD_IAST_UPDATE_GOLDEN=1 node --test test/endtoend.test.js