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

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 lookup
GET /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 file
GET /v1/control
At login, then every 5 minutes while Vigilance runs Nothing No CloudFront, then Amazon S3 Nothing
3 Signing keys
GET /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 check
POST /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 check
GET /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 Counts
POST /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 download
GET /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 check
GET /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 download
GET /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 later
GET /v1/newly-harmful/recent
GET /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 Join
POST /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 preview
POST /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 events
POST /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 download
GET /dl/win/Vigilance-{code}.exe
GET /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.

KindExtra fieldsWhat we do
attack_installedEcosystem, name, versionIn 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_matchEcosystem, name, versionThe later-found check matched a version on the list of request 11
routing_fallbackThe package manager, and a causeRouting 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_restoredThe package manager, and the minutesRouting came back. The gap email gives the minutes
filter_fallbackThe package manager, and a causeThree redirects in a row for that manager. The cause is proxy, tls or auth
tool_uncoveredA countHow many AI tools Vigilance found that it cannot cover. No tool name
leftNoneThe person ran the uninstaller
binary_goneNoneA 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:

  1. The bucket for its code lists the name as a public package.
  2. That version has no verdict yet.
  3. 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

Request 1
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

Request 4
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

Request 5
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

Request 12
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

Request 14
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
}
FieldMeaning
schemaThe format name
kindmonth, watch or first_look
periodThe month (2026-11), or the start date of the watch
send_idA 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, partsA large send can split into up to 64 parts. Each part says which one it is
client_versionThe Vigilance program version
epochsThe library edition numbers the computer used that period
modesOne block of counters for each mode that ran that period: watch, paused, ask_first or auto
held_metEach 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
codesThe 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
toolsThe 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_projectsWith 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_computerEmpty, 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.

CounterCounts
installsInstall commands that people or AI tools ran with npm, pnpm, npx, yarn, bun, pip or uv
versions_seenEvents for public packages that Vigilance read. It equals versions_checked plus unchecked_new plus unchecked_old
versions_checkedEvents that met a verdict
served_older_held(would) Events where an older allowed version was used because a newer one was held
served_older_uncheckedEvents 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_noAsk-first questions shown to the person, and the answers. "Not now" counts as no. Always 0 in watch and paused mode
held_no_answerAsk-first questions with no answer before the time ran out. The install stays held
swaps_appliedPinned versions changed in a project after a test install passed. Always 0 in watch and paused mode
smoke_pass, smoke_failTest installs, by result
target_refused_upstreamTest 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_newEvents that went ahead because no verdict came back in time
unchecked_oldEvents for versions published before the first trusted version of the package
source_mismatchEvents 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_outageEvents 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_commandsCommands for a supported package manager that went ahead with no Vigilance program to check them. The number of packages in them is unknown
checks_requestedVersions sent in request 4
not_publicDifferent names that Vigilance did not look up as public: private names, and names not on the public list. Only the count leaves
not_coveredEvents from package managers Vigilance does not cover yet: conda, Poetry, and any manager our tests show cannot route
bypassedEvents 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
reroutedCommands 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_holdsEvents used after our control file released a hold that a failure in our checks had set. Always 0 in watch mode
routing_fallbackTimes Vigilance put the old package settings back because its program did not answer
tool_addedAI tools that got the Vigilance hook that period
hook_offAI 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_projectCommands in a project whose files turn off user hooks. Vigilance still checks them
other_clientRequests 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_noAnswers to that offer. Always 0 in watch and paused mode
client_too_oldEvents 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:

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.

DataWhyKept
Company name, and the email address for readouts and receiptsTo send the readout and the receiptsWhile you are a customer, then 2 years, then deleted
The hash of each device credential. We never store the credential itselfTo check each computerWhile 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 computerThe owner page, gap emails, counts for each computer, and billingWhile the computer is joined, then 90 days
Your company secret for count codes, encrypted with a key that only the join service can useTo count different packagesDeleted on the day your company leaves
Flagged package versions and swap outcomes, linked to your company by nameTo review false alarms and tune swapsWhile 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 dateTo answer the plan checkWhile you are a customer, then 2 years, then deleted
Stripe customer, saved card reference and subscription ids. Stripe holds the card itselfTo charge only after your yes, and to match paymentsWhile you are a customer, then 2 years. Stripe keeps its own records under its own policy
Counts for each computer and each periodThe readout and the receipts2 years after the period ends
Readouts and receipts (signed JSON and PDF)Your record2 years
Your watch sign-up: name, work email, company, AI tools in use, number of computers, the record of your Terms tickTo set up the watchUntil 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

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.

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