Commit Graph
711 Commits
Author SHA1 Message Date
mrhid6 bb698eba8a feat: let an agent create steps, workflows and monitors, inert until a human arms them 2026-09-09 08:07:26 +00:00
mrhid6 3bdbf33f90 fix: scope monitor runner and workflow targets to the caller's fleet
GET /api/monitors and GET /api/monitors/:id returned Monitor.Runner
unfiltered; for an agent-pushed monitor that field is literally a server
ID, so a restricted token learned which out-of-scope server a monitor
runs on directly, not merely that one exists. services.RedactMonitorRunner
replaces Runner with models.RunnerRestricted when it names a server
outside the caller's scope, resolved once via the new
services.VisibleServerIDs rather than per monitor. The monitor itself is
still returned — a restricted operator may legitimately need to see that
it exists and is up or down — only the runner field goes neutral; omitting
the monitor entirely was considered and rejected as more surprising than
one field changing. Runner "server" (control-plane-run) is never
touched. The MCP list_monitors/get_monitor_status projections never had a
Runner field to begin with, so REST and the tool surface already agreed;
a comment now records why.

GET /api/workflows and GET /api/workflows/:id returned
Workflow.TargetServerIDs unfiltered — directly naming out-of-scope
servers, worse than a count. services.FilterVisibleServerIDs narrows the
list to what VisibleServerIDs admits and reports hidden (no count) when
at least one target was dropped; WorkflowResponse wraps *models.Workflow
with a scoped TargetServerIDs and a TargetsRestricted flag. TargetTags is
left untouched — the tag vocabulary is already ruled acceptable to
expose. The MCP list_workflows/get_workflow tools get the identical
treatment: list_workflows' target count is now based on the filtered ID
list, and get_workflow's workflowDetail carries the same
TargetsRestricted flag, so a model that sees a filtered target list and
then has run_workflow refuse the same workflow for out-of-scope targets
is not left concluding the refusal invented a problem the list never
mentioned.

All four routes recorded in serverScopedRoutes as true; none is
boot-enforced, for the same substring-filter reason as the key routes
added in the previous round.
2026-09-09 08:03:26 +00:00
mrhid6 e06f9d5670 fix: scope the assignment count leaked by the key list
GET /api/keys returned each key's AssignedCount as a raw
CountDocuments over every non-revoked assignment, with no scope filter —
a tag-restricted token reading the list saw a nonzero count for a key it
can see nothing assigned to in its own scope, which is enough to tell it
an assignment exists on a host it must not know about. Same class of leak
getKey's assignment-list filter closed on the detail route, surviving on
the list route through a count instead of a server object.

services.ListKeys now takes the caller's tokenScope. An unrestricted
caller (empty scope) takes the original unfiltered per-key
CountDocuments with no extra work, so the common case is not slower. A
restricted caller resolves the visible fleet once via ListServers before
the per-key loop, then counts each key's assignments with an added
server_id $in filter — one extra query total, not one per key.
ListKeys had exactly one caller (listKeys), so the parameter went there
rather than adding a second entry point.

Recorded GET /api/keys in serverScopedRoutes as true; its path, like GET
/api/keys/:id, matches none of serverTouchingRoutes' substrings, so the
entry is not boot-enforced. Deliberately did not widen the filter to
catch "keys" — that would sweep in create/delete/private-key routes with
no server data at all. The real fix for this shape of gap is the
declare-by-default inversion already recorded as a follow-up.
2026-09-09 07:54:42 +00:00
mrhid6 dc6e1b3c29 fix: widen server-scope boot check, filter out-of-scope key assignments
serverTouchingRoutes in cmd/main.go filtered on "server"/"console"/an exact
workflows-run match, which is how POST /api/keys/:id/assign reached
production with no scope check and no boot-time signal at all: its path
names neither. Widen the filter to also match ":serverId" and "assign",
and document at the filter why a substring match is the weak part of this
design — a route that acts on a server without saying so in its path stays
invisible to it — noting that inverting the model (every /api route
declares itself, with an exemption list) would be the stronger fix and is
left as a follow-up. Re-running the mechanical check against the widened
filter swept in no route beyond what serverScopedRoutes already declared.

GET /api/keys/:id also leaked out-of-scope hostnames: it returned every
assignment for a key, server attached, unfiltered by the caller's tag
restriction. getKey now drops any assignment whose server fails
services.ServerInTokenScope before returning the list — silently, so the
response carries no count of what was removed — while still returning the
key itself, since a restricted token may legitimately hold a key also
assigned outside its scope. GetAssignmentsWithServers has exactly one
caller (getKey), so the filtering is done in the handler rather than
threaded into the service. Recorded in serverScopedRoutes; its path
matches none of the filter's substrings either, so it is not boot-enforced
and is kept as a considered decision, same as the assign/revoke entries.
2026-09-09 07:51:04 +00:00
mrhid6 cbf929fe2d fix: audit refused and failed mcp write calls, scope key assignment to token
Every early return from a write-tool handler skipped both the tool's own
LogCall and transport.go's gated LogCall (which only fires for reads), so a
blocked mutation attempt left no audit trail. registerSDKTool now routes
every write-tool error through LogDenied (fan-out and tag-scope refusals,
by gate name) or LogFailure (everything else), keeping the successful-write
path logging its own resolved server count exactly as before.

Also close a live scope gap surfaced while reviewing this: POST
/api/keys/:id/assign called services.AssignKey with an unscoped GetServer
lookup, so a tag-restricted token could assign a key to a server outside
its restriction. The handler now resolves the target through
GetServerScoped first, matching its sibling revoke route, and the route is
recorded in serverScopedRoutes.
2026-09-09 07:46:45 +00:00
mrhid6 b3651ab58c feat: add mcp write tools with a fan-out guard 2026-09-09 07:28:48 +00:00
mrhid6 191a8e9074 fix(mcp): correct fleet online status, unsafe version filter, audit totals
summariseServer compared Status against "online", a value never assigned
anywhere (the real vocabulary is pending/active/offline), so every server
misreported as offline. search_fleet's version_below used a lexicographic
comparison across dpkg/rpm/apk version schemes with no common ordering, so
it refuses that filter now and returns all matches instead of a wrong
answer. listAuditResult's Total carried the "shown" JSON tag and the
capped count; it now reports the real total alongside shown.
2026-09-09 07:24:09 +00:00
mrhid6 ed79df4270 fix: restore the vantage-shared go.sum entries dropped by go mod tidy 2026-09-09 07:17:53 +00:00
mrhid6 7ec97ae8c2 feat: Removed go.mod replace
Chart Release / chart (push) Successful in 17s
Server Deploy / deploy (push) Successful in 3m9s
2026-09-08 15:10:05 +00:00
mrhid6 b96cd85e43 feat: go mod tidy
Chart Release / chart (push) Successful in 18s
Server Deploy / deploy (push) Failing after 1m16s
2026-09-08 15:00:54 +00:00
mrhid6 971bcece44 feat: Updated openapi doc 2026-09-08 14:51:43 +00:00
mrhid6 333d729026 feat: Updated go deps
Chart Release / chart (push) Successful in 19s
Server Deploy / deploy (push) Failing after 1m20s
2026-09-08 14:44:45 +00:00
mrhid6 8dd68e34c1 docs: mark the api keys redesign plan implemented
Chart Release / chart (push) Successful in 20s
Server Deploy / deploy (push) Failing after 1m11s
2026-09-08 14:14:57 +00:00
mrhid6 e5b9894384 feat: surface the mcp endpoint and its scopes on the api keys page 2026-09-08 14:14:44 +00:00
mrhid6 14b947f791 feat: add mcp read tools for fleet, health and workflow data 2026-09-08 14:12:44 +00:00
mrhid6 98233b620c docs: fold the mcp task 12 token-form steps into the api keys redesign 2026-09-08 14:12:26 +00:00
mrhid6 2d6b5bd8a3 feat: restrict an api key to tagged servers from the create dialog 2026-09-08 14:12:10 +00:00
mrhid6 ac9cc57e7e feat: rebuild the create key dialog around a scope matrix and a preview 2026-09-08 14:10:37 +00:00
mrhid6 a0641e8ecb feat: summarise key posture above the ledger 2026-09-08 14:09:00 +00:00
mrhid6 67ac029354 feat: redesign the api key list as a ledger with lifetime bars 2026-09-08 14:07:56 +00:00
mrhid6 a0b5565a63 docs: explain why GET /api/mcp deliberately answers 405 in stateless mode 2026-09-08 14:07:54 +00:00
mrhid6 aedc388535 refactor: split the api keys panel into ledger, chips, lifetime and dialog 2026-09-08 14:06:02 +00:00
mrhid6 5e4c8afdd1 feat: model an api key's remaining lifetime as a single value 2026-09-08 14:03:49 +00:00
mrhid6 0166b17299 feat: serve the mcp endpoint behind the licence feature 2026-09-08 13:57:21 +00:00
mrhid6 674236bb76 feat: audit mcp tool calls and guard against fleet-wide fan-out 2026-09-08 13:50:28 +00:00
mrhid6 5943d98681 feat: add the mcp tool registry and its scope gates 2026-09-08 13:50:01 +00:00
mrhid6 7ea8e2fff0 fix: close out-of-scope server access in vulns, packages, run logs, and key revoke 2026-09-08 13:48:11 +00:00
mrhid6 f87986b4f7 feat: enforce token tag restrictions at the server resolution chokepoints 2026-09-08 13:43:38 +00:00
mrhid6 2481974b3a feat: carry the token tag restriction on the session 2026-09-08 13:37:05 +00:00
mrhid6 f9df426e6c feat: allow an API token to be restricted to servers by tag 2026-09-08 13:34:58 +00:00
mrhid6 e8e41f197a build: temporarily replace vantage-shared with local checkout 2026-09-08 13:31:56 +00:00
mrhid6 1f2b56ea29 Revert "chore: pick up the mcp licence feature from vantage-shared"
This reverts commit a2fe478c82.
2026-09-08 13:31:20 +00:00
mrhid6 8234bdf9f3 feat: add the mcp scope resource 2026-09-08 13:29:41 +00:00
mrhid6 a2fe478c82 chore: pick up the mcp licence feature from vantage-shared 2026-09-08 13:28:25 +00:00
mrhid6 a2eee958f0 docs: implementation plan for the mcp server, and creation tools in the spec 2026-09-08 13:24:03 +00:00
mrhid6 998e1c419d docs: design spec for the MCP server feature 2026-09-08 13:12:57 +00:00
mrhid6 d92ca7591f Rephrased secrets page
Chart Release / chart (push) Successful in 18s
Server Deploy / deploy (push) Successful in 1m3s
2026-09-08 10:25:01 +00:00
mrhid6 11a81e714f chore: Bump chart
Server Deploy / deploy (push) Successful in 14s
Chart Release / chart (push) Successful in 15s
chart/v1.1.1
2026-09-08 09:22:30 +00:00
mrhid6 c946a23afc fix: Fixed chart values
Server Deploy / deploy (push) Successful in 11s
Chart Release / chart (push) Successful in 14s
2026-09-08 09:21:38 +00:00
mrhid6 262795bf2e fix: Fixed ci workflow
Chart Release / chart (push) Successful in 19s
Server Deploy / deploy (push) Successful in 1m18s
2026-09-08 09:19:11 +00:00
mrhid6 3164f5ca22 fix: Fixed ci workflow
Chart Release / chart (push) Successful in 25s
Server Deploy / deploy (push) Failing after 1m9s
2026-09-08 09:16:44 +00:00
mrhid6 574057811f chore: remove go.work
Chart Release / chart (push) Successful in 18s
Server Deploy / deploy (push) Canceled after 42s
2026-09-08 09:10:48 +00:00
mrhid6 e0cc3988fc refactor: move vantagectl to vantage-ctl
vantagectl/ becomes the root of gitea.hostxtra.co.uk/vantage/vantage-ctl.
The command keeps the name vantagectl; only the repository and the image
path change, to vantage/vantage-ctl.

chart-release.yml's render checks are repointed at the new image. The
chart itself has no default backup.image and fails loudly without one, so
an existing cluster keeps working until someone changes the value.

go.work stays, now with a single use ./server entry: without it a go.work
further up the developer's filesystem is picked up instead.
2026-09-08 09:07:14 +00:00
mrhid6 44d9036440 refactor: move the agent and its installer to vantage-agent
agent/ becomes the root of gitea.hostxtra.co.uk/vantage/vantage-agent,
with installer/ alongside it, and agent-release.yml goes with them.

Releases now come from that repository, so the six places this server
generates or reads a release URL are repointed: both install scripts,
both update scripts, and the latest-version lookup in dispatch.go. The
agent/v* tag prefix is unchanged — those scripts grep for it.

Agents built before this move have the old mrhid6/vantage path compiled
into their self-update and will 404 on the push-button update. The
remedy is the /update one-liner, which this server generates and which
therefore has to ship first.
2026-09-08 09:00:22 +00:00
mrhid6 1c6d9e8495 refactor: move proto/ to vantage-shared
vantage.proto documents the hand-written types in shared/grpc/pb, and
nothing compiles it. Keeping it in a different repository from the Go
types it describes meant the one rule holding them together — add the
message to both in the same commit — could not be followed at all.

server's rebuild trigger loses proto/, which it only carried as
insurance against exactly that split.
2026-09-08 08:44:47 +00:00
mrhid6 f9dec9b230 refactor: move the public host out to vantage-site and vantage-docs
site/ and sitesvc/ become web/ and server/ in vantage-site; docsite/
becomes the root of vantage-docs. Their images move with them, to
vantage/vantage-site/{web,server} and vantage/vantage-docs.

Nothing here imported any of them, and sitesvc turned out to read no
database at all, so both cuts are clean. docker-compose.site.yml is
deleted rather than emptied: every service it held now ships with the
repository that builds it, and deploy/docker/docker-compose.yml is once
again exactly a self-hosted install.

Corrects four comments that named sitesvc for work it no longer does.
2026-09-08 08:41:11 +00:00
mrhid6 872699c38c refactor: move Vantage HQ out to the vantage-admin repository
admin/ and adminsite/ are extracted with their history to
gitea.hostxtra.co.uk/vantage/vantage-admin, where they are named server/
and web/ for what they are rather than for the services they run. Their
images move with them, to vantage/vantage-admin/{server,web}.

Nothing here imported them, so the cut is clean: the only coupling was
always at runtime, through admin writing into the control plane's
database. The parts of that contract this side enforces are unchanged and
still documented here — hq-sourced users, POST /license answering 409
cloud_managed, and FREE_INSTANCE_REAP_AFTER needing to match.

LICENSE_SIGNING_KEY now appears in no compose file in this repository.
Keeping it out used to be a rule someone had to remember; it is the
repository boundary now.

docker-compose.site.yml loses both services and gains a note on how the
host composes the three files together.
2026-09-08 08:13:55 +00:00
mrhid6 eb32d367c8 feat: Removed comments in workflow 2026-09-08 07:47:42 +00:00
mrhid6 a134f8443a fix: download vantage-shared before asking the module cache where it is
go list -m -f '{{.Dir}}' reports an empty Dir and exits 0 for a module that
is not in the cache, so on a cold runner swag was handed an empty --dir and
failed several steps later with 'dir:  does not exist'. Download first, and
fail loudly if the path is still not there.
2026-09-08 07:47:00 +00:00
mrhid6 ee1f9f3b32 refactor: consume vantage-shared as an external private module
shared/ is extracted to gitea.hostxtra.co.uk/vantage/vantage-shared and
pinned at v0.1.0 by server, agent, admin, sitesvc and vantagectl. The
replace directives and the ./shared entry in go.work are gone.

Every Go build now needs a credential for the private module: CI writes a
netrc per job from REGISTRY_USER + RELEASE_TOKEN and sets GOPRIVATE, and
the four Go Dockerfiles take it as a BuildKit secret rather than a build
arg, which would survive in the builder layer's history. RELEASE_TOKEN
needs read access to the vantage org.

admin, sitesvc and vantagectl now build from their own directory; only
server still needs the repository root, for default_steps/. The rebuild
triggers in server-deploy.yml lose their shared/ patterns, since a
service now moves when its own go.mod pin does.
2026-09-08 07:42:58 +00:00