Skip to content

security: host-controlled per-package capability enforcement (compile-time) #501

@proggeramlug

Description

@proggeramlug

The headline supply-chain feature. Most npm packages will never declare capabilities themselves — control sits entirely in the host app's package.json. The compiler walks each imported package's HIR, cross-references stdlib call sites against the host's policy, and refuses to compile on violation. Package authors do nothing.

Example host config

{
  "perry": {
    "permissions": {
      "lodash": [],
      "axios": ["net:https://api.example.com"],
      "left-pad": [],
      "@scope/utils": ["crypto"],
      "*": ["crypto"]
    }
  }
}

If lodash's compiled HIR contains a call to fs.readFile, the build fails at the offending line:

lodash calls fs.readFile at node_modules/lodash/template.js:42 but is not granted fs:read in your perry.permissions; either add the capability or replace the dep.

Most real-world npm supply-chain incidents (event-stream, ua-parser-js, several 2023/2024 incidents) would be caught by a default-deny policy on *.

Mechanism

HIR pass per imported source module; cross-reference each stdlib call site against the host's policy; fail compilation on violation. Zero runtime cost — pure compile-time refusal, no FFI-boundary checks emitted.

The user's own root code defaults to * (full capability); per-dep entries override the * default.

Capability tokens (initial set)

  • fs:read, fs:write, fs:exec
  • net:fetch, net:listen, net:<host> (literal or glob)
  • proc:env, proc:exec, proc:argv
  • crypto
  • time (Date.now / performance.now — optional, side-channel paranoia)
  • * for unrestricted

Token taxonomy is extensible as Perry's stdlib grows.

Acceptance

  • Host package.json perry.permissions: { "<pkg>": ["cap1", "cap2"], "*": ["default"] }
  • HIR pass walks each dep's source modules and cross-references stdlib calls
  • Violation fails build at the offending source span (file + line + capability + missing token)
  • User's own root code defaults to *, configurable
  • perry audit (separate issue) shows which capabilities each dep would need to compile cleanly
  • Wildcard host patterns: "net:*.example.com", "net:https://api.acme.com/v1/*"
  • Nested deps inherit policy: a transitive call from lodash that lands in fs.readFile is attributed to whichever package's source module contains the call site
  • Initial capability tokens documented + extension path defined
  • Composes with the URL allowlist issue (capability net:<host> and the URL allowlist enforce the same constraint at different granularities)

Part of the supply-chain hardening series. The big lever. Host-app-controlled. Zero runtime cost (compile-time analysis only).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions