npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

glance-scanner

v1.5.6

Published

Security scanner for AI agents. Static analysis by default (semgrep + npm audit), no API key. Optional AI semantic analysis on your own provider key.

Readme

Glance Scanner

Reads code and tells you what is already wrong in it. Built for the files an AI agent runs on: skills, MCP server definitions, hooks, and the repositories an agent has been pointed at.

Part of Glance. This package is the half that reads code at rest. The half that watches a running agent, Glance guard, is in early access and is not in this package.

MIT licensed. Runs locally; the only data that leaves your machine is the dependency list npm audit checks against npm's advisory database. Turn on AI analysis and your code also goes to the provider whose key you supply, and only to them.

Try it in one line

npx glance-scanner analyze --repo https://github.com/Panniantong/Agent-Reach

That clones a public repository to a temporary directory, scans it, prints a report and deletes the clone. No key, no install, no configuration.

To scan something of your own:

npx glance-scanner analyze --path .        # a directory, recursively
npx glance-scanner analyze --file app.js   # one file

Code is only half of it. To scan what an agent reads — MCP server configs and skill files:

npx glance-scanner surfaces --root .       # MCP configs and prompt files

What you get without a key

Two real static engines, no API key, no account:

| Engine | Finds | Needs | | --- | --- | --- | | semgrep p/default | 1074 rules: injection, traversal, XSS, deserialisation, leaked credentials | one install, see below | | rules/ in this repo | SQL injection built by string concatenation in JavaScript, which p/default misses | nothing, it ships here | | npm audit | dependencies with published advisories | a package.json and a lockfile |

Every line number comes from the engine that found it. Nothing here recomputes or guesses one. A finding that has no line number is printed without one — an npm audit advisory is about a dependency, not a place in your code, so it gets no line rather than a made-up one.

The scan names both engines every time it runs, and says why either one did not:

Static analysis (no API key). Add --ai for semantic analysis.
  semgrep (p/default + glance rules): 23 findings in 19.4s
  npm audit: 144 findings in 2.1s

If neither is available it says so and returns nothing. It never falls back to invented output.

A partial scan is never printed as a clean one. semgrep applies its own ignore list, and pointed at a directory it can decline to open a single file while still exiting 0 with "Findings: 0". So the number of files semgrep says it scanned is checked against the number it was given, and any shortfall is printed as a warning naming the paths — on stderr as well as in the report, and in --json under engines. If it scanned nothing at all, the engine is reported as not having run:

  semgrep: did not run — scanned 0 of 1 path(s) — semgrep skipped everything it
  was given. This is NOT a clean result.

Installing semgrep

npx glance-scanner install-tools --semgrep     # or: pipx install semgrep

The first semgrep run downloads its rules to ~/.semgrep. After that it runs with no network at all. The CLI tells you when that download is about to happen. --metrics=off is passed on every invocation, always.

If pip refuses with externally-managed-environment, that is PEP 668: your Python is managed by Homebrew or your distribution and pip will not write into it. --user fails the same way, so re-running pip is not the fix. install-tools detects this, retries through pipx — which puts semgrep in its own virtualenv, which PEP 668 allows — and if pipx is missing or fails it names the interpreter to install rather than repeating the command that just failed.

The verdict line, and what an "audit" tag means

Every report opens with one sentence answering the question you actually have, before any counts. It is derived from the severity mix and the rule classes, never written by hand, and it changes with how you invoked the scan: --repo asks whether this code can be trusted, --path asks whether it is ready to ship.

Findings tagged [audit] come from semgrep's audit rule class. An audit rule reports a construct worth looking at; it has not established that anything is wrongexpress.security.audit.xss.direct-response-write fires on every res.send() of a variable, including ones whose value never leaves your program. A run that found only audit findings will never tell you to fix something before shipping, because nothing has asserted a defect; it will say they are worth a look, and where to look.

A zero-finding run says which engines ran and over how many files, rather than "clean". An engine that did not run cannot have found nothing.

Known gaps, so you are not surprised by them

Measured, not assumed:

  • SQL injection built with a template literal is missed. db.all(`SELECT * FROM orders WHERE id = ${req.params.id}`) produces no finding. The + form — "SELECT * FROM users WHERE id = '" + id + "'"is caught, by rules/js-sql-concat.yaml in this repository, which p/default misses. That rule requires + on purpose: without the requirement it flagged a correctly parameterised query whose table name is interpolated and validated against an allowlist, because semgrep's open-source taint analysis cannot see a guard like if (!ALLOWED.includes(t)) return. Zero false positives was worth the template-literal case. --ai covers it.
  • npm audit is skipped when a project has a package.json but no lockfile. A scanner should not write files into the tree you pointed it at, so run npm install --package-lock-only yourself if you want it covered. The one exception is --repo, where the tree is a shallow clone in /tmp that this process made and deletes: there the lockfile is generated for you, with --ignore-scripts, and the report says it happened.
  • semgrep's own .semgrepignore excludes test directories and untracked files when it is given a directory. glance-scanner passes explicit file paths instead, so what you asked to scan is what gets scanned, and it checks semgrep's own count of scanned files afterwards.
  • Neither engine reasons about intent. A parameterised query that merely looks like concatenation, or a test fixture that looks like a leaked key, will still be reported. --ai --filter-fp is the pass that drops those.
  • obfuscated_text fires on legitimate mixed-script identifiers. A non-English codebase that writes a Cyrillic or Greek word next to Latin characters is reported as a homoglyph attack. This is a false positive, not intended behaviour, and it is open as #41.

We have not measured a false-positive rate against a public benchmark, so this README does not quote one.

What you get with a key

Semantic analysis reads the code the way a reviewer would. It reasons about intent rather than shape, so it covers the gaps above — including SQL built with a template literal. It costs whatever your provider charges, which for a small repository is cents.

AI_API_KEY is your AI provider's key, not a Glance account. There is no Glance signup and nothing to pay us for. Get a key from Anthropic, OpenAI or OpenRouter, and note that an API key is billed separately from any chat subscription you may already have.

export AI_API_KEY="your provider key"
export AI_PROVIDER="anthropic"     # anthropic, openai, or openrouter

npx glance-scanner analyze --path . --ai

AI_PROVIDER defaults to anthropic, so if you're using Anthropic you can skip it. If you already have ANTHROPIC_API_KEY set, for instance from Claude Code, the scanner picks it up automatically and you can skip AI_API_KEY too.

Add --filter-fp and each finding gets a second pass that asks whether it is real before it reaches your report. Parameterised queries flagged as SQL injection, test fixtures flagged as leaked secrets, and the rest of the usual noise get dropped with a stated reason:

npx glance-scanner analyze --path . --ai --filter-fp

We have not measured the false-positive rate against a public benchmark, so this README does not quote one.

Scanning agent surfaces

Code is not the only thing an agent reads. It reads MCP server configs as configuration and skill files as instruction, and npm audit and semgrep read neither.

glance-scanner surfaces --root ~/.hermes --json
glance-scanner surfaces --inventory inventory.json --json

It reports four things about MCP entries — plain-HTTP transport to a non-loopback host, a literal secret held inline in a config, shell metacharacters that would actually be interpreted, and, at medium, an unpinned npx -y-style fetch-and-run — and four about prompt files: instruction-override phrasing, text hidden from a human reader but not from the parser, an instruction to send local data to a network destination, and a literal credential.

The ten category names, as surfaces --list-categories prints them:

unencrypted_transport     secret_in_config          command_injection_risk
unpinned_remote_exec      prompt_injection          hidden_instruction
exfiltration_instruction  credential_leak           fenced_directive
obfuscated_text

Build your map from --list-categories rather than from this list, which is a copy and can drift.

--root walks a directory and reads thirteen filenames — six MCP configs and seven prompt files:

| MCP configs | Prompt files | | --- | --- | | .mcp.json | SKILL.md | | mcp.json | AGENTS.md | | mcp_servers.json | CLAUDE.md | | claude_desktop_config.json | GEMINI.md | | settings.json | SYSTEM.md | | .claude.json | PROMPT.md | | | INSTRUCTIONS.md |

.claude.json is Claude Code's user-scope MCP config and lives at $HOME/.claude.json, a sibling of ~/.claude/ rather than inside it, so a scan of ~/.claude alone does not reach it.

Findings carry no matched text by default. Pass --evidence to see it. The default is set in the engine rather than left to the caller because the caller is sometimes an LLM prompt, and quoting an injection payload into an agent's context is delivering it.

Fenced code blocks are treated as documentation, so a security page can quote an attack without tripping the scanner reading it. A directive found inside a fence is not dropped: under the default balanced policy it is reported as fenced_directive at medium, and --policy strict reports it as written. Concealed characters are fence-immune under both, because a fence does nothing to hide a homoglyph.

A ninth and tenth check, fenced_directive and obfuscated_text, round out ten categories. obfuscated_text needs no phrase list: it fires on a zero-width character between two ASCII letters, a Cyrillic or Greek letter inside an otherwise Latin word, or a bidirectional control (Trojan Source, CVE-2021-42574). The mixed-script rule is currently too broad — see Known gaps below and #41.

Configuration is never read from the tree being scanned, and there is no off level.

Full rules, schema and limitations: src/surfaces/README.md.

The extra Python scanners

These are optional and separate from the default scan. Install them once and glance-scanner will call them and merge their findings, deduplicated.

npx glance-scanner install-tools

| Tool | Finds | Applies to | | --- | --- | --- | | detect-secrets | API keys, tokens, credentials | every file type | | bandit | command injection, unsafe file operations, hardcoded passwords | Python | | pylint | undefined names, unused imports, errors | Python | | pip-audit | dependencies with known vulnerabilities | requirements.txt, pyproject.toml |

Then add them to a scan:

npx glance-scanner analyze --path . --with-all-checks

Or one at a time with --with-secrets, --with-bandit, --with-linting, --with-dependencies. These need Python on your machine. Nothing else here does.

Every flag

One table per subcommand, taken from --help on the published binary. Measured against 1.5.5.

surfaces

| Flag | What it does | | --- | --- | | --inventory <path> | inventory JSON produced by a platform adapter | | --root <dir> | discover surfaces under a directory instead | | --json | emit the report as JSON | | --evidence | include matched text on each finding; off by default | | --list-categories | print the categories this engine can emit, as JSON, and exit | | --policy <level> | balanced (default) or strict; there is no off level |

analyze

| Flag | What it does | | --- | --- | | --file <path> | scan one file | | --path <dir> | scan a directory, recursively | | --repo <url> | clone a repository to a temporary directory, scan it, delete it | | --ai | semantic analysis, needs AI_API_KEY (or ANTHROPIC_API_KEY) | | --semantic | alias for --ai | | --semantic-only | deprecated alias for --ai | | --filter-fp | second pass that drops findings it judges unreal, needs --ai | | --with-secrets | add detect-secrets | | --with-bandit | add bandit | | --with-linting | add pylint | | --with-dependencies | add pip-audit | | --with-all-checks | add all four | | --json | machine-readable report on stdout | | -v, --verbose | print the code around each finding | | --no-cache | rescan even if the content has not changed |

install-tools

| Flag | What it does | | --- | --- | | --semgrep | install semgrep only (powers the default no-API-key scan) | | --secrets | install detect-secrets only | | --bandit | install bandit only | | --linting | install pylint only | | --dependencies | install pip-audit only |

-V, --version and -h, --help work at the top level and on every subcommand.

Caching applies to --ai only, where it is keyed by file content so scanning the same code twice costs nothing. The cache is a JSON file in your home directory and --no-cache skips it. The static engines are not cached: they are local and fast, and a stale security result is worth less than the seconds it saves.

Exit codes

Measured on 1.5.5, surfaces:

| run | exit | | --- | --- | | clean | 0 | | info only | 0 | | medium only | 0 | | high or critical | 1 | | bad --root | 2 |

A CI check on $? therefore treats info and medium as clean. If you want those to fail a build, read the JSON rather than the exit code.

Exit 2 on a root that does not exist is deliberate (since 1.5.2): a scan that looked at nothing must never report clean. It is distinct from 1 so a pipeline can tell "found something" from "could not run".

This contract is under review; if it changes it will be a documented breaking change.

What it looks for

sql_injection, command_injection, path_traversal, hardcoded_secrets, xxe_attack, xss, csrf, insecure_deserialization, weak_crypto, missing_auth, hardcoded_config, unvalidated_redirect, information_disclosure, insecure_random, unsafe_pickle, vulnerable_dependency.

semgrep rules that do not map onto one of these keep their own rule id as the category rather than being forced into a bucket they do not fit. Coverage of each depends on the mode you ran in. See the two sections above.

Building from source

You do not need this to use the scanner. npx is enough.

git clone https://github.com/golem-labs-etc/agent-security-scanner.git
cd agent-security-scanner
npm install
npm run build
node dist/cli.js analyze --path .
npm test

Reporting something

Bugs and false positives both go to the issue tracker, or to [email protected]. If the scanner flagged your project and it was wrong, we want the case, because that is the part that is hard to get right.

License and scope

The scanner in this repository is MIT licensed. Use it, fork it, ship it, sell it. That is deliberate: a tool that claims to run locally and upload nothing is only worth believing if you can read it.

Glance guard and Glance fixes are not covered by this licence. They are separate products and no part of them lives in this repository. Glance guard is free for one developer on your own model key, which is a price, not a licence. Nothing here grants a right to their source.

MIT. Golem Labs.