All three found testing the bootstrap installer on Windows.
- The published command used the irm-pipe-iex form, which Microsoft Defender
detects as Trojan:Win32/Commando.A!ml and kills before it runs, every time,
on a default install. The command now downloads the script to a file, runs
it, and removes it in a finally block, which Defender does not object to.
The UI also offers the script as a plain download for a tech who would
rather save it.
- Approving a request only linked the asset, so a PC whose pending reason was
"unknown hostname" - which is every PC installed this way - kept getting 403
on every call afterwards, because its computer record had no hostname to
match the credential against. Approval now stamps the hostname when the
record has none, refuses with a 409 naming both when the record has a
different one, and overwrites only when the admin confirms. The Credentials
tab offers that confirmation.
- A hostname that resolves to no record now says so instead of reading as an
impostor, while still refusing the request.
- While waiting for approval the agent retried on the 15-minute telemetry
cadence, so a PC approved immediately enrolled a quarter of an hour later
and the installer always ended on "still pending". It now retries about
every minute for the first ten minutes, then every five to thirty minutes,
then every fifteen, with jitter. A rate limit or server error still uses the
slower backoff.
An admin generates a short-lived token in Settings > Endpoints > Endpoints and
copies the command it produces. A tech runs that one line in an elevated
PowerShell on a PC with no agent:
powershell -ExecutionPolicy Bypass -c "irm https://<server>/shopdb/api/
endpoints/siteagent/bootstrap.ps1?t=<token> | iex"
The script refuses to run unelevated, refuses a PC that already has the agent
(a managed bay gets it from GE-Enforce), downloads the package the existing
builder produces, checks both binaries against the hashes the server baked in,
installs with NO shared key, and reports what happened. The PC then asks to
enroll and waits for an admin to approve it, which the script watches for
locally for two minutes before exiting.
- New endpointbootstraptokens table and migration
endpoints0024bootstraptokens: hash only, expiry, use count, revoke.
- Admin routes to generate, list and revoke tokens; the secret is shown once.
- Two token-only routes: the script, and the package (which spends a use).
Anything wrong with the token answers 404 rather than confirming it exists,
both are rate limited per address and both are audited with the caller.
- The package builder is now shared rather than duplicated, and a bootstrap
package can never carry a collector key.
Found in the Windows VM re-test.
- A CA-flagged server certificate (common with a plain openssl self-signed
cert) produced an installer package with no pin at all, so agent 1.20
refused the server. The package now always carries the SPKI pin of the
certificate PCs receive; the CA flag only skips the trusted-root import.
When an uploaded CA-flagged certificate differs from the server's own, both
pins are included. The build response carries X-ShopDB-Build-Warning and the
installer logs what older agents need.
- Pins written into appsettings.json by the installer were merged with the
registry, so removing a pin from HKLM never took effect. When the ServerPins
value exists in the registry it is now authoritative, even when empty;
appsettings pins apply only when it is absent. Set-ShopDBServerPins.ps1
-Clear deletes the value; new -None writes an empty value (no pins).
- A script or package download sent with a credential revoked moments earlier
got 401 CREDENTIAL_INVALID and failed the command. The agent now retries that
download once with the shared key and records the fallback in the result.
Found in the Windows VM re-test.
- A PC installed without the shared key got 401 from POST
/api/endpoints/enroll while the shared key was allowed, so no request was
recorded and no admin could approve it. Enroll now never refuses: a valid
shared key only decides auto-issue on a clean match; no key or an invalid
key creates or updates the one pending request for that hostname and
serial. An approved request still issues only to its own nonce.
- A PC blocked with "server certificate not trusted by this PC" could never be
queued again after trust was fixed, because the blocker comes from its last
failed upgrade. Naming the PC in a wave now queues it when trust is its only
blocker, and the response lists it under "retried"; "next n" still skips
it. The Migration table lets such a PC be selected.
- counts.pending on GET /enrollment counts pending requests, including ones
for unknown hostnames; counts.pendingcomputers keeps the per-computer figure.
- Endpoints (shared key) page: the key is presented as what PCs use until
they enroll, with enrollment progress, a link to Enrollment, rewritten
guidance, and rotate/revoke confirmations that say how many PCs are not
enrolled yet.
- Settings cards describe the server certificate as what PCs actually
receive, on IIS or Docker; Enrollment has its own icon (the icon set lacked
the one the Server Certificate card already asked for).
- Server Certificate page checks the certificate the site presents at its
public address and compares its pin with the certificate file, warning when
they differ. The host comes from site settings only; the check is a TLS
handshake, never an HTTP request.
- Fleet list: Credential column and filter (enrolled, pending, re-enroll
blocked, shared key, not reporting), computed in the list query.
- PC detail: credential state, prefix and dates for fleet readers, state only
for others, with admin Revoke (optionally blocking re-enrollment) and Allow
re-enrollment.
- Dashboard: pending enrollment requests, and PCs blocked from re-enrolling
for more than seven days.
- Runbook: where to find each of these in the app.
Found in an end-to-end test on a Windows 11 VM against a self-signed server.
- Rollback: a 1.20 agent re-enrolls within a second of an un-enroll, so an
older agent pushed afterwards arrived on an enrolled PC and was refused.
Revoke now takes {"blockreenroll": true, "reason": ...}; a blocked PC is
never issued a credential and keeps working on the shared key until
POST .../assets/<assetid>/allow-reenroll. The Enrollment page offers the
block when un-enrolling and shows blocked PCs with an allow button.
- First upgrade of old agents: agents before 1.20 check the server
certificate when downloading the upgrade, so a store wave failed on PCs that
do not trust a self-signed server while the Migration tab said ready. A
failed download now marks the PC blocked with "server certificate not
trusted by this PC". New Install-ShopDBServerCertificate.ps1 (GE-Enforce,
SYSTEM) adds a non-CA server certificate to the trusted roots after checking
its thumbprint; the server certificate page offers a .cer download and the
SHA-1 thumbprint.
- The generated installer writes ServerPins (with an optional next pin for a
planned rotation), can leave the shared key out so the PC waits for
approval, never touches an existing credential, and skips the trusted-root
import for a CA certificate.
- The agent re-reads server pins about every 60 seconds and recycles pooled
connections within 2 minutes, so a pin change needs no restart.
- An alert can only be acknowledged by a PC it targets.
- A wave no longer counts a PC as failed after a later wave upgraded it.
- The scripts report a pin mismatch only when the pin really is missing, and
otherwise log the real error.
- Runbook: trust step before the first wave, rollback order, pin re-read,
verifying the pin against the live URL; agent contract updated.
Mapped drives stopped reporting because the user-context path failed silently
in three separate places, and none of them left a trace to read back.
- Probe: launch via -EncodedCommand instead of staging a temp .ps1 and running
powershell.exe -File. A missing or quarantined script file fails BEFORE any
statement runs, so the script's own try/catch never sees it and the only
symptom is exit -196608.
- Spool: pin to the ShopFloor profile. Resolving it through WTSQueryUserToken
is privileged, endpoint protection blocks it, and on failure the tray
(writer) and service (reader) silently disagree about the path.
- Spool: consume after a successful parse, so a stopped tray cannot keep
re-reporting stale mapped drives as current. Unparseable files are left in
place for the next cycle.
- Logging: user-session components cannot write to Program Files or to the
SYSTEM-owned ProgramData log directory, and the file sink degrades to a
silent no-op when it cannot create its directory. Tray and probe now log to
C:\Users\ShopFloor\ShopDB\Logs, installs transcript to ProgramData, and the
sink records a SinkProblem instead of swallowing UnauthorizedAccessException.
- Installer: provision the operator spool/log directories with ShopFloor:Modify
before the tray first runs, and stop the transcript via trap so a throw
cannot orphan exactly the failed install worth reading.
- Upload cap 64 -> 128 MB: the self-contained tray publishes at ~69 MB and was
rejected on SIZE, but the size check returns before the PE is parsed, so the
operator only ever saw a version-detection failure.
AGENT-CONTRACT.md gains a normative section defining the three security
contexts, their log destinations, the fixed spool path and consume-on-read.
Agent 1.8.0 -> 1.8.1, tray 1.1.0 -> 1.1.1 so the deployed build is
identifiable from a log line.
Uploading an agent build whose product version carried semver build metadata
(1.5.0+abcdef) was rejected, because the typed version string was validated as
a dotted-numeric value before the file was ever read. That string is only a
hint from the operator, and the build metadata it carries is not a legal
directory name for the version store.
The authority is the dotted-numeric FILE version stamped in the binary PE
resource, so validation now happens after the upload is parsed: when the typed
value is missing or not dotted-numeric, fall back to the detected version, and
reject only when neither yields a usable one.
Append the staged agent version to the site-agent ZIP download name so DT operators see the build at a glance; the exe name inside the ZIP is unchanged so install.ps1 is untouched. Read the versioned name from Content-Disposition on the client. Group agent/tray binary management under a collapsed 'Upload New/Legacy Agents' section and default the key explainers to collapsed.
Restructure the agent and tray binary blocks around an auto-detected current-release upload plus a separate legacy/rollback upload that stages a version without seizing current. Order staged versions by version number for the active-version dropdown, add a PE version detect route, and drop stale dev-status tags and manual-staging copy.
Surface versioned agent/tray uploads, current-version display, and an expandable retained-version list with rollback in the Build Site Agent card, consuming the new siteagent status fields.
Operators need to see when a machine's running services or processes fall outside the desired state defined by policy. This adds a reusable monitor catalog (shipped read-only rows plus user-built local rows), computes drift server-side on agent check-in, and surfaces a warning on the endpoint panels. Remediation stays manual for now; automation can follow once demand is proven. Includes migrations 0006-0008 and the monitors API client.
Adds the two delivery actions to the endpoints command allowlist, each gated on a new endpoints.deploy permission ON TOP OF endpoints.command so pushing software is a heavier grant than a routine toggle. Enqueue validates update_app appversionid ownership and installer presence before queueing. New collector-authenticated download routes let the agent fetch installer packages and script bodies by digest rather than pre-staging them or sending bodies over the command wire. Wires the scriptsApi client, the siteagent generator routes, and a temporary dev-only Delivery test bench (view/route/nav) for exercising the path while the production UI is built.
Standalone one-shot enrollment tool that creates a ShopDB PC + Endpoint entry using only the endpoints collector key, reusing the existing collector adopt path. Collects identity, WMI hardware, and installed-software SBOM; requires admin. Server-side packaging route attaches to the existing endpoints blueprint; no core contract change.
Default fuzzy reconcile threshold to 0 and refuse merges when digit runs differ so numbered labels like Bay 311 never bind to Bay 310. Keep edit forms from clearing saved model fields when category filtering hides untyped rows. Includes in-progress computers policy and endpoints command feature work.