
Security News
Re-Enabled GitHub Actions Expose Thousands of Repositories to Mini Shai-Hulud
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.
@zhiliangtech/agentgate
Advanced tools
Control plane for the tools agents run. Inventory, evidence, policy, enforcement. Clean is only emitted when every check ran.
A control plane for the tools agents run. Inventory what exists, state the evidence behind it, decide what is allowed, and enforce that decision in CI and at runtime.
The reason it exists is one rule, and everything here follows from it:
A verdict of
cleanis only emitted when every check ran. Anything that could not be measured isunmeasured, and an artefact with an unmeasured part isincomplete, neverclean.
A security tool's worst failure is a green build for work nobody did. This project is built so that cannot happen.
| part | what it does | package |
|---|---|---|
| inventory | enumerate the registry, resolve packages, fetch repositories | packages/collect |
| evidence | join it into one record per server, with the bytes behind every claim | packages/collect |
| policy | scan configs, hooks, manifests and source for what a company would refuse | packages/guard |
| verification | check a claim against something outside the claim | packages/verify |
Deployment instructions are in docs/operations/deployment-runbook.md. docs/verification.md records the first deployment on 2026-09-16, along with what was checked and what remains unverified.
Clean is a claim about work that was done — five failures, four of them mine, and what the tool does about them. The short version: ask a scanner what it did not do.
The service is running at https://app.xn--5kvo87g.com/ — the landing page, the pricing page, the browseable evidence index (rebuilt daily) and the API under the same host. The personal site that was already on that machine is untouched.
https://ciceroyang.github.io/agentgate/ — the landing page. The evidence index is one browsable page at https://ciceroyang.github.io/agentgate/evidence.html, rebuilt daily from the live registry: records are embedded, filtering is local, and there is nothing to sign up for. Pricing is at /pricing.html, and /try.html walks through using it in ten minutes.
Node 20 or newer, no dependencies. A repository clone already carries a sample index,
so the service answers immediately; refresh replaces it with a current one.
node bin/agentgate.mjs serve
# agentgate serving http://127.0.0.1:8080
curl -s localhost:8080/health
curl -s localhost:8080/v1/index/summary
curl -s localhost:8080/v1/servers/<name>
curl -s localhost:8080/badge/<name>.svg
It is packaged for npm as @zhiliangtech/agentgate. If npx answers 404 the first publish has not
happened yet — docs/operations/publish-checklist.md is that
one-time setup, and until then the clone above is the way to run it.
npx @zhiliangtech/agentgate check --root .
npx @zhiliangtech/agentgate serve
With no policy file present check uses a built-in default that refuses nothing extra, and
serve answers from the snapshot the package was published with. refresh always writes to
./data beside you, never inside the installed package.
Or with docker, which runs the same command in a container:
docker compose up # the service on :8080
docker compose --profile collect run --rm refresh # rebuild data/index.json and seed the first snapshot
Start node bin/agentgate.mjs serve and open /inventory.html on the printed local
address. Paste a tool-name list or choose a text/JSON file, resolve ambiguous matches,
enter the version you actually use, and download a standalone HTML evidence report.
The page compares the list in browser memory against its embedded index snapshot:
it does not upload the list, store it, scan your machine, or execute tools.
For the same workflow without a browser:
node bin/agentgate.mjs inventory --input examples/inventory/tools.json --out my-tools.html
node bin/agentgate.mjs inventory --input tools.json --index data/index.json --format json
An input can be one name per line, a JSON array, or { "tools": [...] }. Each object
accepts only name, server, package, registry, and version; complete client
configurations and credentials are deliberately not accepted. See the
inventory input and report guide.
Unmatched, ambiguous, missing-version, different-version, and incomplete-evidence items stay in the report. A matching version is not proof of what is installed. The committed sample is explicitly historical and cannot provide a confirmed match; neither can old evidence without an exact content binding. Even a confirmed evidence match is not a safety certification or a new scan. Review the checked scopes, findings, snapshot date, and gaps before deciding what to use.
The command exits zero when it produces a report, not when all tools pass; malformed
input or unreadable data exits 2. --out refuses to overwrite an existing file.
Use check, not inventory, for policy enforcement in CI.
A policy says what the company refuses. It is data, not code, and it is specified: see docs/spec/policy-v1.md.
{
"version": "agentgate.policy/v1",
"threshold": "high",
"required": { "pinnedPackages": true, "measuredEvidence": ["packageManifest"] },
"forbidden": { "rules": ["AG-INSTALL-001"], "servers": ["internal/*"] }
}
node bin/agentgate.mjs check --policy agentgate.policy.json --root .
With no policy file and no --policy, the check still runs: it reports what the checks
found and says it is using the built-in default, which refuses nothing extra. Inventing
obligations on the user's behalf would make the result mean less, not more. A policy named
explicitly that cannot be read is still an error, because that is a typo.
The same evaluation can be handed to a person rather than a terminal:
node bin/agentgate.mjs check --policy agentgate.policy.json --root . --format html --out report.html
One static file, printable, no script. Anything that could not be measured gets its own section above the findings, because a report that buries what it did not check reads as more complete than it is. That file is the deliverable of the free checkup.
Three outcomes, and incomplete outranks findings: if a check failed to run, or an
evidence block the policy requires is unmeasured, the exit code is 2 however clean
the findings look. No threshold can turn a partial answer into a pass.
| exit | meaning |
|---|---|
| 0 | clean |
| 1 | findings |
| 2 | incomplete |
A pull request that adds something the policy refuses does not merge, and the reason is in the pull request rather than in a log nobody opens.
- uses: ciceroyang/agentgate@main
with:
policy: agentgate.policy.json
See examples/github-actions/policy.yml. The action runs the check, writes SARIF for code scanning, comments the human report on the pull request, and then exits with the check's own code, so an incomplete scan still fails the build at 2.
The same policy applies to what has already shipped, by putting a gateway in front of the server instead of pointing the client at it:
node bin/agentgate.mjs proxy --policy agentgate.policy.json --log calls.jsonl -- \
npx -y @modelcontextprotocol/server-filesystem /data
A tool call the policy refuses is answered locally with a reason and never reaches the server; a forbidden tool is removed from the advertised list so a client cannot ask for it at all. Every decision, allowed or refused, is appended to the log, because the log is what an audit reads.
The index is kept, so two builds can be compared, and the interesting column is the last one: changes that a release would have explained and did not.
node bin/agentgate.mjs diff --from previous-index.json --to data/index.json
added: 0
removed: 0
verdict changed: 1
package changed: 0
silent (no version move, different evidence): 1
A new finding on an unchanged version is the shape of a package replaced without a release, a repository edited in place, or a scan that has started seeing something. Nobody can back-fill that record; it only exists if someone kept looking.
node packages/collect/mcp-audit.mjs --max 6000 --out data/census.json
node packages/collect/scripts/guard-scan.mjs --census data/census.json --out data/guard-scan.json
node packages/collect/scripts/build-index.mjs --census data/census.json --guard data/guard-scan.json --out data/index.json
And the scanner on a local project:
node packages/guard/bin/agent-guard.mjs . --fail-on high
node packages/collect/bin/agent-add.mjs --index data/index.json <server-name>
npm test
node scripts/bench.mjs 50000 200 # lookups must stay under 10 ms p50
node scripts/measure-verify.mjs # claim extraction, against a small labelled set
npm test # the whole suite; it prints how many ran
node packages/guard/scripts/regression.mjs # benign must stay silent, positives must fire
packages/guard the scanner: engine, eight checks, CLI, corpus, GitHub Action
packages/collect census, package and repository scanning, the evidence index
packages/policy policy evaluation and human-readable reports
packages/gateway runtime policy enforcement for MCP servers over stdio
packages/history index snapshots and change comparisons
packages/service the read-only evidence API
packages/verify cross-model claim checking
docs/ architecture and product notes
Beyond the tests, which are written by the same party as the code, docs/verification.md records the checks against things nobody here wrote: a real MCP server through the gateway, and the list of what is still unverified.
node scripts/verify-real-server.mjs
To check whether the index's high and critical findings still match recorded human
reviews, run node scripts/review-criticals.mjs (the command keeps its original name).
Each review must bind the finding's identity and evidence to an exact package version
and complete scanned-content provenance, including its SHA-256 digest and scope.
Missing or changed bindings require another human review. Legacy approval records are
not automatically upgraded. --accept records a completed human review and refuses
incomplete provenance; it does not perform the review or certify third-party code.
--apply.This is an early open-source core. It includes collection, an evidence index, scanning, policy checks in CI, a runtime gateway for MCP servers over stdio, historical diffs and a read-only service. Deployment scripts and a runbook exist; the first server deployment and its checks are recorded in docs/verification.md. That record does not establish the current health of the hosted service, and the Docker image build remains unverified there.
The enterprise capabilities described in the pricing proposal — SSO/SAML, RBAC, multi-tenancy and signed audit export — are not implemented. Team and Enterprise prices are hypotheses that have not been validated with customers; the free pilot is intended to test that demand. See the product decisions and the pilot scope.
The scanner's suite includes the invariant that a crashed check can never produce
clean. Run npm test for the current results; this page does not repeat a test count.
AGPL-3.0-only. A commercial licence is available for the case the AGPL does not permit: offering a modified agentgate as a closed service without publishing your changes. See docs/product/licensing.md.
FAQs
Control plane for the tools agents run. Inventory, evidence, policy, enforcement. Clean is only emitted when every check ran.
The npm package @zhiliangtech/agentgate receives a total of 1,158 weekly downloads. As such, @zhiliangtech/agentgate popularity was classified as popular.
We found that @zhiliangtech/agentgate 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.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.

Research
/Security News
The compromise affects MemTensor's MemOS, an open source memory framework for large language models (LLMs) and AI agents. Both npm package @memtensor/memos-cloud-openclaw-plugin and the PyPI package MemoryOS are compromised. They drop cross-platform Go binaries that exfiltrate developer secrets.