
Research
/Security News
TensorLake npm SDK Compromised in ChainDrop Shai-Hulud Credential-Stealing Attack
Tensorlake npm SDK version 0.5.144 was compromised in a ChainDrop / Shai-Hulud attack, delivering credential-stealing malware.
@ultimat3/policy
Advanced tools
One authz system. Two authz systems — one for HTTP, one for "the API", one for
jobs — is how every Meteor-shaped framework died: the surfaces drift, one of them is
wrong, and nobody finds out until it is a CVE. This package exists so a second one is
never necessary. Every MCP tool, live query, job and route resolves the same policy
object through the same evaluate().
export const publishPost = action({
policy: can('post:publish', ({ input, actor }) => ownsPost(actor, input.postId)),
});
No exception, as of 1.3.0. @ultimat3/auth's requireRole() / requireScope() used to gate
routes on the ambient actor without evaluating a policy; they are deleted. They were documented
here as "one honest exception" and had zero callers in the framework or in either tracked app —
a sanctioned second door nobody walked through, whose own documentation admitted that a route
gated that way is invisible to x policy list, to framework.manifest.json and to openapi.json.
Gate a route with a Policy: can('admin:access'), which every introspection surface can read.
requireActor() and currentActor() remain — those assert authentication, which is what
@ultimat3/auth produces.
A policy is a pure (input, actor, row, ctx) => PolicyDecision.
type PolicyDecision =
| { allowed: true }
| { allowed: false; reason: string; code: string };
reason is always safe to log (it names permissions, never row data) and useful
to an agent: actor lacks post:publish and post:publish predicate returned false
are different problems with different fixes.
interface PolicyArgs<I = unknown, R = unknown> {
input: I;
actor: Actor | null;
row: R | null; // required — `null` means "this rule decides on input alone"
ctx?: Ctx;
}
A predicate is written once and is correct in an HTTP route, a job, an MCP tool and a live query's per-row gate:
// decides on input alone — `row` is null
can<{ orgId: string }>('post:create', ({ actor, input }) => actor?.orgId === input.orgId);
// decides about a row the surface already loaded
can<{ postId: string }, Post>('post:publish', ({ actor, row }) => row?.authorId === actor?.id);
row is required and nullable, not optional. An optional field is how the two shapes
drifted apart the first time: the realtime row gate nested the row inside input, so a
row rule and an input rule received different objects and nothing caught it.
Callers have it easier — EvaluateArgs.row is optional, and evaluate() normalises a
missing row to null. A surface that has no row passes { input, actor, ctx } unchanged.
| Builder | Behaviour |
|---|---|
can(p, predicate?) | permission first, then the row-level predicate |
allow() / deny(reason) | terminal; allow() is how "public" is said out loud |
and(...) | first denial wins, its reason is the reason; no clauses is X_POLICY_CLAUSE_EMPTY |
or(...) | first allowance wins; otherwise the last denial — except one carrying X_UNAUTHENTICATED, which outranks a later one. No clauses is X_POLICY_CLAUSE_EMPTY |
not(p) | inverts — except X_UNAUTHENTICATED, which propagates unchanged |
not() never turns "there is no actor" into an allow. can() denies a null actor with
X_UNAUTHENTICATED, and inverting that would make not(can('order:internal')) — the natural
simplification of and(can('order:read'), not(can('order:internal'))) — a public door into the
internal one. or() is what makes that true of a whole TREE rather than only of not's direct
child: it used to report the LAST denial, so not(or(can('order:internal'), deny('read-only mode')))
allowed an anonymous caller — deny's X_FORBIDDEN had overwritten the code not() had to
recognise.
and() and or() refuse an empty clause list at the call that builds them. Nobody writes and();
they write and(...requiredCaps.map(can)) over a config-driven list that filters to nothing — and
an empty and() found nothing to deny and allowed everyone, anonymous callers included, on all
four surfaces, with no diagnostic beyond a label reading and(). allow('public') and
deny('<reason>') are the explicit spellings, so the refusal costs you nothing you cannot say
another way.
policy.permissions (or policyPermissions(policy)) is the flattened, deduped, sorted list of
every permission a tree references, not() clauses included. It is what a compliance report has
to read: label renders a composite as and(post:publish, org:administer), which is a sentence,
never a permission.
admitsAnonymous(policy) is the other derived question As of 2026-08, and it is a walk, not a
root read:
whether an anonymous caller can be allowed at all. policy.kind === 'allow' is the read it
replaces, and it answered "needs a session" for or(allow(), can('x:y')) — so an HTTP route 401'd
a caller the policy itself allows, while the same policy over MCP or a job let that caller in.
import { admitsAnonymous, allow, and, can, not, or } from '@ultimat3/policy';
admitsAnonymous(or(allow('public'), can('post:publish'))); // true
admitsAnonymous(and(allow('public'), can('post:publish'))); // false
admitsAnonymous(not(can('order:internal'))); // false — X_UNAUTHENTICATED propagates
It is exact for an anonymous caller, not a heuristic: with actor === null, can()
short-circuits on the actor check before its predicate runs and allow()/deny() ignore their
arguments, so no predicate is ever consulted and the tree alone decides. true never means
"unguarded" — it says only that a 401 before the handler is wrong; the surface still calls
enforce(). @ultimat3/action and @ultimat3/query derive RouteMeta.auth from it.
surfaces.ts is the proof. Each adapter evaluates and maps a denial to that surface's
error shape; allowed returns undefined.
| Adapter | Denial shape |
|---|---|
enforceHttp | RFC-9457 fields, and the status its code means: 401 for X_UNAUTHENTICATED (no actor), 403 for every other denial. The title is the code's registered one |
enforceLive | close frame 4403 |
enforceJob | failed, retryable: false — the answer will not change on retry |
enforceMcp | isError: true with readable text |
Adding a fifth surface means adding an adapter here and nothing else.
assertAllowed(policy, args) is the throwing form, and it throws the code the DECISION carries:
X_UNAUTHENTICATED for no actor, X_FORBIDDEN for an actor who may not, and the code a predicate
chose with denied(reason, code) otherwise (denialError, a PolicyDenialError for a code this
package does not own).
A predicate — can(p, fn) or definePolicy's check — allows only by returning true or an
{ allowed: true } decision. false denies, and so does anything that is neither a boolean nor a
decision (undefined from a forgotten return, 1, { allowed: 'yes' }): with check under
the declared deny key, with can() as <permission> predicate returned neither a boolean nor a decision. Never a bare TypeError, and never an allow.
enforce(surface, policy, args) dispatches over that table with Object.hasOwn, and a surface
with no adapter is X_POLICY_SURFACE_UNKNOWN. Not a formality: the table is an object literal, so
it inherits Object.prototype — enforce('valueOf' as Surface, …) used to call
Object.prototype.valueOf with the table as its receiver and return a truthy value, so an authz
dispatch failed closed with a SurfaceDenial no caller could read.
definePermissions(['post:publish', ...]) gives a typed set; augmenting
PermissionRegistry (which x g policy generates) makes a typo a compile error, and
can() throws X_PERMISSION_UNKNOWN at declaration time either way. Roles are sugar:
defineRoles({ owner: { grants: ['post:delete'], inherits: ['editor'] } }) expands
depth-first to a flat set, cycles included. post:* and * are supported.
defineRoles() merges into the app's one role map. A second call in a new feature folder
adds roles; it never deletes the first module's. A role two modules define differently is
X_ROLE_REDEFINED, naming both declaration sites — and an identical re-declaration is a no-op,
so defineRoles({ ...roleDefinitions(), … }) stays legal.
permissionDeclarationSites() answers where each permission was declared — one stack frame per
distinct definePermissions() call. x verify's policy step reads it to refuse an app rule
whose permission only a package declared (X_PERMISSION_BORROWED: defineAdmin() declares
<entity>:read|write|delete, and a process that never mounts the admin does not).
The flattened grant set is memoised per actor and invalidated the moment the role map changes.
It is keyed on the actor object, so it lives exactly as long as the request does: @ultimat3/auth
re-reads the user row every request, and a revoked role takes effect on the next one.
evaluate() returns a depth-first trace naming the clause that decided. /_x renders
it, explain() logs one line, and policyMatrix() turns actors × policy into an
assert-ready table:
owner allow
editor deny post:read predicate returned false
viewer deny actor lacks post:publish
Building it is opt-in: on outside production, and in production only once a decision sink is
installed. A live query evaluates policy per subscriber on every change event, so an unread
TraceEntry[] per evaluation is real allocation on the busiest path there is. Force it with
evaluate(policy, args, { trace: true }).
setDecisionSink({
record(event) {
// { label, allowed, code, reason, actorId, actorKind, orgId, surface, deciding }
},
});
No-op until installed, emitted from one place — inside evaluate(), so a fifth surface
inherits it — and it records the allow as well as the denial, which is the half an access
review actually asks for. It never carries row or input: reason is safe to log by
construction, and the sink inherits that guarantee. A sink that throws is logged and swallowed;
it never turns an allowed request into a 500.
X_FORBIDDEN · X_POLICY_MISSING · X_PERMISSION_UNKNOWN · X_POLICY_SURFACE_UNKNOWN ·
X_ROLE_REDEFINED
A missing policy is a type error, not a throw: ActionDef.policy is required, so an action
without one does not compile. policyMissing() stays for a declaration site that cannot say it
in a type — a config-driven route table, a policy resolved by name.
Every error class src/index.ts exports, for instanceof inside one process. Across a wire or
a job boundary the class is gone and the code is what survives — match on that.
| Class | Code | Declared in |
|---|---|---|
PolicyError | any PolicyErrorCode — POLICY_ERROR_CODES | src/errors.ts |
Tier 2. Imports @ultimat3/core only. Surface error shapes are declared structurally
so this package never imports @ultimat3/http (sibling tier) or the tier-3/4 surfaces
that import it.
FAQs
The one authz rule, evaluated identically in every surface
The npm package @ultimat3/policy receives a total of 2,078 weekly downloads. As such, @ultimat3/policy popularity was classified as popular.
We found that @ultimat3/policy demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Research
/Security News
Tensorlake npm SDK version 0.5.144 was compromised in a ChainDrop / Shai-Hulud attack, delivering credential-stealing malware.

Research
/Security News
Socket found 16 malicious Firefox extensions designed to steal crypto wallet recovery phrases and private keys using cloned Rabby and OKX interfaces.

Product
Socket now scans VS Code extensions, giving teams early detection of risky behaviors, hidden capabilities, and supply chain threats in developer tools.