Security
What Leaves the Computer
Effective October 21, 2026. Version 1.
Vigilance needs package names and versions to do its job. This page lists every field it sends from a computer, where each field goes, and how long we keep it.
A test in our code fails the build if a request gets a field that is not on this page. Our server also refuses any request with a field it does not know.
1. The Short Version
- Each computer talks to one Vigilance host:
check.vigihq.com, on port 443. - Most lookups send a 4-character code, not a package name. Dozens of package names share each code. A bucket is the signed file that lists every public package name with one code, and the verdicts we hold for them.
- Our library is the list of verdicts we hold about public package versions. Each edition of it has a number.
- The first look is a single read of the package lists in your code folders, at install. It counts the packages that were already on the computer.
- A package name and version leave the computer only when the package is public and nobody has checked that version yet.
- Each computer has a random device id and a two-word name, such as "blue otter". Its credential goes with three kinds of request only: a daily check of your plan, the counts, and a same-day event.
- The counts arrive for each computer. Your report shows each computer by its two-word name. No request names a person, an app or a project.
- Different packages are counted with count codes. A count code is made from a secret for your company, so we can count different packages without knowing their names. We do not look at the codes. Every use of that secret writes an audit record.
- We write no log line with your IP address or a device credential.
2. The One Host
Vigilance on each computer talks to one Vigilance address: check.vigihq.com,
TCP port 443, over HTTPS. Requests go to Amazon CloudFront, and from there to our servers
in AWS Canada (Central).
Vigilance never opens a port to the network. It listens on the computer's own loopback
address, 127.0.0.1, so npm, pnpm, yarn, bun, pip and uv on that computer can
use it.
The computer still talks to the package registries, as it did before Vigilance:
registry.npmjs.org, pypi.org and
files.pythonhosted.org, or your company's own registry. Package files come
from those registries unchanged. Vigilance does not host or change package files.
If an install command names another package server, Vigilance sends that command through its check too, with that server as the source. Nothing is stopped. The address of that server stays on the computer.
Only with the optional Claude plugin: Claude Code fetches the Vigilance plugin from
check.vigihq.com too, as it fetches any Claude Code plugin (request 9).
3. Every Request
Every request from Vigilance carries Host: check.vigihq.com and
User-Agent: vigi-seat. The user agent is a fixed text. It has no version
number. Request 9 comes from Claude Code itself, with its own headers.
| # | Request | When | What it sends | Credential | Where it goes | What we keep, and for how long |
|---|---|---|---|---|---|---|
| 1 | Bucket lookupGET /v1/b/{eco}/{prefix}?e={epoch} |
The first time the computer meets a package name, then at most every 10 minutes for that name | The ecosystem, which is the package registry (npm or pypi). A 4-character code: the first 4 characters of the SHA-256 of the ecosystem and the name. The library edition number (e), which names the version of our verdict list. It is the same for every computer. The headers Accept, Accept-Encoding and If-None-Match (a tag of the copy the computer already holds) |
No | CloudFront, then Amazon S3 | Nothing. Access logs are off |
| 2 | Control fileGET /v1/control |
At login, then every 5 minutes while Vigilance runs | Nothing | No | CloudFront, then Amazon S3 | Nothing |
| 3 | Signing keysGET /v1/keys |
At login, once a day, and when the control file says new keys exist | Nothing | No | CloudFront, then Amazon S3 | Nothing |
| 4 | Version checkPOST /v1/check |
When an install meets a public package version that has no verdict yet | The header Content-Type. The ecosystem, the package name and the version, for 1 to 32 versions. How many seconds to wait for an answer (0 to 20). The format name |
No | CloudFront, then AWS API Gateway, AWS Lambda and a work queue. CloudFront removes the credential header on this path | The work item: at most 1 day. A server log line with the ecosystem, name and version: 14 days. The name joins our list of packages to watch for new versions for 90 days. That list names no company. The verdict is a fact about a public package. It names no company, and we keep it while the library runs |
| 5 | Plan checkGET /v1/entitle |
Once a day, and at login when the last answer is older than a day | The device credential, in the X-Vigi-Device header. Nothing else |
Yes | CloudFront, then AWS API Gateway and AWS Lambda | We match the credential by its hash. We never store the credential. We add 1 to a count of plan checks for your company for that day, kept 90 days. Each answer also marks the computer as seen that day |
| 6 | CountsPOST /v1/counts |
Once a month, at the first login on or after the 1st. Once on day 14 of the free watch. Once when the first look ends | The device credential, and the counts payload of section 4 | Yes | CloudFront, then AWS API Gateway and AWS Lambda | Added into one total for your company for that period, and kept for each computer as section 4 says. Kept 2 years after the period ends, then deleted. The random send number sits in a separate record with no counts, and is deleted 60 days after the period ends |
| 7 | Program downloadGET /dl/seat/{version}/... |
When Vigilance installs or updates itself | The program version, the operating system and the chip type, in the path | No | CloudFront, then Amazon S3 | Nothing |
| 8 | Network checkGET /v1/ping |
Only when someone runs netcheck. It also makes requests 1, 2 and 3 once |
Nothing | No | CloudFront, then Amazon S3 | Nothing |
| 9 | Plugin downloadGET /dl/plugin/...Only with the optional Claude plugin |
When Claude Code installs or updates the Vigilance plugin | The plugin version, in the path. Claude Code's own request headers | No | CloudFront, then Amazon S3 | Nothing. We do not log it |
| 10 | Every request above | Every request | The computer's IP address and the TLS handshake. Every connection on the internet carries these | n/a | CloudFront, AWS WAF, AWS API Gateway | Never written to a log. Our rate limit holds the address in memory for 5 minutes, then forgets it. Our code deletes the address from each request before it does anything else |
| 11 | List of versions found harmful laterGET /v1/newly-harmful/recentGET /v1/newly-harmful/full |
Once a day. A computer that was off for more than 30 days reads the full list once | Nothing. recent or full is in the path |
No | CloudFront, then Amazon S3 | Nothing |
| 12 | JoinPOST /v1/join |
Once per computer, when a person installs with a join link | The headers Content-Type and User-Agent. The format name, the join code, the operating system name (darwin or windows), the chip type (arm64 or amd64), the program version, and a yes or no: did the person say this is their own computer. If an earlier join on this computer got a group id from our server, the same id (computer_group), so two accounts on one computer count as one. No host name, no user name, no hardware id |
No. The join code only | CloudFront, then AWS API Gateway and AWS Lambda | The device record of section 7. We keep only a keyed hash of the join code. The code expires after 30 days. A server log line with no code in it: 14 days |
| 13 | Join previewPOST /v1/join/preview |
When a person opens a join link, so they see their company name before they download | The format name and the join code. The browser's own headers | No. The join code only | CloudFront, then AWS API Gateway and AWS Lambda | A server log line with no code in it: 14 days |
| 14 | Same-day eventsPOST /v1/events |
When something needs an email the same day. At most 20 a day for each computer | The device credential, the event kind, the time, and the fields of section 3.1. Nothing else | Yes | CloudFront, then AWS API Gateway and AWS Lambda | The finding or gap email it causes. A flagged package version is kept for your company as section 7 says |
| 15 | Installer downloadGET /dl/win/Vigilance-{code}.exeGET /dl/mac/install.sh |
When a person downloads the installer from the join page | On Windows, the join code is in the file name in the path. The Mac path carries nothing | No | CloudFront, then Amazon S3 | Nothing. The access log drops the path |
3.1 Same-day events
A computer sends an event only for the kinds below. Each event carries its kind and the time. The extra fields are the whole list.
| Kind | Extra fields | What we do |
|---|---|---|
attack_installed | Ecosystem, name, version | In watch mode, a known attack went ahead. We check the finding first. Then we email the owner, naming the computer and the first steps |
retro_match | Ecosystem, name, version | The later-found check matched a version on the list of request 11 |
routing_fallback | The package manager, and a cause | Routing for that manager fell back to its normal registry. The cause is one of daemon_down, port_taken, probe_failed, credential_upstream or binary_changed. binary_changed means the program file no longer matched its signed list, so Vigilance turned routing off and did not run it |
routing_restored | The package manager, and the minutes | Routing came back. The gap email gives the minutes |
filter_fallback | The package manager, and a cause | Three redirects in a row for that manager. The cause is proxy, tls or auth |
tool_uncovered | A count | How many AI tools Vigilance found that it cannot cover. No tool name |
left | None | The person ran the uninstaller |
binary_gone | None | A small job of the operating system found the Vigilance program gone and its folder still there, most often taken by antivirus. It sends this once, puts the old package settings back and removes itself. The next quiet-computer email tells the owner that installs on that computer go ahead unchecked |
An email names a computer by its two-word name only. We send at most one email for each company, for each kind, each day.
3.2 When a package name leaves the computer
A package name and version leave the computer in request 4 only. All three of these must be true:
- The bucket for its code lists the name as a public package.
- That version has no verdict yet.
- The public registry lists that version.
A name that the bucket does not list is private or unknown. Vigilance sends nothing more about it, to any address, ever. It counts the name as "not public". A package scope that your npm settings send to your own registry never reaches Vigilance.
3.3 Sample: a bucket lookup
GET /v1/b/npm/8d1d?e=41 HTTP/2
Host: check.vigihq.com
User-Agent: vigi-seat
Accept: application/json
Accept-Encoding: gzip, br
8d1d is the code for npm:@vigihq/canary. The answer is a signed
file that lists every public name with that code, and the verdicts we hold for them. The
computer picks out the name it needs. We never learn which name it was.
For a package with no version older than 90 days, the answer can also name an established package that does the same job, and its checked version. That part comes from us to the computer. Nothing new leaves the computer.
3.4 Sample: a version check
POST /v1/check HTTP/2
Host: check.vigihq.com
User-Agent: vigi-seat
Content-Type: application/json
{"schema": "vigi.check-req/1", "wait_s": 20, "items": [{"eco": "npm", "name": "@vigihq/canary", "version": "2.0.0"}]}
3.5 Sample: the plan check
GET /v1/entitle HTTP/2
Host: check.vigihq.com
User-Agent: vigi-seat
X-Vigi-Device: dv_k2m7q4xw9pa3hn6tz5rbcydueq.<43 characters>
The device credential unlocks no data. It only tells the computer which mode your company runs in, and ties the counts to your company.
3.6 Sample: a join
POST /v1/join HTTP/2
Host: check.vigihq.com
User-Agent: vigi-seat
Content-Type: application/json
{"schema": "vigi.join-req/1", "code": "7KQ2M4PX9TRB", "os": "darwin", "arch": "arm64", "client_version": "1.0.3", "personal": false}
The answer holds your company name, a random device id, the device credential and the two-word name. Our server makes the name. The computer never sends it. If a computer already reports to another company, it refuses a second join on its own, with no call.
3.7 Sample: a same-day event
POST /v1/events HTTP/2
Host: check.vigihq.com
User-Agent: vigi-seat
X-Vigi-Device: dv_k2m7q4xw9pa3hn6tz5rbcydueq.<43 characters>
Content-Type: application/json
{"schema": "vigi.event/1", "kind": "attack_installed", "at": "2026-11-05T15:20:00Z", "eco": "npm", "name": "csv-tidy", "version": "4.1.0"}
4. The Monthly Counts
Vigilance counts on the computer. Once a month it sends the totals for the month before. During the free watch it also sends the totals on day 14. When the first look ends, it sends one more set. This is the whole payload. Our server refuses a payload with any other field.
Sample. The numbers are made up.
{
"schema": "vigi.counts/1",
"kind": "month",
"period": "2026-11",
"send_id": "BMyr7kOWOjdzuKsWS2XsuA",
"part": 1,
"parts": 1,
"client_version": "1.0.3",
"epochs": [41, 42],
"modes": {
"watch": {
"installs": 38, "versions_seen": 400, "versions_checked": 371,
"served_older_held": 2, "served_older_unchecked": 5, "pins_blocked": 1,
"swaps_asked": 0, "swaps_yes": 0, "swaps_no": 0, "swaps_applied": 0,
"smoke_pass": 0, "smoke_fail": 0, "no_safe_version": 0,
"unchecked_new": 3, "unchecked_old": 26, "unchecked_outage": 0, "unverified_commands": 0,
"checks_requested": 9, "not_public": 4, "not_covered": 2, "bypassed": 1,
"rerouted": 1, "alt_offered": 1, "alt_yes": 0, "alt_no": 0,
"client_too_old": 0, "drill_pass": 1, "drill_fail": 0,
"held_no_answer": 0, "released_holds": 0, "routing_fallback": 0,
"tool_added": 0, "hooks_disabled_by_project": 0, "other_client": 0,
"target_refused_upstream": 0, "source_mismatch": 0, "hook_off": 0
}
},
"held_met": [
{"eco": "npm", "name": "@vigihq/canary", "version": "2.0.0", "first_met": "2026-11-05", "flag": null, "outcome": null}
],
"codes": {"seen": ["q3T1v0bZ8yJ2mP4kXw9a6g", "..."], "checked": ["q3T1v0bZ8yJ2mP4kXw9a6g", "..."]},
"tools": ["claude_code", "cursor"],
"waiting_projects": null,
"whole_computer": null
}
| Field | Meaning |
|---|---|
schema | The format name |
kind | month, watch or first_look |
period | The month (2026-11), or the start date of the watch |
send_id | A random number made fresh for each send. It stops a resend from being counted twice. The computer forgets it after the send. We never store it next to a count, so it links nothing across months |
part, parts | A large send can split into up to 64 parts. Each part says which one it is |
client_version | The Vigilance program version |
epochs | The library edition numbers the computer used that period |
modes | One block of counters for each mode that ran that period: watch, paused, ask_first or auto |
held_met | Each public package version that Vigilance held and that this computer met that period. Ecosystem, name, version, the day it first met it, a flag (found_at_install, found_after_install or empty) and the swap outcome (asked, accepted, declined, no_answer, smoke_failed, target_refused_upstream, swapped or empty). No count, no project. At most 500. A name that is not public never appears |
codes | The count codes. seen holds one code for each different public version that Vigilance read, or that the first look found. checked holds the codes of those that met a verdict. A code is the first 16 bytes of an HMAC-SHA256 of the version, made with a key for that period. A name that is not public never gets a code |
tools | The AI tools found on the computer that period, from a fixed list of six: claude_code, cursor, codex, copilot, devin_desktop and gemini_cli. A tool that is not on the list is never named |
waiting_projects | With ask-first or automatic swaps only: how many project folders on this computer still wait on a held version at the end of the period. A number. No folder name |
whole_computer | Empty, except with the whole-computer watch. Section 5 lists its fields |
4.1 The counters
An "event" is one package version that a covered package manager downloaded, or tried to download, in one install command. The same event in one command counts once. In watch and paused mode, a counter whose row starts with "(would)" counts what Vigilance does when swaps are on. A held version is a version that Vigilance does not let in as it stands. Every mode block holds all 37 counters, zero included. Some rows list two or three counters.
| Counter | Counts |
|---|---|
installs | Install commands that people or AI tools ran with npm, pnpm, npx, yarn, bun, pip or uv |
versions_seen | Events for public packages that Vigilance read. It equals versions_checked plus unchecked_new plus unchecked_old |
versions_checked | Events that met a verdict |
served_older_held | (would) Events where an older allowed version was used because a newer one was held |
served_older_unchecked | Events where an older allowed version was used because a newer one had no verdict yet |
pins_blocked | (would) Events where a pinned version was held |
swaps_asked, swaps_yes, swaps_no | Ask-first questions shown to the person, and the answers. "Not now" counts as no. Always 0 in watch and paused mode |
held_no_answer | Ask-first questions with no answer before the time ran out. The install stays held |
swaps_applied | Pinned versions changed in a project after a test install passed. Always 0 in watch and paused mode |
smoke_pass, smoke_fail | Test installs, by result |
target_refused_upstream | Test installs that did not run, because your company's own package source refused the safe version. Nothing changes in the project |
no_safe_version | (would) Events held with no safe version of the same package |
unchecked_new | Events that went ahead because no verdict came back in time |
unchecked_old | Events for versions published before the first trusted version of the package |
source_mismatch | Events where the file that the package server sent is not the file we checked for that version. The event gets no verdict, so it also counts under checks_requested, then unchecked_new or served_older_unchecked. Counted in every mode |
unchecked_outage | Events that went ahead because our host did not answer, a signature check failed, the library had no current edition, or Vigilance was not running |
unverified_commands | Commands for a supported package manager that went ahead with no Vigilance program to check them. The number of packages in them is unknown |
checks_requested | Versions sent in request 4 |
not_public | Different names that Vigilance did not look up as public: private names, and names not on the public list. Only the count leaves |
not_covered | Events from package managers Vigilance does not cover yet: conda, Poetry, and any manager our tests show cannot route |
bypassed | Events found in a project's package list that did not pass this computer's checks, for example a cache hit or a list written by a cloud agent. It never blames a person |
rerouted | Commands that named another package server and that Vigilance sent through its check with that server as the source. Nothing is stopped. Counted in every mode |
released_holds | Events used after our control file released a hold that a failure in our checks had set. Always 0 in watch mode |
routing_fallback | Times Vigilance put the old package settings back because its program did not answer |
tool_added | AI tools that got the Vigilance hook that period |
hook_off | AI tools whose Vigilance hook a person turned off with vigi-seat hooks off, once for each tool in the period. vigi-seat hooks on turns it back on. Counted in every mode |
hooks_disabled_by_project | Commands in a project whose files turn off user hooks. Vigilance still checks them |
other_client | Requests from a program that is not a supported package manager. Vigilance passes them on unchanged |
alt_offered | (would) Installs of a package with no version older than 90 days, where an established package does the same job |
alt_yes, alt_no | Answers to that offer. Always 0 in watch and paused mode |
client_too_old | Events that went ahead because the Vigilance program was too old |
drill_pass, drill_fail | (would) Installs of our test package, @vigihq/canary or vigihq-canary, by result |
On our side, we keep the counts of each computer for each period, under its random device id and its two-word name, so your report can show a number for each computer. We add them into one total for your company. The number of different packages comes from the count codes of every computer, counted once. Your company can link a computer name to a person. We cannot.
5. The Whole-Computer Watch
On Done For You, Vigilance also checks the apps installed on each computer. This is the
one place where an app name leaves the computer. The counts payload then fills the
whole_computer field:
Sample. The numbers and names are made up.
"whole_computer": {
"apps_seen": 212,
"apps_checked": 198,
"flagged": [
{"app": "Example Editor", "version": "4.2.1", "powers": ["fetch", "shell-pipe"]}
]
}
apps_seen and apps_checked are numbers. flagged holds
at most 50 items, one for each app whose update gained a power. An item holds the app's
public product name, its version and the powers it gained. No file name, no path and no
other app name leaves the computer.
We keep the two numbers and the flagged items for each computer, under its two-word name. Your report shows them for each computer.
6. What Never Leaves
Vigilance never sends:
- Code.
- Files.
- File names.
- Paths.
- Project names.
- Folder names.
- App names, except the flagged apps of section 5.
- Staff names.
- User names.
- Host names and machine names.
- Hardware ids.
- Install ids.
- Lockfiles. The first look reads them on the computer and sends only public package names and versions.
- Private package names.
- Error reports.
- The address of any package server that Vigilance routes through its check.
- The program version, except in the counts and the join.
- The name of any AI tool that is not on the fixed list of section 4.
Each computer sends a random device id in place of an install id. It names no person and no machine. The two-word computer name is made by our server. The computer never sends it.
Nothing outside the table in section 3 leaves the computer. That includes your prompts and the answers of your AI tools.
7. What We Keep About Your Company
This data comes from you, from Stripe and from the join of each computer.
| Data | Why | Kept |
|---|---|---|
| Company name, and the email address for readouts and receipts | To send the readout and the receipts | While you are a customer, then 2 years, then deleted |
| The hash of each device credential. We never store the credential itself | To check each computer | While you are a customer, then 2 years, then deleted |
| One record for each joined computer: device id, two-word name, operating system, chip type, program version, join date, the days it checked in, and whether the person said it is their own computer | The owner page, gap emails, counts for each computer, and billing | While the computer is joined, then 90 days |
| Your company secret for count codes, encrypted with a key that only the join service can use | To count different packages | Deleted on the day your company leaves |
| Flagged package versions and swap outcomes, linked to your company by name | To review false alarms and tune swaps | While you are a customer. 90 days after you leave, we remove the link to your company |
| Plan, mode, the number of computers you told us, computers billed, watch dates, paid-through date | To answer the plan check | While you are a customer, then 2 years, then deleted |
| Stripe customer, saved card reference and subscription ids. Stripe holds the card itself | To charge only after your yes, and to match payments | While you are a customer, then 2 years. Stripe keeps its own records under its own policy |
| Counts for each computer and each period | The readout and the receipts | 2 years after the period ends |
| Readouts and receipts (signed JSON and PDF) | Your record | 2 years |
| Your watch sign-up: name, work email, company, AI tools in use, number of computers, the record of your Terms tick | To set up the watch | Until you ask us to delete it. The Terms tick record stays as long as the account, then 2 years |
The readout and every receipt hold counts and public package versions. They show each computer by its two-word name only. They name no person, app, project, path or IP address. Our signing service refuses a report with any other field.
8. Where the Data Lives
- Every store is in AWS Canada (Central), region
ca-central-1. - Amazon CloudFront serves the host from locations around the world. It passes requests through and keeps copies of the signed public files, which are the same for everyone. Its access logs are off.
- The rate-limit rules run at the edge. They keep no record.
- Server logs hold the request id, the route, the status, the time taken and the error type. A version-check log line also holds the public package name and version. A plan-check or counts line holds your company id only when our server fails with a 5xx error. Logs are kept 14 days.
- An AI model never reads anything from your computers. The verdict each computer reads comes from our own checks, run on the public package files.
- Requests pass through Amazon CloudFront servers in other countries, mostly the United States, on their way to Canada. Payments run on Stripe, in the United States and other countries. Email runs on Google, in the United States and other countries. The authorities of those countries can have access to data held there.
9. Check It Yourself
You do not need to trust this page. Three ways to check it:
1. Watch the wire. On a computer with Vigilance on, run:
vigi-seat wire
It prints each request to check.vigihq.com before it goes, until you press
Ctrl-C. Install a package in another window and read what it prints. To see the requests
of one command only, run:
vigi-seat --show-wire npm install lodash
The device credential shows as dots and its last 4 characters.
2. Test the network path. Run:
vigi-seat netcheck
The last line says PASS when the computer reaches
check.vigihq.com on port 443 and gets signed answers back. Otherwise it says
FAIL and names the step that failed. The installer result screen runs the same check.
3. Read your firewall log. The only Vigilance host in it is
check.vigihq.com.
10. What Vigilance Cannot See
Vigilance does not see everything. We list the gaps on every readout and receipt.
- Apps built only in a cloud workspace, such as cloud agents.
- Apps built on personal AI accounts on computers that did not join.
- Computers that did not join. The readout lists each joined computer by name, so a missing one stands out.
- Package versions with no verdict yet. The readout counts them as unchecked.
- Package managers it does not cover: cargo, go, gem, Homebrew, conda and Poetry, and scripts piped to a shell. These installs pass unchecked.
- Project or environment settings that point a package manager away from Vigilance.
- Linux computers. Vigilance runs on Mac and Windows.
- Who installed a package, and in which app. We cannot tell you, because we never get that data.
When a gap opens on a computer, we email you the same day. A computer that stops checking for 3 days gets an email too.
On a readout, no report means we do not know. A report with zero events means none were seen. Repeated package events are not different packages.
11. Changes to This Page
A new field needs a new version of our public request format and a new version of this page, first. We change this page before any computer sends the new field. Each version keeps its date. We keep every old version. Email support@vigihq.com for a copy.
For the rest of our security work, read Security at Vigilance. For what the website collects, read the Privacy Policy.
Modul4r Solutions, 18207 61 Ave NW, Edmonton, AB T6M 1T6, Canada