Policy reference
This page is the lookup reference. For a guided introduction with examples, see Policy authoring.
Policy source resolution
When you run any Kratex command, the CLI resolves a policy through three slots in order (first match wins):
| Slot | Source | How |
|---|---|---|
| 1 | Walk-up file discovery | Looks for kratex.policy.json starting at CWD and climbing to the filesystem root. Stops at the first match. |
| 2 | Rules cache | rules-cache/rules.json under the per-user Kratex config directory. If present, it is validated as a full policy and used ahead of the bundled default, surfaced as policy source remote; an unparseable or invalid cache file is ignored with a warning. |
| 3 | Bundled default policy | Bundled in the CLI binary. Uses mode: "enforce". Always matches. |
Mode source order
For CLI commands, the effective mode is resolved in this order (first match wins):
--auditor--enforceflag on the command.modefield in the policy file.- Resolver default:
"enforce". A policy that omitsmodeenforces; Kratex never defaults to observe-only.
The KRATEX_MODE environment variable is read only when the runtime is loaded directly as a preload hook (node --import with KRATEX_REGISTER=1), where it overrides the policy file’s mode. It has no effect on CLI commands: kratex run, kratex install, and kratex ci set it on the child process themselves.
Top-level schema
{
version: 1 // required, always 1
mode?: "audit" | "enforce"
root?: string // base for relative paths; defaults to file directory
metadata?: PolicyMetadata
groups?: PolicyGroups
rules?: PolicyRule[]
sensitivity?: SensitivityPolicy
dataClasses?: Record<string, { displayName: string; category?: string; description?: string }>
// declare your own data-class ids for sensitivity and conditions
telemetry?: { sinks: SinkConfig[] }
extensions?: Record<string, unknown> // vendor metadata; keys must be namespaced (contain a ".")
}
Unknown top-level keys are rejected at parse time.
Subject schema
{
kind?: "package" // default
source?: "first-party" | "third-party" | "unknown" | "any"
package?: string // exact package name
packageGroup?: string // name from groups.packages
}
Every field is optional. With none set, or subject omitted entirely, the rule matches any caller at the lowest specificity: an absent source filters nothing and an absent package pins nothing. Narrow by source (origin) or by package / packageGroup (specific packages); a package match outranks a source match.
A second subject form matches editor extensions (the Guardian lane) by extension id:
{
kind: "extension" // required to select this form
source?: "first-party" | "third-party" | "unknown" | "any"
extension?: string // exact extension id, publisher.name
extensionGroup?: string // name from groups.extensions
}
Target schema
Every target requires kind; all other fields are optional filters, combined with logical AND. A target with only kind matches every operation of that kind: { "kind": "network" } matches any outbound connection, { "kind": "fs" } any read or write. Each field you add narrows the match further.
fs
{
kind: "fs"
operation?: "read" | "write"
path?: string // glob; ~ expands to home directory
pathGroup?: string // name from groups.paths
}
network
{
kind: "network"
operation?: "connect" | "dns" | "unix-socket" | "custom-lookup" | "host-header-override"
host?: string // glob
hostGroup?: string // name from groups.hosts
port?: number // 1-65535
protocol?: string
path?: string // URL path glob
pathGroup?: string
}
process
{
kind: "process"
operation?: "spawn" | "shell"
command?: string // exact binary name or absolute path
commandGroup?: string // name from groups.commands
args?: string[]
argsMatch?: "exact" | "prefix" | "any" // default: "exact"
cwd?: string
env?: string[]
}
env
{
kind: "env"
operation?: "read" | "write" | "delete" | "enumerate"
name?: string // exact variable name
nameGroup?: string // name from groups.env
}
runtime
{
kind: "runtime"
capability?: "workers" | "cluster" | "native-addons" | "native-binding"
| "ffi" | "wasi" | "inspector" | "vm" | "dynamic-code"
| "patched-api-replacement"
}
All capabilities are enforced on every supported Node version. One authoring detail: rules targeting the ffi capability by name fire on Node 22.15+; on older versions those loads are caught under native-addons. See Node.js compatibility.
install
{
kind: "install"
operation?: "lifecycle-script"
package?: string
packageGroup?: string
script?: string // e.g. "postinstall"
}
Condition schema (when)
All fields are optional. All specified fields must match (logical AND).
{
phase?: "runtime" | "install" | "build" | "test" | "dev" | "ci"
platform?: string | string[] // "darwin", "linux", "win32"
arch?: string | string[] // "x64", "arm64"
nodeVersion?: string // semver range
packageVersion?: string // semver range
dependencyKind?: "dependencies" | "devDependencies"
| "optionalDependencies" | "peerDependencies"
ci?: boolean
cwd?: string
env?: Record<string, string | boolean>
extensions?: Record<string, unknown> // vendor metadata; keys must be namespaced (contain a ".")
exposed?: string | string[] // "*" or specific label
targetClasses?: string | string[]
targetOutsideSubjectRoot?: boolean
targetOutsideProjectRoot?: boolean
targetExistsBeforeWrite?: boolean // fs write ops only
}
Effect schema
{
action: "allow" | "audit" | "block" // required
severity?: "info" | "low" | "medium" | "high" | "critical"
reason?: string
tags?: string[] // cannot include "kratex.overrides-user" or "kratex.enforce-in-audit" (reserved)
}
Groups schema
groups: {
packages?: Record<string, { include: string[], exclude?: string[], description?: string }>
extensions?: Record<string, { include: string[], exclude?: string[], description?: string }>
paths?: Record<string, { include: string[], exclude?: string[], description?: string }>
hosts?: Record<string, { include: string[], exclude?: string[], description?: string }>
env?: Record<string, { include: string[], exclude?: string[], description?: string }>
commands?: Record<string, { include: string[], exclude?: string[], description?: string }>
}
Each group has at least one include entry. exclude is optional. extensions holds extension ids (publisher.name) and backs subject.extensionGroup; extension ids and package names are separate namespaces with separate group maps.
Sensitivity schema
Declares the path, environment-variable, and host patterns that carry data-class labels. Each entry is either a bare pattern string or a { pattern, classes } object.
sensitivity: {
paths?: (string | { pattern: string; classes?: string[] })[]
env?: (string | { pattern: string; classes?: string[] })[]
hosts?: (string | { pattern: string; classes?: string[] })[]
}
A bare pattern carries the generic sensitive class; the object form assigns the listed classes. Built-in classification already labels common credential paths (credentials), crypto wallets (wallet), and similar before any sensitivity block is added. Classes feed the exposed and targetClasses conditions. For a guided walkthrough, see Sensitivity and data classes.
Rule precedence
When multiple rules match an operation, the engine picks one winner using these dimensions in order (the first non-zero delta decides):
- Immutable tag. A rule carrying
kratex.overrides-useroutranks any rule without it. No user rule can carry this tag. - Subject specificity. Named package (highest) > named group >
sourceclass > unspecified (lowest). - Target specificity. Literal paths/hosts score higher than globs; globs score higher than omitted fields. More constrained targets win.
- Condition specificity. More
whenkeys means higher score. - Origin. User rules beat built-in rules at equal specificity.
- Package-chain block beats allow. When two package-constrained rules both match but their matched caller frames are different packages with different actions,
blockwins. - Index tiebreaker. Later position in the
rulesarray wins.
When no rule matches, the operation is allowed by default with no severity and no tags.
Common parse errors
| Message | Cause |
|---|---|
Kratex policy <path> must be an object | Top-level value is not an object: e.g. a bare array or scalar. |
rules[N].id duplicate id "<id>" | Two rules in the policy share the same id. |
duplicate rule id: <id> | A rule id collides with a built-in rule id. Checked after schema validation, when built-in and user rules are merged. |
<path> <message> | Zod schema validation failure. <path> is a dotted path with bracketed array indices (rules[0].subject.package). |
ENOENT: no such file or directory, open '<path>' | A discovered policy file exists but cannot be read (e.g. a broken symlink). A permissions failure surfaces as EACCES instead. |
Failed to parse Kratex policy <path>: … | Invalid JSON: most commonly a comment or trailing comma. kratex.policy.json is strict JSON, so neither is allowed; the underlying parser message follows the colon. |