The scanner: run it
Who this is for: the engineer who wants to know what the AI agents in their code, and on their machine, can do before anything is installed.
Who this is for: the engineer who wants to know what the AI agents in their code, and on their machine, can do before anything is installed.
What you end up with: a report of every tool a model can call, which of them cannot be undone, and a draft policy for ZIFFER, the agent authorization service, written from what was found. Nothing is sent anywhere, and you do not need an account.
Read what the scan reads and does not before you trust a clean result. The scan reads source files and configuration. It reads no running system, so a tool your application only builds at run time can be missing from the report, and a check the scan did not find is not proof that nobody is asked.
This page describes @ziffer-io/scan version 0.3.0. The command it installs is ziffer-scan.
1. What the scan is for
It finds the tools a model can call: the ones your own code gives a model, and the ones the AI tools installed on your machine can reach. It then shows what ZIFFER would do with each call, using ZIFFER's own decision engine, which runs on your machine inside the package.
2. What you need
- Node.js 22 or later.
- Python 3.9 or later, only if your project has Python files. The scan reads Python with your
machine's own Python, found on your PATH as
python3orpython. Without it, the Python files are not read: the report says how many Python files were present and not read, and it does not claim it found no tools in them. - For a TypeScript or JavaScript project, run it after installing your dependencies. The scan follows types, and imports it cannot resolve make the grade provisional. What the scan reads says more.
3. Run it
From your project folder:
npx @ziffer-io/scanIt first says what it will read. It reads your code, then lists every tool server your AI tools are configured to start, and asks before starting any of them:
1 tool server will be started to list their tools:
mail node mail-server.mjs (Claude Code)Answer y to start them. Any other answer stops the run by name, and nothing is started. This is output from an example project
with one invented server, mail.
To see every option:
npx @ziffer-io/scan --help4. Three ways to run it
The default: your code, then your installed AI tools. One draft policy covers both. It also
replays eight injected instructions against that draft, to show what each would do with and
without ZIFFER. Run from your home folder or from /, the code half is skipped with one line
saying so: pass --cwd or run from your project folder.
--code: your code only. It starts no tool server and reads no home folder. It ends with the
one place to put the ZIFFER call in your code and the code to paste there. There is no replay,
because the injected instructions are aimed at an installed AI tool and none was read. Run from
your home folder or from /, it refuses by name.
npx @ziffer-io/scan --code --report--ci <policy-dir>: a pull request in your policy repository. It reads no code. It starts the
tool servers configured in that repository, without asking, and grades each tool against the draft
policy in <policy-dir>, read unsigned because a pull request carries a draft. It prints one line
per tool and exits 1 if any tool is refused, with the line to add to the policy.
npx @ziffer-io/scan --ci policyIn an example repository with one mail server, the first and last lines were:
UNSIGNED: the policy was read without checking a signature, because a pull request carries a draft; this checks what the rules decide, not that the bundle verifies.
2 tools graded: 1 allowed, 1 held for approval, 0 refused; 0 not checked. Exit 0.A server that cannot start in the job is printed as not checked, and does not fail the run.
There is also ziffer-scan replay: the eight injected instructions against the test policy
shipped in the package, and nothing else.
5. Every option
| Option | What it does |
|---|---|
--code | Your code only. Starts no tool server. Cannot be combined with replay, --no-code, --ci, --home, --yes, --timeout or --no-replay. |
--no-code | Your installed AI tools only. The code is not read. |
--cwd <dir> | The folder whose code is read, and the project whose AI tool configuration is read. Default: the current folder. |
--full | After the one-screen summary, the complete report and the replay case by case. |
--json | One JSON document on standard output instead of the screen. Progress and questions go to standard error. |
--yes | Start the tool servers found without asking. Use it only when you have read the list another way. |
--out <dir> | Where the draft policy folder is written. Default ./ziffer-scan/ziffer-policy. Everything else goes beside it. |
--no-replay | Write the draft policy, skip the replay. |
--timeout <s> | Seconds each tool server has to start and list its tools. Default 60. A server fetched by npx or uvx can need longer the first time. |
--home <dir> | Read the AI tool configuration under this home folder instead of yours. |
--platform <p> | darwin, linux or win32. Default: this machine's. |
--color | Colour even when standard output is not a terminal. |
--no-color | No colour. Setting the NO_COLOR environment variable does the same. |
--report | Also write the HTML report, the JSON file and the review archive. See section 6. |
--ci <policy-dir> | The pull-request check in section 4. Implies --yes. Cannot be combined with replay, --out, --report, --home, --full, --no-replay or --no-code. |
--help | The option list. -h does the same. |
--version | The version of the package. |
An option the scan does not know is refused by name, with the option list after it.
6. What it writes, and where
Everything goes in one folder, ./ziffer-scan/, in the folder you ran it from. --out moves the
policy folder, and everything else is written beside it.
| File | When |
|---|---|
ziffer-policy/ | Every run except --ci and replay. The draft policy. |
ziffer-tools.json | When your code was read. The ZIFFER call you paste reads the tool names from it: ship it with your application, or point ZIFFER_TOOLS_FILE at it. |
ziffer-scan.json | When your code was read, or with --report. The same document --json prints. |
ziffer-replay-own.json | When the replay ran. One replay case the scan wrote from one of your own tools. |
ziffer-scan-report.html | With --report. The report to open and forward. |
ziffer-review.tar.gz | With --report. The report, the JSON file and the policy folder in one archive, for the review. |
It never overwrites. If a file it would write is already there, it refuses by name and writes
nothing: move the old folder, or pass another --out. Run inside a git repository, it tells you to
add the folder to .gitignore or to write it elsewhere. The first lines after the report's header
say what was written and that it is yours to delete.
--ci writes nothing. Credentials are shown as [redacted] on the screen, in the report and in the
JSON, and paths under your home folder start with ~ in the files.
7. What it starts, and what it never does
It sends nothing anywhere. The scan, the draft policy, the grading and the replay make no
network request. npx itself downloads the package the first time, like any other package.
It starts the tool servers your AI tools are configured to start, and only after you say yes.
Before it starts anything it prints each server and the command it will run, and waits for y.
With --yes it does not ask. With no terminal to ask on and no --yes, it refuses and starts
nothing. At most four servers start at once. The only thing it asks a server is its list of tools:
it never calls a tool. A server is given the environment its own configuration declares plus a
few basic variables such as PATH and HOME, not the rest of yours. The servers are your own
programs and do whatever they do when your AI tools start them, which can include reaching the
network. A remote server, one that is turned off, or one whose command is not installed is not
started, and the report says why.
Reading your code starts no tool server, which is why --code starts none. To read Python it
runs your machine's own Python on the scan's reader, which ships in the package. It looks for that
Python whenever it reads your code, even in a project with no Python files.
It runs no AI model and does not change your source files. The only files it writes are the ones in section 6.
8. Exit codes
| Code | Meaning |
|---|---|
| 0 | The run finished. Under --ci: and no tool was refused. |
| 1 | Under --ci: at least one tool was refused. Otherwise: an error the scan does not name, printed as one line. |
| 2 | The run was refused, by name, on one line of standard error. For example a wrong option, no terminal to ask on, a folder that already holds a policy, or --code run from a home folder. |
A refusal looks like this:
UnknownArgument: --verbose9. The scan from your AI assistant
The same scan runs from the ZIFFER MCP server, the tool server your AI coding assistant can call. First, add the ZIFFER MCP server to your assistant.
Then ask your assistant, in your own words, to scan the project. It calls the server's scan
tool, which runs this package:
- The first call reads your code and returns what it found. It starts no tool server.
- Installed tool servers are listed first and started only after you agree. If your AI tools
are configured to start tool servers, the first call lists them, with credentials shown as
[redacted], and starts none of them. The tool's instructions tell your assistant to show you that list and to call again, to start them, only once you agree. - The files go into a new folder under your system's temporary folder, never into your project, unless your assistant names another folder. By default that includes the HTML report and the review archive.
- You can ask why one tool got its result. Your assistant calls
explain_scan_findingwith the tool's name, and gets back the engine's decision for it, the entry in the draft policy it came from, and how to change it. It answers about the last scan in that session.
ZIFFER has no AI agent of its own. The server runs the scan and returns its results, and no model is asked anything. The explanation you read, and any change made to your code, come from your own AI assistant.
Two things are not offered from your assistant: the pull-request check (--ci) and the replay on
its own (ziffer-scan replay). Run those from the command line.