Skip to content
Docs menu

Policy authoring

A Kratex policy is a single JSON file, kratex.policy.json, that says what each package is allowed to do. This page is how to write one. For examples ready to paste, see Recipes; for the exhaustive field-by-field schema, see Policy reference.

Anatomy of a rule

Every rule answers four questions: who is acting, what they are trying to do, when it applies, and what to do about it.

{
"id": "my-app.example",
ida unique name you give the rule
"subject": { "package": "some-pkg" },
subjectwho is calling
"target": { "kind": "network" },
targetwhat they are doing
"when": { "phase": "runtime" },
whenconditions (optional)
"effect": { "action": "block" }
effectallow, audit, or block
}
  • subject: who is calling: a package, or a source class like third-party. Omit to match any caller.
  • target: what they are trying to do: a file read, a network call, a spawn. Required.
  • when: optional conditions that must hold, such as a lifecycle phase or CI only.
  • effect: what happens: allow, audit, or block. Required.

Every rule also carries an id, a unique name in org.description form.

Your first policy

Start with one rule. This blocks any third-party package from making a network call:

{
  "id": "my-app.block-third-party-network",
  "subject": { "source": "third-party" },
  "target": { "kind": "network" },
  "effect": { "action": "block" }
}

Read it as a sentence: for any third-party package (subject), on any network call (target), block it (effect).

A rule lives inside a policy file. The minimum is a version and a rules array:

{
  "version": 1,
  "rules": [
    {
      "id": "my-app.block-third-party-network",
      "subject": { "source": "third-party" },
      "target": { "kind": "network" },
      "effect": { "action": "block" }
    }
  ]
}

Save that as kratex.policy.json at your project root and it is active on the next kratex run. A policy that omits mode runs in enforce (blocking) mode; set "mode": "audit" to record violations without blocking while you take inventory.

Now add a rule that carves out an exception. Rules match most-specific-first, so a narrow allow beats the broad block:

{
  "version": 1,
  "rules": [
    {
      "id": "my-app.allow-stripe",
      "subject": { "source": "third-party" },
      "target": { "kind": "network", "host": "api.stripe.com" },
      "effect": { "action": "allow" }
    },
    {
      "id": "my-app.block-third-party-network",
      "subject": { "source": "third-party" },
      "target": { "kind": "network" },
      "effect": { "action": "block" }
    }
  ]
}

Any third-party package can now reach api.stripe.com, and nothing else over the network. The rest of this page is the vocabulary for the four rule parts.

The policy file

The CLI looks for a single file named kratex.policy.json, starting in the working directory and walking up to the filesystem root, so committing it at your project root covers every subdirectory. It is parsed as strict JSON: no comments, no trailing commas, no YAML.

The fields you will use most:

FieldDescription
versionRequired. Always 1.
mode"audit" or "enforce". Defaults to "enforce" when omitted.
rulesYour rules. Omit to run with built-in rules only.
groupsReusable named sets. See Groups.
sensitivityPatterns that label data as sensitive. See Sensitivity and data classes.
telemetryWhere events are routed. See Telemetry.

root, metadata, and a few others round out the schema; the reference has the complete list.

Subject: who is calling

The subject matches the package that made the call, identified by caller-chain attribution.

{ "source": "third-party" }     // any third-party package
{ "package": "prisma" }         // exactly this package
{ "source": "first-party" }     // your own code
  • source classifies the caller by origin: first-party, third-party, unknown, or any.
  • package pins an exact package name, e.g. "prisma" or "@scope/pkg". A package match is more specific than a source match, so it wins ties.
  • packageGroup references a named set from groups.packages.

Omit subject entirely to match any caller. Full subject fields: reference.

Target: what they are doing

Every target has a kind. The other fields narrow it; omit them to match the whole kind.

fs: file reads and writes

{ "kind": "fs", "operation": "read", "path": "~/.aws/credentials" }
{ "kind": "fs", "operation": "write", "path": "**/node_modules/**" }
{ "kind": "fs" }   // any file operation

Match on operation (read or write) and path, a glob where ~ expands to your home directory and ** matches across directories.

network: outbound connections

{ "kind": "network", "host": "api.example.com" }   // any connection to a host
{ "kind": "network", "host": "*.internal" }        // host globs are allowed
{ "kind": "network", "port": 443 }                 // by port

Narrow by host (globs allowed), port, or protocol. The full operation list (dns, unix-socket, and more) is in the reference.

process: subprocess spawns

{ "kind": "process", "command": "curl" }
{ "kind": "process", "command": "npm", "args": ["publish"], "argsMatch": "prefix" }

Match on command (a binary name or absolute path) and args. argsMatch controls how args compare: exact (default), prefix, or any.

env: environment variables

{ "kind": "env", "operation": "read", "name": "OPENAI_API_KEY" }
{ "kind": "env" }   // any env operation

Match on operation (read, write, delete, or enumerate) and name.

runtime: Node capability gating

{ "kind": "runtime", "capability": "workers" }

Gates a specific Node capability, such as workers, vm, ffi, or native-addons. Enforced on every supported Node version; the ffi label specifically needs Node 22.15+ (see Node.js compatibility). Full capability list: reference.

install: lifecycle scripts

{ "kind": "install", "operation": "lifecycle-script", "package": "some-pkg" }

Allows or blocks a named package’s lifecycle scripts during kratex install and kratex ci.

Effect: what happens

{ "action": "allow" }
{ "action": "audit", "severity": "high" }
{ "action": "block", "severity": "critical" }

action is required and is one of:

  • allow: permit it silently. Visibility events on permitted operations occur only when no rule matched and the target carries a data class.
  • audit: permit it and always emit an event.
  • block: what happens depends on mode:
Modeblock does
enforce (default)Throws KratexPolicyViolation in the guarded process; the operation never completes.
auditPermits the operation but emits a would-block event.

severity (info through critical) is optional and carried through to events for filtering. Set "mode": "audit" while taking inventory, then switch to enforce once your allow rules cover the calls you intend to keep.

Conditions: when a rule applies

The optional when block adds conditions that must all hold (logical AND).

{ "when": { "phase": "install" } }   // only during install / ci
{ "when": { "ci": true } }           // only in CI
{ "when": { "exposed": "*" } }       // caller previously touched sensitive data

The conditions you will reach for most:

  • phase: the lifecycle phase: runtime, install, build, test, dev, or ci.
  • ci: true matches only in CI, detected from standard CI environment variables.
  • exposed: matches callers that previously acquired an exposure label, for example after reading a credential. "*" matches any exposure.
  • targetClasses: data classes the target carries: secrets, credentials, pii, wallet, and so on.

exposed and targetClasses work with data classes, covered in Sensitivity and data classes. The full condition list (platform, nodeVersion, targetOutsideProjectRoot, and more) is in the reference.

Groups

Define a named set once and reference it from many rules:

{
  "groups": {
    "paths": {
      "credential-dirs": { "include": ["~/.aws/**", "~/.ssh/**", "~/.kube/**"] }
    }
  },
  "rules": [
    {
      "id": "my-app.block-credential-reads",
      "subject": { "source": "third-party" },
      "target": { "kind": "fs", "operation": "read", "pathGroup": "credential-dirs" },
      "effect": { "action": "block" }
    }
  ]
}

Group types: packages, extensions, paths, hosts, env, and commands. Each entry has include (at least one item) and an optional exclude. Reference a group from the matching field: packageGroup, pathGroup, hostGroup, and so on.

How rules are selected

When more than one rule matches an operation, Kratex keeps the single best match. The intuition:

  • More specific wins. A named package beats a source class; a literal path or host beats a glob; more when conditions beat fewer.
  • Immutable built-ins always win. A handful of structural built-in rules outrank any user rule, regardless of specificity (see Built-in rules).
  • Your rules beat built-ins at equal specificity, and block beats allow when two package-pinned rules disagree.
  • Ties go to the later rule in the array.

If nothing matches, the operation is allowed. The full ordering with worked examples is in the policy reference.

Once you know the vocabulary, Recipes collects rules ready to paste for the common cases.