permission-surface shows what an agent artifact can do before you install it; it does not certify that artifact as safe. By Shakhzod, fintech founder and former fraud practitioner, for runsagents.
| Problem | A one-line install can hide lifecycle hooks mutable downloads and writes outside the repository |
| Theory | Software supply chain risk management evaluates component exposure before adoption |
| This tool | A non-executing static scan inventories capability and permission signals before installation |
flowchart LR
A[Unpacked artifact] --> B[Static scan without execution]
B --> C[Eight signal categories]
C --> D[Declared versus observed]
D --> E[Surface report]
E --> F[Human install decision]
An agent finds an innocent-looking skill. Its SKILL.md is polished, its
README promises a useful shortcut, and the install command is one line. Hidden
behind that line is a package lifecycle hook. The hook downloads a changing
script and pipes it into a shell before the human has seen what it touches.
The problem is not that every hook or network request is malicious. The problem
is that the artifact's effective permission surface was invisible at the moment
of choice. permission-surface makes those static signals inspectable before
installation. It never executes scanned content.
The v1 scanner reports eight categories:
- Executable files and shebangs — executable mode bits and interpreter lines.
- Install and lifecycle hooks —
preinstall,install,postinstall, andpreparepackage scripts. - Network endpoints — referenced HTTP(S) URLs and their domains.
- Shell execution — curl/wget-to-shell,
eval, and Nodechild_processpatterns. - Writes outside the repository — write-like commands aimed at dot-directories in the home folder or common absolute global paths.
- Secret-looking patterns — credential-shaped values, redacted in the report.
- Mutable-ref fetches — remote raw content addressed by
main,master,HEAD, or similar mutable labels rather than an immutable commit SHA. - Declared versus undeclared capabilities — observed capabilities compared with a
## Declared capabilitieslist inSKILL.md.
Each match includes a severity, file, line, evidence, and explanation. Severity is triage metadata, not proof of exploitability or intent.
Requirements: Node.js 20 or newer. There are no runtime or development dependencies and no install script.
git clone https://github.com/runsagents/permission-surface.git
cd permission-surface
npm testScan an unpacked artifact before running its installer:
node src/scan.mjs fixtures/benign-skill
node src/scan.mjs fixtures/risky-skill
node src/scan.mjs --format json fixtures/risky-skillThe documentation-only synthetic fixture is quiet:
# SURFACE REPORT — benign-skill
> **This is a STATIC heuristic scan, not a security audit. Findings may include false positives and miss real risks.**
**Overall signal:** QUIET · **Findings:** 0
- Declared: none
- Observed: none
- Undeclared: none
No heuristic findings. This means the scanner was quiet, not that the artifact is safe.
The deliberately suspicious synthetic fixture is flagged:
# SURFACE REPORT — risky-skill
> **This is a STATIC heuristic scan, not a security audit. Findings may include false positives and miss real risks.**
**Overall signal:** HIGH · **Findings:** 9
INFO 0 · LOW 0 · MEDIUM 3 · HIGH 6 · CRITICAL 0
Observed: execute-code, install-hooks, network-access, shell-execution, writes-outside-repo
Undeclared: execute-code, install-hooks, network-access, shell-execution, writes-outside-repo
The complete generated Markdown for both fixtures is in
examples/surface-report.md. The JSON contract is
schemas/surface-report.schema.json.
An artifact can declare capability slugs in its SKILL.md:
## Declared capabilities
- network-access
- execute-codeV1 recognizes these observed capability slugs: execute-code,
install-hooks, network-access, shell-execution, and
writes-outside-repo. A declaration provides context; it does not suppress the
underlying finding.
Start with capability differences, then inspect every high-severity row at the reported source line. A mutable fetch asks “what content will arrive later?” A lifecycle hook asks “when can this run?” An outside-repository write asks “what state can this change?” Network references in documentation and example code can be benign, so read the surrounding source.
QUIET means no v1 heuristic matched. HIGH means at least one high-severity
pattern matched. Neither result is an install decision. The human should compare
the behavior with the artifact's purpose, provenance, pinned inputs, and their
own risk tolerance.
This is a static heuristic scan. It has false positives and false negatives, does not execute code, resolve indirection, inspect fetched content, model every language or installer, or prove whether a behavior is reachable. It is not a security audit and makes no “safe” or “malicious” determination. Review source, provenance, signatures or hashes where available, and runtime isolation before installing. Respect the licenses of projects you scan and of any code or text you reuse from them.
The scanner skips .git, node_modules, binary-looking files, and symlinks. Its
outside-path check focuses on common shell write forms and common global roots;
other write mechanisms and paths will be missed. Secret matching is deliberately
coarse and report evidence is redacted, but the original scanned file is not
modified.
Created by Shakhzod for
runsagents: know what an artifact can do before
your agent installs it. Released under
CC0 1.0 Universal.
See ATTRIBUTION.md for source and fixture notes.
This pattern is known in the field as software supply chain risk management, related to SBOM-based component transparency. This implementation adds a static, pre-install inventory of the capabilities an agent skill can request.